E on an office chair (hands empty) mounts it: the player's capsule collision is disabled and it rides on top, WASD shoves it (strong) and yaws it (weak, on purpose — steering a chair is bad), momentum is real. Ramming breaks brittle things through the same brittle_speed path as any collision — no new damage code. Sledge the chair out from under yourself and the ride ends on the spot. E or death dismounts and restores collision. Split _try_mount (the crosshair ray) from _mount (the state change) so the ride mechanics are testable without synthetic camera aim. dev/probe_chair.gd: mount disables collision, player tracks the seat to 2mm, dismount restores it. CHAIR OK. All gates green: smoke clean, 6/6 sites 0.00 m/s, overlap CLEAN, all four LANE9 probes pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
402 lines
22 KiB
Markdown
402 lines
22 KiB
Markdown
# Destroyulator — the game
|
||
|
||
Mac-first (Apple Silicon / Metal), **Godot 4.7**, **Jolt** physics.
|
||
|
||
## Run it
|
||
|
||
**Editor:** open `/Applications/Godot.app`, import this folder (`game/`), press **F5** / ▶.
|
||
|
||
**CLI:**
|
||
```sh
|
||
/Applications/Godot.app/Contents/MacOS/Godot --path /Users/m3ultra/Documents/Destroyulater/game
|
||
```
|
||
|
||
## Controls
|
||
|
||
| Input | Does |
|
||
|---|---|
|
||
| **WASD / Space** | move / jump |
|
||
| **mouse** | look (Esc releases the cursor, click recaptures) |
|
||
| **LMB** | swing the equipped weapon |
|
||
| **1–6 · wheel · Q** | pick a weapon · cycle · swap to previous |
|
||
| **H** | cycle HUD style (Arcade / Minimal / Work Order / Dev) |
|
||
| **M** | cycle game mode (Cubicle Hell / Gauntlet / Total Destruction / Against the Clock) |
|
||
| **L** | cycle site (Scranton / Pawnee / Incubator / Sub-level 4 / Greengrocer / Level 0) |
|
||
| **G** | restart a Gauntlet run |
|
||
| **F** | report the disc in hand (Find-the-Misfiled-Disc) |
|
||
| **E** | work at the station you're standing at (locks you in place); else hop on / off an office chair under the crosshair; else pick up whatever's there |
|
||
| **LMB · G** | with something in hand: throw it · put it down |
|
||
| **drag ←→** | with a *record* held: slide the disc out of the sleeve |
|
||
| **digits · arrows · ENTER · SPACE** | while working: type · move cell · commit · the other thing |
|
||
| **B · R** | rain 500 bodies (stress gate) · reset the office |
|
||
|
||
## The six sites
|
||
|
||
`scripts/Floorplan.gd` builds a workplace from a **data spec**; `scripts/Levels.gd` holds
|
||
six of them. A site is a Dictionary — bounds, palette, walls with doors and glazing,
|
||
window runs, ceiling style, light grid and style, slabs, props, desk clusters, chairs,
|
||
smashables, gauntlet records-area and spawn — so a new one is authorable in minutes and a
|
||
generator can later emit the same structure.
|
||
|
||
| | palette / light | what makes it different |
|
||
|---|---|---|
|
||
| **SCRANTON BRANCH** | magnolia, grey carpet, troffers | the baseline office |
|
||
| **PAWNEE CITY HALL** | civic beige, blue-grey | a public counter, pinboards, over-partitioned |
|
||
| **THE INCUBATOR** | timber, white, 3 m, pendants | nobody has a desk; they work at a dining table |
|
||
| **SUB-LEVEL 4** | concrete, green strip lights | **no windows**, exposed services, a wall of servers |
|
||
| **THE GREENGROCER** | bright, pale, warm strips | everything is **soft** except the bottles — see below |
|
||
| **LEVEL 0** | one colour, everywhere | the Backrooms. Walls are **generated**, and everything you're here to destroy is **invisible** |
|
||
|
||
`dev/probe_levels.gd` builds every one and reports residual motion after a full second.
|
||
All six must read `0.00 m/s`.
|
||
|
||
### THE GREENGROCER — soft things
|
||
|
||
The only site whose contents are mostly **soft**, and that one material change rewrites
|
||
the level. `produce` is a new material in `Smashable.PROFILES` that doesn't throw shards
|
||
at all — `scripts/Splat.gd` bursts it instead: coloured pulp, a puddle that **stays**, a
|
||
squelch, and litres counted, because "how much juice did you make" is a far better score
|
||
for a greengrocer than "how many objects did you destroy".
|
||
|
||
Two knock-ons make the level play itself:
|
||
|
||
- Produce has a low `brittle_speed`, so **anything heavy landing on it squashes it**. A
|
||
pyramid therefore crushes its own bottom layer as it collapses, with no special code —
|
||
knock one off the top and the pile does the rest. A shove test turned 257 items into 45
|
||
and 42 litres.
|
||
- Puddles are **slippery**. The more mess you make, the less the floor cooperates. It cuts
|
||
your acceleration rather than your top speed, so it reads as sliding rather than as
|
||
being slowed down.
|
||
|
||
Stacks are built bottom-up by `Main._build_stack()` and nothing is glued or frozen —
|
||
they stand because they're stacked, so pulling one out of the bottom row does exactly
|
||
what you'd hope. The glass bottles behind the juice bar are the deliberate exception:
|
||
one shelf in the room that still rewards a proper swing.
|
||
|
||
Round produce is authored **centred**, not floor-centred like every other prop, because
|
||
a rolling body whose origin sits on the floor plane wobbles like a loaded die. It also
|
||
gets a **sphere** collider rather than the usual auto-boxed AABB — a box on an apple is
|
||
why nothing used to roll, and why the lattice needed hand-tuned spacing to nest.
|
||
|
||
Three things make a pyramid of spheres actually stand up, and all three are needed:
|
||
|
||
- **Measured radii.** `Main._glb_radius()` reads the asset instead of a table. The table
|
||
said a cabbage was 84 mm; the model is 213 mm, so every row of that pyramid was driven
|
||
a third of the way into the row beneath it.
|
||
- **Sphere lattice geometry.** Each fruit sits in the dimple between four below, so the
|
||
rise per row is `sqrt(4r² − step²/2)`, not a guessed multiple of `r`. Get it wrong and
|
||
every row above the first spawns in mid-air.
|
||
- **A tray.** A pyramid of frictionless spheres on a bare flat table cannot stand: nothing
|
||
holds the bottom row in, so the weight above wedges it outward and the display walks
|
||
itself apart in about a second. `Main._stack_tray()` frames each pile in four low
|
||
timber walls sized to its base row — which is what every greengrocer on earth already
|
||
does. Everything sleeps within 3 s.
|
||
|
||
**Hanging scales.** Brass shop scales hang down the aisles at chest height on real
|
||
`PinJoint3D` pendulums — a static dial and rod, a `scale-pan` rigid body pinned at its
|
||
own origin, so the pan can only rotate about the pivot. Walk into one and it swings; hit
|
||
it with the bat and it swings hard, and the pin holds to within a millimetre. It's
|
||
`steel`, so it never breaks: what you get instead is a heavy brass weight loose in a room
|
||
full of stacked fruit.
|
||
|
||
**Carrying and throwing.** E picks up anything under 6 kg that isn't a record — an apple,
|
||
a bottle, a stapler — and LMB throws it at 11 m/s, which is six times any brittle
|
||
threshold in the game. Thrown fruit bursts wherever it lands via the same
|
||
`brittle_speed` path as a collapse, so there's no separate "thrown object" code. The
|
||
carried body is frozen kinematic with its collision layers zeroed while it's in the hand,
|
||
and gets them back on release.
|
||
|
||
**The bag roll.** The station at the bag stand is `Tasks.Kind.BAGS`, and it is the purest
|
||
distillation of what this game is about. Tearing one off the roll is easy. Then you have
|
||
to open it, and the bag has two ends, only one of which opens, and **nothing anywhere
|
||
tells you which**. You rub — alternating ← and →, because mashing one key is not rubbing
|
||
— and either the meter climbs or it doesn't, and the only way to find out is to have
|
||
already wasted several seconds on the wrong one. SPACE turns it over. That's the answer.
|
||
The game never says so.
|
||
|
||
### LEVEL 0 — the Backrooms, and the dust
|
||
|
||
The only site whose walls are **generated** rather than authored, which is both the
|
||
honest test of the spec and the right call for the fiction: the place is meant to feel
|
||
endless and samey, and a seeded RNG does that better than a human placing slabs. It
|
||
emits partial wall runs on a coarse lattice plus freestanding pillars — the Backrooms
|
||
aren't corridors, they're big offset slabs in an open floor, and a proper maze would be
|
||
legible.
|
||
|
||
`scripts/Dust.gd` is its mechanic. **Everything smashable here is invisible but solid** —
|
||
you find things by walking into them. Right-click with the fire extinguisher throws a
|
||
cloud of powder; anything it touches is coated, glows faintly out of all that yellow for
|
||
seven seconds, and then settles back into nothing.
|
||
|
||
And you breathe it. Every cloud puts a little in your lungs, and the haze it builds is on
|
||
**your** side of the glass. So the mechanic argues with itself: spray more to see
|
||
individual objects, and see the room less. Past 72% you start coughing — the camera
|
||
jolts, the screen blooms, and it feeds the rage meter, because of course it does.
|
||
|
||
The intended rhythm is spray → switch to something heavy → break it before the coating
|
||
settles → spray again, coughing.
|
||
|
||
### …and there is something in there with you
|
||
|
||
`scripts/Entity.gd` borrows from three places, and each borrowing does a different job:
|
||
|
||
- **Slenderman** — it does not move while you are looking at it, and it has closed the
|
||
distance every time you look back. Looking at it is *also* what builds the **static**,
|
||
so the safe option (keep it in view) is the one that kills you slowly and the fast
|
||
option (run) is the one that lets it catch up. That's the trap, and it's why Slender
|
||
worked.
|
||
- **Xenomorph** — it hunts by **sound**, and the thing you came here to do is smash
|
||
furniture. Every break and every puff of powder drops a marker it walks toward.
|
||
Playing well is what feeds it; playing quietly means not playing. A loud enough noise
|
||
overrides the freeze entirely.
|
||
- **Blair Witch** — walk far enough in any direction and you come out of the opposite
|
||
wall into the same room, because every room here *is* the same room. And a wrap is
|
||
when the entity gets to reposition, so "I've been here before" and "it's already here"
|
||
land in the same second.
|
||
|
||
It can't be fought — weapons pass through. The outs are distance, silence, and not
|
||
looking, three things the rest of the game actively discourages.
|
||
|
||
**And after three loops there are two of them.** THE OTHER ONE shares the silhouette —
|
||
same height, same suit, same dark — so at range you cannot tell which you're looking at.
|
||
Its rules are the first one's rules **inverted**:
|
||
|
||
| | the hunter | the other one |
|
||
|---|---|---|
|
||
| while you look at it | frozen | **it comes** |
|
||
| while you don't | it closes | inert |
|
||
| noise | hunts it | **deaf** |
|
||
| correct play | keep it in view, stay quiet | **look away**; noise is irrelevant |
|
||
|
||
Every instinct the first one trains into you is the way the second one kills you. The
|
||
tells are close-range only, and all wrong in the same direction: its head is thrown back
|
||
so it's staring at the ceiling rather than at you, its arms are raised, and its feet
|
||
never reach the carpet.
|
||
|
||
### The room changes one thing
|
||
|
||
Every loop, **exactly one thing** is different. Never two — that constraint is the whole
|
||
effect. Change nothing and a loop is just a teleport; change several and it reads as a
|
||
new room, which is the opposite of what this place is for. One change means you're never
|
||
sure whether you noticed something or imagined it, and since the room is otherwise
|
||
identical, when you *are* sure it's worse.
|
||
|
||
It escalates: early loops move or turn a prop, or kill a bank of lights. Later ones hang
|
||
a totem that wasn't there, permanently reveal an object nobody dusted, or put the entity
|
||
where you were standing a moment ago.
|
||
|
||
### DREAD, and dying
|
||
|
||
**No health bar.** Nothing else in this game does damage, so hit points would be a
|
||
survival-horror UI bolted onto a demolition game. Instead there is one meter — **DREAD** —
|
||
fed by every way this place gets at you: being stared at, something close behind you,
|
||
each loop, and your own coughing. It falls when nothing is happening to you, and every
|
||
loop makes it harder to shake.
|
||
|
||
At 100% you die, and **which** death you get is decided by whichever source pushed it
|
||
over — so the three memes stay legible in the moment it matters:
|
||
|
||
| | how | the card |
|
||
|---|---|---|
|
||
| **Slenderman** | you looked too long | *YOU LOOKED TOO LONG — it was still. it was always still. that was never the safe part.* |
|
||
| **the xenomorph** | it reached you, and it sprays | *IT WAS ALREADY IN THE ROOM — you were making so much noise.* |
|
||
| **Blair Witch** | eleven loops of the same room | *YOU ARE STILL IN THE ROOM — you walked it eleven times. you will walk it again.* |
|
||
|
||
`scripts/Gore.gd` does the rest, procedurally: seeded blobs that swell on the lens and
|
||
then bleed downward in streaks, a wet burst at the camera (physical deaths only — being
|
||
taken by the static shouldn't spray), and the camera going down to land on its side on
|
||
the carpet. The sequence is deliberately **slow** — a jumpscare is a spike, but a death
|
||
should take its time, because the seconds you spend watching the lens settle are where it
|
||
actually lands. `R` to wake up.
|
||
|
||
`scripts/Dread.gd` is the tape: grain, scanlines, chroma split, a wandering tracking
|
||
tear, and Slender's interference driven straight off the entity's stare — the HUD element
|
||
that isn't a HUD element.
|
||
|
||
## The four modes
|
||
|
||
`scripts/Modes.gd`, cycled with M.
|
||
|
||
- **CUBICLE HELL** — the main one, below.
|
||
- **THE GAUNTLET** — `scripts/Gauntlet.gd` + `scripts/Cabinet.gd`. Five identical
|
||
cabinets, each labelled with an alphabetical range. The brief names a **client**, never
|
||
a cabinet, so working out that HENDERSON lives in F–J is the puzzle — nothing glows.
|
||
Destruction is granular: pull a drawer, it slides out and goes live, and the folders
|
||
inside become individual smashables. Doing that by hand inside the clock isn't really
|
||
possible — **every site hides a shredder that eats a whole open drawer at once, and the
|
||
game never says so.** Noticing it is the level.
|
||
- **TOTAL DESTRUCTION** — no job, no pay. Clear the whole site, scored on time.
|
||
- **AGAINST THE CLOCK** — fixed timer, maximise damage *value*. The exact inverse of
|
||
Cubicle Hell: here the expensive things are the goal rather than the mistake.
|
||
|
||
## CUBICLE HELL
|
||
|
||
The mode the game is about. See [`../LANES/LANE7-cubicle-hell.md`](../LANES/LANE7-cubicle-hell.md)
|
||
for the full design.
|
||
|
||
`Rage.gd` is the tilt meter: work provokes it, clean work drains it slightly, and at 100%
|
||
you SNAP into a forced destruction window that **stalls if you stop destroying**.
|
||
`RageOverlay.gd` is the blood vessel — a vignette that closes in, procedural veins that
|
||
creep further into frame, and a heartbeat that goes 62 → 172 BPM. All of it generated in
|
||
code; no textures, no shader files, no audio assets.
|
||
|
||
`Tasks.gd` is the job — four stations, each with one designed way of wasting your time:
|
||
|
||
- **SPREADSHEET** — the selected cell **silently drifts one cell** partway through entry.
|
||
No sound, no animation. Commit without noticing and the figure is in the wrong cell.
|
||
- **PHOTOCOPIER** — jams, and wants the right tray out of `1, 2, 2A, 3, 3B`.
|
||
- **STAPLES** — SPACE lifts a staple, then it must be pulled the way it's bent. Dither
|
||
and the sheet tears and the bundle reprints.
|
||
- **GUILLOTINE** — align to a mark and cut. **The guide has a permanent offset** and
|
||
nothing on screen admits it; you have to learn to distrust the equipment.
|
||
|
||
`Ambience.gd` is the attrition you can't play around: the tube above your desk flickers
|
||
(and actually flickers, on the real lights), someone puts fish in the microwave, a phone
|
||
rings four desks away, reply-all to forty-one recipients. No interaction, just a periodic
|
||
tick on the meter — it's what makes the room hostile rather than merely beige.
|
||
|
||
`CubicleHell.gd` scores it as a payslip: **wages − damages**. Rage drains a flat amount
|
||
per object but each object bills at its own value, so the meltdown decision is *smash a
|
||
lot of cheap things fast* versus *one expensive swing*.
|
||
|
||
## The fun layer (LANE9)
|
||
|
||
Three antagonists to the *tidy* impulse, all riding systems that already exist:
|
||
|
||
- **The Roomba** (`scripts/Roomba.gd`) — a robot vacuum that deploys the moment the first
|
||
thing breaks and patrols *eating your debris*, docking points as it goes. It's company
|
||
property (`steel`), so the weapon matrix already knows it: bare hands clank, a crowbar
|
||
dents it (it panics and flees), a sledge kills it — and then the payslip bills you for it.
|
||
Not in LEVEL 0; nothing cute lives in the Backrooms.
|
||
- **The incident report** (`scripts/Receipt.gd`) — when a meltdown ends, HR prints a
|
||
dot-matrix receipt itemizing what you broke during it: object · unit $ · ×N · DEDUCTED.
|
||
A meltdown where you broke nothing prints nothing.
|
||
- **The office chair** — `E` on one to ride it. WASD shoves it (strong) and steers it
|
||
(badly); momentum is real and ramming breaks things through the same `brittle_speed`
|
||
path as any collision. Sledge your own vehicle and the ride ends where you're sitting.
|
||
|
||
## The weapon-vs-material matrix
|
||
|
||
This is the decision the game is built on. A swing deals `Weapon.power × Weapon.vs[material]`
|
||
against that material's `hp`, and there are three outcomes: it **breaks**, it **dents**
|
||
(knockback + a chip of damage), or it's **futile** — a dead clank, no score, no combo,
|
||
and a HUD nudge to switch tools.
|
||
|
||
| | cardboard | paper | vinyl | glass | wood | steel |
|
||
|---|---|---|---|---|---|---|
|
||
| **Bare Hands** | ok | good | ok | ok | poor | useless |
|
||
| **Box Cutter** | **shreds** | **shreds** | good | poor | useless | useless |
|
||
| **Cricket Bat** | good | poor | **great** | **great** | ok | poor |
|
||
| **Crowbar** | ok | poor | ok | good | good | **great** |
|
||
| **Sledgehammer** | ok | poor | ok | **great** | **great** | **great** |
|
||
| **Fire Extinguisher** | ok | poor | ok | **great** | good | good |
|
||
|
||
Tuned in one place: `scripts/Weapon.gd` (`vs` tables) and `scripts/Smashable.gd`
|
||
(`PROFILES[kind].hp`). The office printer is the level boss — steel at `toughness_scale
|
||
= 5.0`, so it's a real fight with anything but the sledge.
|
||
|
||
Weapons also differ in **when** they connect, not just how hard: the hit lands at
|
||
`Weapon.contact` through the swing animation, so the sledgehammer commits late and heavy
|
||
while the box cutter is near-instant.
|
||
|
||
## The viewmodel
|
||
|
||
`scripts/ViewModel.gd`. Real rigged hands (mixamorig, all 20 finger bones per hand)
|
||
holding real weapon meshes.
|
||
|
||
The arms rig is **instanced once per hand** and each instance is *placed* so its own hand
|
||
bone lands on the grip — no IK solver. That works because in first person you only ever
|
||
see forearm and hand. Placement solves two aims at once: the fist's bore lines up with
|
||
the weapon shaft, and the forearm runs back toward where that shoulder would be. Both
|
||
directions are **measured off the rig** at load (pinky-knuckle → index-knuckle for the
|
||
bore, elbow → hand for the forearm), so the grip survives re-tuning `grip_rest_rot`
|
||
instead of needing a fresh set of hand-authored euler angles.
|
||
|
||
Motion is layered on one node: look-sway with spring-back, walk bob scaled by speed and
|
||
weapon heft, idle breathing, landing dip, and a keyframed swing arc per archetype
|
||
(`overhead` / `horizontal` / `jab` / `thrust`).
|
||
|
||
## Assets
|
||
|
||
| Path | Source |
|
||
|---|---|
|
||
| `assets/viewmodel/fps_arms.glb` | `tools/gen_fps_arms.py` — cut from the GODVERSE modular character kit |
|
||
| `assets/viewmodel/{bat,sledge,crowbar,cutter,extinguisher}.glb` | `tools/gen_viewmodel.py` — procedural, grip at origin, shaft +Y |
|
||
| `assets/store/*.glb` + `*.fractured.glb` | Lane 2 prop batch; the fractured sibling drives real chunk destruction |
|
||
| `assets/art/sleeves_*.jpg` | 3×3 sheets of generated cover art, windowed per record via uv1 offset |
|
||
|
||
Regenerate either set with:
|
||
```sh
|
||
/Applications/Blender.app/Contents/MacOS/Blender --background --python tools/gen_viewmodel.py -- --render
|
||
```
|
||
|
||
## Dev harness
|
||
|
||
`dev/` is not shipped. Because macOS screen-recording permission isn't available to the
|
||
CLI, the game records **itself**:
|
||
|
||
```sh
|
||
Godot --path game --resolution 1280x720 --write-movie /tmp/cap.avi --quit-after 2250 dev/demo.tscn
|
||
```
|
||
|
||
`dev/DemoDriver.gd` drives the player through a scripted tour (every weapon, the material
|
||
matrix, the boss, all four HUDs) so a change can be eyeballed frame by frame without a
|
||
human at the keyboard. `dev/probe_*.gd` print rig/scale/placement numbers — renders are
|
||
too coarse to tell a 5 cm error from a 50 cm one.
|
||
|
||
## Smoke test before committing
|
||
|
||
```sh
|
||
/Applications/Godot.app/Contents/MacOS/Godot --headless --path game --quit-after 300
|
||
```
|
||
|
||
Zero script errors required. After adding any `class_name` script, run
|
||
`Godot --path game --editor --quit` first — headless Godot won't rescan a stale class cache.
|
||
|
||
## The level
|
||
|
||
`scripts/Office.gd` builds LEVEL 01: an open-plan office after the Dunder Mifflin floor
|
||
plan — bullpen of facing desk pairs behind cubicle partitions, reception, a glass-walled
|
||
manager's office, conference room, break room with vending machines, a copier alcove for
|
||
the printer boss, and a warehouse roller door. Drop ceiling, fluorescent troffers (which
|
||
are the actual light sources), magnolia walls, grey carpet, a window wall with blinds.
|
||
|
||
Office owns the **shell and static fittings**; `Main._populate()` owns everything
|
||
**smashable**, and asks Office where things go (`desk_spots()`, `chair_spots()`,
|
||
`copier_spot()`). Walls, desks and counters are boxes built in code because they are
|
||
boxes; chairs, vending machines, the microwave and the plant come from
|
||
`tools/gen_office_props.py`, because a box reads as obviously wrong for those.
|
||
|
||
### The spawn guard
|
||
|
||
Placing ~40 props by formula means something will eventually spawn a few centimetres
|
||
inside something else, and Jolt's depenetration is not gentle — a chair overlapping a
|
||
desk left the building at 500 m/s on frame one, knocking over the furniture on its way.
|
||
|
||
`Main._physics_process()` speed-limits dynamic bodies for the first 0.75 s: enough for a
|
||
real overlap to push apart calmly, far too little to launch anything. Every offender is
|
||
named once in a warning with **the position it was placed at**, so the underlying overlap
|
||
stays visible and fixable rather than silently papered over.
|
||
|
||
Verify a level is genuinely at rest with:
|
||
```bash
|
||
/Applications/Godot.app/Contents/MacOS/Godot --headless --path game --quit-after 300 dev/demo.tscn
|
||
```
|
||
The demo's first act touches nothing and prints total body speed each second. It must
|
||
read `0.00 m/s`, and no `[spawn]` warnings should appear.
|
||
|
||
The guard is a net, not a test: it only fires when Jolt happens to resolve a pair
|
||
violently on the frames it's watching, and a symmetric overlap can sit quietly for a
|
||
whole run and then launch a filing cabinet the day you change something unrelated. That
|
||
is exactly what happened — a `cardboard-box` pair had been interpenetrating in four
|
||
levels for weeks and only surfaced when an unrelated change shifted the RNG stream.
|
||
`dev/probe_overlap.gd` finds them by measurement instead of by luck, comparing every pair
|
||
of dynamic colliders across all six sites:
|
||
|
||
```bash
|
||
/Applications/Godot.app/Contents/MacOS/Godot --headless --path game --script dev/probe_overlap.gd
|
||
```
|
||
It must print `CLEAN`. The box asset is 1.35 m wide, which is why the specs now stack
|
||
boxes into piles instead of dotting them about, and why the Backrooms scatter uses
|
||
rejection sampling with a 1.6 m minimum.
|