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: 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 decays when nothing is happening to you, and every loop
makes it grip harder (DREAD_LOOP_GRIP), so the place gets better at this the longer you
stay.
At 100% you die, and WHICH source pushed it over decides the death, which is what keeps
the three memes legible at the one moment it matters:
STARE "YOU LOOKED TOO LONG / it was still. it was always still. that was never the
safe part." (Slenderman)
REACH "IT WAS ALREADY IN THE ROOM / you were making so much noise."
Sprays. This is the gory one. (the xenomorph)
LOOP "YOU ARE STILL IN THE ROOM / you walked it eleven times. you will walk it
again." Eleven loops and the room simply keeps you. (Blair Witch)
Gore.gd is procedural — 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), the camera going down to land on its side on the carpet, then the
card. The sequence is deliberately SLOW: a jumpscare is a spike, but a death should take
its time, because the seconds spent watching the lens settle are where it lands. R wakes
you up.
Player.set_frozen() so you can't stroll around during your own death.
Renamed Backrooms' VHS layer to `tape` — `dread` is the meter now, and it was clearer
anyway.
dev/probe_death.gd drives all three deaths and the revive.
Note: the "Parameter material is null" warning only appears under the headless dummy
renderer; windowed runs are clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
249 lines
13 KiB
Markdown
249 lines
13 KiB
Markdown
# 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.
|
||
|
||
**DREAD and the three deaths** *(built)* — one meter, not a health bar. Nothing else in
|
||
this game does damage, so hit points would be a survival-horror UI bolted onto a
|
||
demolition game. DREAD is fed by being stared at, by proximity while unseen, by each
|
||
loop, and by your own coughing; it decays when nothing is happening to you, and every
|
||
loop makes it grip harder. At 100% you die, and whichever source pushed it over decides
|
||
which death you get, so the three memes stay legible: STARE (Slender), REACH (gory, the
|
||
xenomorph sprays), LOOP (the room simply keeps you at eleven). `Gore.gd` does blood on
|
||
the lens with drips, a wet burst, and the camera going down onto the carpet — slow on
|
||
purpose, because a jumpscare is a spike but a death should take its time.
|
||
|
||
Still open: a second entity that behaves differently, and the "did that chair move?" pass
|
||
on wrapping — the room is identical on every loop, so *changing exactly one thing* would
|
||
be very cheap and very mean.
|