destroyulator/LANES/LANE7-cubicle-hell.md
Monster Robot Party 006c324f1f LANE7: LEVEL 0 gets an entity, a tape, and a loop
THE ENTITY (Entity.gd) borrows from three places and each borrowing does a job:

  SLENDERMAN  it does not move while you look at it, and has closed the distance every
              time you look back. Looking is ALSO what builds the static — so the safe
              option (keep it in view) kills you slowly and the fast option (run) lets
              it catch up. That's the trap, and it's why Slender worked.
  XENOMORPH   it hunts 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. A loud enough noise overrides the freeze,
              because staring is a valid answer to a stalker and not a valid answer to
              something that just heard a sledgehammer hit a filing cabinet.
  BLAIR WITCH the level wraps: walk far enough and you come out of the opposite wall
              into the same room, because every room here IS the same room. A wrap is
              also when it repositions, so "I've been here before" and "it's already
              here" land in the same second.

It can't be fought. Getting caught doesn't kill you — it costs a lungful, a spike of
rage, and the knowledge that it can do that whenever it likes.

Deliberately unresolved design: 2.5 m, faceless, arms past the knees, cranium swept
backwards. Slenderman from the front, something else in profile. A silhouette your brain
finishes beats a monster it recognises. Blair Witch stick totems hang in the maze.

THE TAPE (Dread.gd) — one shader: grain, scanlines, chroma split, a wandering tracking
tear, an edge that breathes when it's near but unseen, and Slender interference driven
straight off the stare. The static IS the read-out; there's no meter for it.

TWO BUGS WORTH THE COMMIT MESSAGE
1. `seen_now` was a pure view-cone test with NO line of sight, so it built static
   through walls — and in a maze that's most of the time.
2. Fixing that exposed the real one: the generated maze was so dense there was no
   sightline longer than a few metres ANYWHERE, which kills the entity outright, since
   a stalker you can never see can never be stared at. Thinned the generator — skip a
   third of the lattice lines, much bigger gaps — which also matches the reference
   photographs far better. They're mostly open floor with occasional slabs, not
   corridors.

dev/probe_horror.gd verifies all of it headlessly, including hunting for a clear
sightline first (otherwise the Slender test silently tests nothing).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 19:25:28 +10:00

