File size: 18,239 Bytes
161d0a9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93a3c54
 
 
 
 
 
 
 
 
 
 
 
161d0a9
 
 
 
 
 
f0726aa
 
 
 
 
 
 
 
 
 
 
 
 
58e1249
 
f0726aa
 
 
58e1249
 
 
 
 
 
 
 
 
 
f0726aa
d67855c
 
 
fc6ce84
 
 
d67855c
 
fc6ce84
 
 
 
d67855c
 
fc6ce84
 
 
 
 
 
d67855c
 
 
 
 
 
 
 
f0726aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93a3c54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f0726aa
 
161d0a9
 
93a3c54
 
 
 
161d0a9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93a3c54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# 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`).