hallway8 / docs /ARCS.md
alvations's picture
Deploy Hallway 8 (multi-arc memory game)
58e1249 verified
|
Raw
History Blame Contribute Delete
18.2 kB
# Arc roadmap
This is the living plan for the game's arcs (skins/backstories). It records the
design rules every arc must follow, what's already built, and the queue of
future arcs so we can pick up any of them later without re-deriving the idea.
Each arc is one self-contained `data/arcs/<id>.json` file plus a
`body[data-skin="<name>"]` block in `static/style.css`. See the README section
*Adding an arc* for the file schema. New arcs appear on the opening screen
automatically.
---
## Design rules (apply to every arc)
The mechanic is fixed: move through a place that should be identical each loop;
**turn back** if something changed, **go on** if nothing did; eight correct
calls in a row wins, one wrong call resets. What differs per arc is the story,
vocabulary, skin β€” and the *flavour* of the anomalies. Every arc must include:
1. **A strong motivation ("the hook").** A concrete in-world reason you're stuck
doing this, revealed in the intro β€” the equivalent of Stairway 8's
out-of-order elevator. Never "you are being tested" with no diegetic cause.
2. **An NPC.** A recurring character that is normally passive but can become the
anomaly β€” turning, watching, approaching, vanishing, duplicating. Every arc
needs its own recurring uncanny figure.
3. **A loop-aware NPC twist.** At least one NPC anomaly that breaks past physical
change into the uncanny: the character *acknowledges you* or the loop itself
(waves, beckons, mouths/says "again"). Property key convention: `knows` for
silent figures, `speaks` for ones that can talk.
4. **A psychological layer (`sense`).** A non-physical property whose baseline is
your ordinary state and whose anomalies are perceptual/emotional wrongness β€”
losing count, a calm that shouldn't be there, a conviction you're going the
wrong way. Anomalies beyond objects, per the "twists" requirement.
5. **Meta drift (free).** The engine already drifts the heading, wording, pacing,
weight, spacing and background every loop β€” no per-arc work needed, but keep
`title_variants` themed.
6. **Win-screen strings (in `meta`).** Three short, arc-flavoured lines the exit
screen needs, so the reward matches the place:
- `share_prompt` β€” the invitation above the share row, phrased for this arc's
exit (Hallway: *Leave the door open.*, Stairway: *Leave the door open
below.*, Coach: *Hold the doors.*).
- `attempt` β€” the "look back" line shown when the run took more than one try.
**Must contain the `{n}` placeholder** (the run count), spliced in front so
it localizes cleanly, e.g. *{n} times down this corridor, and this time it
opened.*
- `attempt_first` β€” the line for a clean first run (no placeholder), a small
reward for a flawless escape, e.g. *First time through, and the corridor let
you go.*
Keep anomalies *plausible*, never loud. One property mutates per anomalous loop,
and it is always among the details shown that loop (the engine guarantees this).
---
## Shared tuning (all arcs inherit this β€” do not re-tune per arc)
These curves are engine-wide, not per-arc. A new arc gets them for free; only
change them if you intend to change the feel of *every* arc.
### Difficulty ramp β€” `hallway.py`
The chance a loop contains an anomaly is a function of the player's level, so the
first stage after entering leans toward "nothing changed" (learn the baseline by
proceeding) and gets harder as you descend/advance:
```
anomaly_chance(level) = min(ANOMALY_MAX, ANOMALY_BASE + ANOMALY_STEP * level)
ANOMALY_BASE = 0.02 # level 0: VERY low, no baseline seen yet
ANOMALY_STEP = 0.076 # per level cleared (steep, to reach the cap by ~L7)
ANOMALY_MAX = 0.55 # capped so "always turn back" never wins
```
So the opening loop (level 0) is only ~2% (easy 1%, hard 3%), rising steeply to
the cap by around level 7 (normal 55%, easy 30%, hard 60%). The very-low opening
is deliberate: on your first loop you have no baseline, so being turned back
would be a coin flip you cannot win. It is not zero (a rare early jolt keeps the
place honest). Level resets to 0 on a wrong call, which re-eases the first stage,
intentional re-onboarding, not a bug. There is no level-0 floor (the low opening
is the point; levels 1+ ramp well clear of zero so "always continue" still
loses). Because content is *shown* fairly (the mutated property is always among
the details displayed), the ramp changes frequency, never fairness. Guard: keep
`ANOMALY_MAX ≀ ~0.55` or turning back becomes dominant.
### Aggro-item / late reveal (`hallway.py`)
Each arc's `meta.late_props` names a couple of small, inconspicuous properties
(existing details, not new ones). Once per climb, `build()` may **hold some out**
of the early loops so they first surface mid-run. `mem.held` maps each held prop
to its reveal level:
```
reveal level ∈ [2, GOAL-2] # ~level 2-6 at goal 8, never the last
hold: hard β†’ always, 1 or 2 items (the "not everything up front" pressure)
normal β†’ rare (~15%), a single item; rotation is the everyday replay hook
easy β†’ never
```
Holding 1-2 items reliably is a **hard-mode** feature. Normal mode leans on the
ordinary fact that only 4-6 of ~9 details show each loop (and Act 2 touch
outcomes vary), so a normal player meets almost every encounter over a few runs
without ever being deliberately starved. Not showing everything every run is a
core principle and the replay hook.
Fairness guard (`mem.seen_props`): an anomaly may only land on a detail the
player has already seen at **baseline on an earlier loop**, so the held item is
never the change on the loop it first appears. Its debut is always a calm
baseline; only later can it move. This keeps a late arrival a memory test, never
pure luck, even at the last level (by then it is long established). Pick
`late_props` that are genuinely minor (a fixture, a small object), never the NPC
or the `sense`.
### Doubt prompt curve β€” `static/game.js`
The confidence prompt is not shown every turn. After a Go-on/Turn-back choice,
the chance it interrupts grows as you near the exit:
```
chance = min(0.85, 0.12 + level * (0.7 / goal)) # ~12% early β†’ ~73% at level 7
```
- **Certain / Almost sure** (conf β‰₯ 3) β†’ the choice commits and is judged.
- **Guessing / I think so** (conf ≀ 2) β†’ does *not* commit; returns you to the
scene for exactly **one** re-decide (the `hesitated` flag blocks a second
prompt that turn). The re-look is the only "reward" β€” no points, no safety net.
This is pure client-side UX: confidence never enters the server's correctness
formula (`correct = (choice == "back") == has_anomaly`). New arcs need no work to
inherit it.
### Session state β€” `app.py`
State is keyed by a client-echoed `sid` (X-Sid header), **not** the session
cookie, so an arc and its progress survive in a cross-site iframe (Spaces). Arcs
never need to touch this.
### Win screen & sharing β€” engine-wide
The exit screen is shared by every arc; an arc only fills in the three `meta`
strings above. The engine handles the rest:
- **Look back (fleeting run count).** A quiet toggle under "You're out." surfaces
the arc's `attempt` / `attempt_first` line, then fades after a few seconds and
reclaims its space so the eye settles on sharing or replaying. The run count is
a per-session counter in `PlayerMemory` (`attempts`, resets + 1), and the line
is localized server-side like win text.
- **Sharing.** One of eight cryptic "invitation" lines (shared UI strings
`share_1..8`) is chosen at random, with the arc's `share_prompt` above it.
Mobile-first: where a native share sheet exists it is the single button (it
reaches Instagram, Snapchat, X, Messages, everything); otherwise explicit X /
Facebook / LinkedIn links plus Copy link. The shared URL unfurls as the game's
own "8" Open Graph card (`static/img/og.png`).
New arcs inherit all of this for free once the three `meta` strings are set.
---
## Built
*These three are the only production arcs for the current phase. Everything in
"Planned", "Backlog", and "Brainstorm" below is idea-stage only β€” keep it out of
the live 3-arc game until an idea is deliberately promoted up into this table.*
| Arc | Hook | Loop unit | NPC (+ twist) | `sense` anomalies | Skin |
|-----|------|-----------|---------------|-------------------|------|
| **Hallway 8** (`hallway-eight`) | Locked in after hours; a sign points to the fire exit | corridor | man in grey suit (`knows`: turns/beckons/"again") | longer, forget, calm | cold teal |
| **Stairway 8** (`stairway8`) | Elevator out of order; descend on foot | landing | figure on the stairs below (`knows`: looks up, waves, ascends) | deeper, forget, wrong-way | emergency green |
| **Coach 8** (`coach8`) | Conductor sent you to coach 8; the train never reaches it | carriage | sleeping passenger (`speaks`: murmurs "again", "you were just here") | forget, calm, watched | cabin amber |
---
## Planned (not yet built)
Each entry is enough to implement directly. Ordered by how strong/fresh the loop
feels; pick any.
### Platform 8 (`platform8`) β€” the underpass
- **Hook:** last train of the night; the turnstiles are shut and a sign routes
you through the pedestrian underpass β€” FOLLOW SIGNS TO PLATFORM 8.
- **Loop unit:** identical tiled underpass segments. **Go on** = walk on /
**Turn back** = double back.
- **NPC:** a busker sitting against the wall with an open case. Twist (`knows`):
stops playing to watch you; case is suddenly empty/overfull; plays the same
bar as you pass "again."
- **Physical anomalies:** tiled advert, the drip-puddle, map's "YOU ARE HERE"
dot, poster date, platform number on the sign, tile colour, a pigeon.
- **`sense`:** the tunnel feels longer; you forget which platform you wanted; a
certainty you already caught the train.
- **Skin:** cold blue-white station tile (`--hue: 205`).
### Aisle 8 (`aisle8`) β€” locked in the supermarket
- **Hook:** you looked up and the shop had closed around you; the tannoy loops
"please proceed to checkout 8 to exit."
- **Loop unit:** identical grocery aisles. **Go on** = next aisle / **Turn
back** = previous aisle.
- **NPC:** a night-shift stocker with a trolley, back to you. Twist (`knows`):
turns and stares; trolley abandoned mid-aisle; softly says "again" over the
muzak.
- **Physical anomalies:** a shelf gap, a product facing the wrong way, a price
label, the end-cap promo, the muzak track, the abandoned trolley, floor sheen.
- **`sense`:** the aisle feels longer; you forget your list; a calm certainty
you've already paid.
- **Skin:** flat retail fluorescent, a touch too bright (`--hue: 60`, high L).
### Ward 8 (`ward8`) β€” the hospital at night
- **Hook:** visiting hours are over; the night sign says the way out is via Ward
8. *(Handle tone with care β€” dread, not gore.)*
- **Loop unit:** identical hospital corridors. **Go on** / **Turn back**.
- **NPC:** a patient in a gown standing in a doorway, or a nurse at the far
station. Twist (`knows`): the figure has moved to the next doorway; is facing
you; mouths "again."
- **Physical anomalies:** a gurney's position, the room-number plate, a
flickering "NIL BY MOUTH" sign, the hand-gel dispenser, a chart on a door,
distant monitor beeps, a wheelchair.
- **`sense`:** the corridor feels longer; you forget why you came; a wrong calm.
- **Skin:** pale clinical green-grey (`--hue: 160`, low sat).
### Level 8 (`level8`) β€” the underground car park
- **Hook:** your ticket says Level 8; the lift to the street is dead, so follow
the ramp up. (Ascending mirror of Stairway 8.)
- **Loop unit:** identical parking rows. **Go on** = up a ramp / **Turn back** =
back down.
- **NPC:** a figure between the parked cars, or someone sitting in a driver's
seat. Twist (`knows`): the seated figure's headlights flick on as you pass; it
has moved cars; it waves.
- **Physical anomalies:** a parked car's colour, the level number on the pillar,
a flickering strip light, the arrow painted on the floor, an oil stain, a
distant engine, a car alarm.
- **`sense`:** the level feels deeper; you forget where you parked; certainty
you're descending, not climbing.
- **Skin:** concrete grey with sodium-orange pools (`--hue: 30`, low L).
### Gate 8 (`gate8`) β€” the airport after the last flight
- **Hook:** you dozed off; the terminal's near-empty and the board shows one
route left β€” EXIT VIA GATE 8.
- **Loop unit:** identical concourse stretches / travelators. **Go on** /
**Turn back**.
- **NPC:** a lone cleaner with a floor-buffer, or a single seated passenger.
Twist (`knows`): stops buffing to watch; the seated passenger is gone/closer;
a gate announcement says your name.
- **Physical anomalies:** the gate number, a departures-board row, the duty-free
window display, an abandoned bag (unsettling), the announcement chime, a clock,
a potted plant.
- **`sense`:** the concourse feels longer; you forget your destination; a calm
that you've already boarded.
- **Skin:** glassy blue night-terminal (`--hue: 215`).
---
## Backlog ideas (thin β€” flesh out before building)
- **Museum after close** β€” galleries loop; NPC is a security guard or a statue
that isn't always in the same pose.
- **Motel corridor** β€” reach room 8 / the exit; NPC is someone at an ice machine.
- **Ferry decks** β€” climb decks to the exit; NPC a lone smoker at the rail;
motion + sea outside the windows.
---
## Brainstorm β€” later phases (idea-stage, NOT production)
Fresh settings that complement the transit/civic bias of the Planned queue: a
knowledge space, a money/dread space, a modern-tech space, a sacred space, and a
play-gone-wrong space β€” five different emotional registers, still melancholy
rather than violent. Each satisfies the design rules (hook, loop unit, NPC with
a loop-aware twist, physical anomalies, a `sense` property). Suggested skin hues
are candidates only; whichever get built must stay visually distinct from the
live blue/amber/green and from each other.
### Stacks 8 (`stacks8`) β€” the library at lockup
- **Hook:** the lights dropped to night mode while you read; a card says the way
out is past Aisle 8 of the stacks.
- **Loop unit:** identical rows of tall shelving. **Go on** = next aisle /
**Turn back**.
- **NPC:** a night librarian re-shelving from a trolley, back to you. Twist
(`knows`): stops, turns to watch, mouths "again". Or a reader asleep at a
carrel (`speaks`: "you already returned it").
- **Physical anomalies:** a book's spine title, a shelf gap, a volume shelved
upside-down or backwards, the aisle-number sign, a reading lamp on/off, the
step-stool moved.
- **`sense`:** the aisle feels longer; you forget which book you came for; a
wrong calm that you've already checked out.
- **Skin:** violet dusk through high windows (`--hue: ~275`). Books are an
unusually strong anomaly engine β€” probably the richest of these five.
### Vault 8 (`vault8`) β€” the bank vault, timelock running down
- **Hook:** your safe-deposit box is in aisle 8; the outer door is on a timelock
that reopens at dawn, so keep going.
- **Loop unit:** identical corridors of numbered brass boxes. **Go on** /
**Turn back**.
- **NPC:** a night guard on his rounds (`knows`: stops, watches, nearer) or a
customer at an open box who won't leave (`speaks`: "you were just here").
- **Physical anomalies:** a box number, a box door ajar, a dropped key on the
runner, the timelock clock's hands, the carpet runner's colour, a convex
security mirror's angle.
- **`sense`:** the vault feels deeper; you forget your box number; a calm that
you've already emptied it; wrong-way.
- **Skin:** crimson carpet + burnished brass (`--hue: ~350`).
### Data Hall 8 (`datahall8`) β€” the data-centre cold aisle at 3am
- **Hook:** the badge reader sent you to Hall 8 to reseat a server; the lift back
is locked to your card, so walk the aisle.
- **Loop unit:** identical rows of caged server racks. **Go on** / **Turn back**.
- **NPC:** a lone technician with a laptop cart far down the aisle (`knows`:
turns, watches, closer) β€” very modern loneliness.
- **Physical anomalies:** a rack's blinking-LED count, a cage door ajar, a coiled
cable on the floor, a KVM screen showing your own login, a warm aisle where it
should be cold, the hum's pitch.
- **`sense`:** the hall feels longer; you forget which rack; the hum stops (a
diegetic "wrong silence"); a wrong calm.
- **Skin:** indigo LED wash (`--hue: ~265`). The only contemporary/tech setting.
### Cloister 8 (`cloister8`) β€” the abbey at night
- **Hook:** locked in after the last visitors; complete the eighth circuit of the
cloister to reach the chapel door.
- **Loop unit:** identical vaulted arcades around a dark garth. **Go on** /
**Turn back**.
- **NPC:** a cowled figure with a taper at the far arcade (`knows`: turns, raises
a hand, nearer each loop, mouths "again").
- **Physical anomalies:** a wall sconce lit/dark, a carved boss or gargoyle, a
statue's pose out in the garth, a grave-slab inscription underfoot, the bell
(tolls once too many), a stained-glass panel.
- **`sense`:** the walk feels longer; you forget which office you came to pray; a
wrong serenity; wrong-way (the cloister circles anticlockwise now).
- **Skin:** candle-lit stone, very low saturation warm grey (`--hue: ~35`,
desaturated so it reads neutral, not Coach's amber).
### Midway 8 (`midway8`) β€” the shuttered funfair
- **Hook:** you stayed past closing; the only lit exit sign is down the midway,
past booth 8.
- **Loop unit:** identical stretches of game stalls and strung bulbs. **Go on** /
**Turn back**.
- **NPC:** a costumed mascot standing dead still by a stall. Twist (`knows`): it
has turned to face you; it's closer; its head is tilted. (Unsettling without
gore.)
- **Physical anomalies:** a prize wall, a strung bulb dark/lit, a carousel
horse's position, a ticket-booth number, the calliope music (loops backward),
a balloon snagged on a wire.
- **`sense`:** the midway feels longer; you forget which ride you were heading
to; a wrong festive calm; wrong-way.
- **Skin:** faded pink-and-gold neon (`--hue: ~320`).