239 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# LANE 7 — CUBICLE HELL
*The game stopped being a smash sandbox and became a game about a man having a bad day.*
## The pitch
You are at work. The work is designed to provoke you. A meter fills. When it fills you
**snap**, and for half a minute you physically cannot go back to your desk until you've
broken enough things. Then you sit back down and try to earn the rest of your shift.
Falling Down, but the payslip is the scoreboard.
## The score IS your payslip
```
NET = WAGES (tasks completed) DAMAGES (everything you broke)
```
The load-bearing detail: **rage drains a flat amount per object destroyed, but each
object bills at its own value.** During a meltdown the optimal play is therefore to smash
a lot of cheap rubbish very fast — cartons, paperwork, monitors — rather than put the
sledgehammer through a filing cabinet. You don't choose whether you lose your temper. You
choose how expensive losing your temper is.
`FURY` (objects broken while actually melting down) is tracked separately and is
deliberately **not** money, so "I ended the shift owing them $2,400" stays a brag.
---
## BUILT (this lane, shipped)
| Piece | File | What it does |
|---|---|---|
| Tilt meter | `game/scripts/Rage.gd` | WORKING → MELTDOWN → COOLDOWN. Provocations fill it, clean work drains it slightly, ambient office drains your patience passively. During a meltdown the clock **stalls if you stop destroying** — you MUST break things. |
| The blood vessel | `game/scripts/RageOverlay.gd` | Red vignette that closes in with rage and jumps inward on every heartbeat; procedural **veins** that creep further into frame the angrier you get; a synthesized lub-dub whose BPM goes 62 → 172. Zero assets: shader built in code, veins are `draw_polyline`, heartbeat is a generated WAV. |
| The job | `game/scripts/Tasks.gd` | Stations you walk to and are **locked in place at** (you're at your desk — that's the joke). Two tasks so far, below. |
| The shift | `game/scripts/CubicleHell.gd` | 5-minute shift, wages vs damages, meltdown count, end-of-shift payslip with a verdict line. |
### The two tasks that exist
- **SPREADSHEET** — enter a figure in a named cell. Partway through, the selection
**silently drifts one cell** — no sound, no animation. Commit without noticing and the
figure lands in the wrong cell, you eat a big provoke, and you start again. This one
task is the whole thesis of the game in about forty lines.
- **PHOTOCOPIER** — run a copy job. It jams. Clearing it needs the **right tray**, and
the trays are labelled `1, 2, 2A, 3, 3B` because that's what head office decided.
---
## BUILT — more ways to be provoked
STAPLES and GUILLOTINE shipped (see `Tasks.gd`), plus `Ambience.gd` for the passive
attrition. The table below is what's still open.
### Still to build
Each is a `Tasks.Kind` + a `panel_state()` case. The framework takes them without
changes; the design constraint is that each must have **one specific, identifiable way of
wasting your time**, not just "it's slow".
| Task | The provocation |
|---|---|
| **Staple removal** | A stack with N staples. Click each. Too slow and the page tears — redo the sheet. The remover slips and takes a chunk out of the page. |
| **Guillotine / paper slicer** | Align the sheet to a line, pull the handle. Misalign and the cut is crooked → redo. The alignment guide is 2mm off and always has been. |
| **The phone** | Rings mid-task. You must answer it. It's hold music. Not answering keeps it ringing and ringing raises rage faster than answering does. |
| **Printer driver** | The printer is "offline". Toggling it takes four clicks in a dialog that moves its buttons. |
| **Reply-all** | An email thread with 40 recipients. You must reply to the sender only. The default is Reply All. |
| **Toner** | Replace the cartridge. It's the wrong model number by one digit. |
| **Timesheet** | Enter hours. It won't accept decimals. It also won't accept fractions. |
### Passive ambient provocations (no interaction, just attrition)
Someone microwaves fish. The air-con drips. A colleague's ringtone. The fluorescent tube
above your desk flickers at a frequency chosen to be maximally irritating. Each is a
small periodic `rage.provoke()` — cheap to add, and they're what makes the room feel hostile
rather than neutral.
---
## BUILT — modes
All four ship in `Modes.gd`; M cycles. The Gauntlet is `Gauntlet.gd` + `Cabinet.gd`
(drawers slide out as their own bodies, folders inside go live, and the shredder eats a
whole open drawer). Versus is still the open one.
### The original design notes
1. **CUBICLE HELL** *(built — the core)*. Shift timer, work, snap, wreck, repeat, payslip.
2. **TOTAL DESTRUCTION** — no job, no pay. Destroy 100% of the level. Scored on time.
3. **AGAINST THE CLOCK** — fixed timer, maximise damage value. The inverse of Cubicle Hell:
here expensive things are the *goal*.
4. **THE GAUNTLET** *(designed below — the most interesting one)*.
5. **VERSUS** — two players, one office. Explore later; the interesting version is
asymmetric (one working, one wrecking) rather than both wrecking.
### THE GAUNTLET (design)
> *"you need to destroy every file in a specific cabinet but which one, heaps cabinets,
> real subtle hints, time limit, and you have to actually pull each drawer out, empty
> files out, destroy them individually or in small lots unless you have a specific tool"*
- A room of **many** identical filing cabinets. Exactly one holds the target files.
- **Subtle hints only**: a memo on a desk names a client; a labelled folder on the copier;
a sticky note with half a reference number. No waypoint, no glow.
- Destruction is **granular**, not one-hit: pull a drawer → drawer slides out as its own
body → files inside become individual smashables → destroy singly or in small lots.
- **The perfect tool.** Each level has ONE tool that makes its gauntlet tractable — an
industrial shredder for files, a crowbar for the drawers, a lighter for a wastepaper
bin. It is somewhere in the level, and **the game does not tell you what it is.**
Working out "what would actually make this fast" IS the puzzle. With the wrong tool the
gauntlet is possible but you will not beat the clock.
- Needs: `Drawer.gd` (pull-out sub-body with its own contents), a `Container` concept on
Smashable, and a `Tool` flag on Weapon marking it as a level's key tool.
---
## BUILT — the other three workplaces
Shipped as `Levels.gd` specs consumed by `Floorplan.gd`. The notes below are the design
they were built from.
### Design notes
Reference floor plans provided: **Parks & Rec** (municipal, partitioned, outdoor patio),
**Silicon Valley hacker house** (domestic-open-plan, kitchen + pool), **The IT Crowd**
(basement, two rooms, glorious junk), plus the generic open-plan bullpen.
Each is a new script alongside `Office.gd` implementing the same interface:
```gdscript
build() # shell, fittings, lighting
configure_environment(env)
desk_spots() / chair_spots() / copier_spot()
spawn_point() / spawn_yaw()
```
They differ in **materials and hazards**, which is what stops them being reskins:
| Level | Signature material | Signature provocation |
|---|---|---|
| Municipal (Parks & Rec) | laminate, pinboard, civic beige | the public counter; forms in triplicate |
| Hacker house (Silicon Valley) | glass, whiteboard, drywall, a pool | standup meetings; someone's monitor wall |
| Basement IT (IT Crowd) | server steel, cardboard mountains, CRTs | "have you tried turning it off and on again" |
### On procedurally generating floor plans
The repo you found — `doratracyer/floor_plan`**won't help**. It's OpenSCAD that turns
hand-drawn SVG wall paths into 3D-printable STLs. It doesn't generate layouts, doesn't
parse plan images, and produces no room polygons or graph you could build a level from.
It's also GPL-3.0, which we would not want to link against.
**Better path, and we're most of the way there already:** `Office.gd` is a parametric
floor-plan builder that just happens to have its numbers hard-coded. Lift those into a
data spec —
```gdscript
{ rooms: [{name, x0, x1, z0, z1, walls, door_at}],
fittings: [{kind, pos, yaw}],
lights: {grid_x, grid_z},
spawn: {pos, yaw} }
```
— and a generic `Floorplan.gd` builds any of them. Then each workplace is a **data file**,
new levels are authorable in minutes, and a *generator* becomes possible later (rooms as a
BSP split of the footprint, doors on shared walls, fittings by room type) because the
consumer is already data-driven. Do the spec first, generation second.
---
## Asset needs (MODELBEAST / `tools/gen_office_props.py`)
Already generated procedurally: office chair, vending machine, microwave, potted plant.
Needed next, in rough priority order:
`filing cabinet with pullable drawers` (split mesh per drawer) · `desk phone` ·
`guillotine / paper slicer` · `stapler + staple remover` · `wall clock` · `whiteboard` ·
`server rack` · `pinboard` · `water cooler bottle (separate from base)` · `wastepaper bin` ·
`hole punch` · `shredder` (the gauntlet's perfect tool)
Rule of thumb established this lane: **if it's a box, build it in code**; if its silhouette
can't be faked by a box, generate it.
---
## LEVEL 0 — THE BACKROOMS *(built)*
> *"special back rooms level can get weird with it? stuff is invisible have to release
> dust or gas or something to coat items to see them to destroy them? and you inhale
> some of it too"*
`Levels.backrooms()` + `Dust.gd`. Mono-yellow, 2.55 m ceiling, damp carpet, a dense grid
of buzzing troffers, no windows and no way out.
**The walls are generated.** This is the site that proves the Floorplan spec can be
emitted rather than authored — a seeded RNG lays partial wall runs on a 4.6 m lattice
with random gaps, plus 22 freestanding pillars. It follows the reference photographs
rather than a maze algorithm on purpose: the Backrooms aren't corridors, they're big
offset slabs in an open floor. A proper maze would be legible, and legible is wrong.
**The mechanic argues with itself.** Everything smashable is invisible but solid — you
find things by walking into them. RMB with the extinguisher throws powder; anything in
the cloud is coated, glows faintly out of all that yellow for 7 s, then settles back to
nothing. Every cloud also goes in your lungs, and that haze is on YOUR side of the glass.
Spray more to see individual objects; see the room less. Past 72% you cough: camera jolt,
screen bloom, and a rage tick.
Rhythm: spray → switch to something heavy → break it before the coating settles → spray
again, coughing. Pairs naturally with TOTAL DESTRUCTION, because the level *is* a search.
Still open here: a second gas with a different trade (something that reveals further but
costs more lungs), and the entity. There is currently nothing in here with you.
### LEVEL 0's horror layer *(built)*
`Entity.gd` · `Dread.gd` · `Backrooms.gd` · `tools/gen_horror_props.py`
Three borrowings, each doing a different job. **Slenderman**: frozen while watched,
closer every time you look back, and staring is what builds the static — so the safe
play kills you slowly and the fast play lets it catch up. **Xenomorph**: it hunts sound,
and the objective is to make sound, so playing well feeds it. **Blair Witch**: the level
wraps, and a wrap is when it repositions, so "I've been here" and "it's already here"
arrive together.
The entity is deliberately unresolved — 2.5 m, faceless, arms past the knees, cranium
swept backwards. Slenderman from the front, something else in profile. A silhouette your
brain finishes beats a monster it recognises.
Two bugs worth remembering:
- `seen_now` was a pure view-cone test with **no line of sight**, so it built static
through walls. In a maze that's most of the time.
- Then the fix exposed the real one: the generated maze was so dense there was **no
sightline longer than a few metres anywhere**, which kills the entity outright — a
stalker you can never see can never be stared at. Thinned the generator (skip a third
of the lattice lines, much bigger gaps), which also matches the reference photos far
better; they're mostly open floor with occasional slabs.
Still open: it can only touch you, never end a run. A real fail state, a second entity
that behaves differently, and the "did that chair move?" pass on wrapping — the room is
identical, so *changing one thing* on a loop would be very cheap and very mean.