guts/docs/progress/c-progress.md
jing 1b5ec767cd [lane C] Round 1: schema v1, levels registry + selfcheck + pacing sim, L2 vertical slice
Data spine:
- Schema v1 ratified (TECH.md, C-owned). Additive over v0: schema/par/next, segment
  name, cross-section placement (theta/rho as a FRACTION of safe radius), spread, span,
  pickup+gate event types, hazard params. STUB_LEVEL synced (that const only, per
  ROUND1 §Lane C.1) — world.hash() unchanged (1bdf001), geometry provably untouched.
- levels/index.js: registry + loader (node+browser, no THREE), real schema selfcheck
  (qa's --selfcheck hook is now live) and a pacing simulator (--sim). Consumes the other
  lanes' contracts live rather than restating them: A's biomes.js for ids and
  wave.amp+breathe, A's SKIN, B's tuning.js hull radius, B's balance.js for scores.
- levels/enemies.js (fiction id -> archetype join table), levels/difficulty.js.

L2_esophagus — the vertical slice, complete: 3600 units, 7 beats, 47 events, 65 enemies,
10 checkpoints, 3 samples, reflux-surge finale. L1/L3 skeletons (shape only).
Anatomy is the level designer: the three real esophageal constrictions are the three
setpieces, and all three enemies are real esophageal pathology (bolus chunk, eosinophilic
esophagitis, candida esophagitis).

Measured (node web/js/levels/index.js --selfcheck|--sim; qa GREEN):
- selfcheck 3/3 valid, exit 0.
- pressure curve 0.27 -> 2.14 -> 4.35 -> 0.69 -> 4.53 -> 6.35 -> 4.40 (breather/pressure/
  spike/breather/pressure/peak/finale).
- par 3:30 vs measured 2:47 speedrun / 3:19 par-pace / 4:22 timid; par.score 9000 of a
  9400 kill budget (95%), computed from B's scores.
- all 10 checkpoint gaps within the <=30s death-cost law (max 28.8s); min clearance 2.73.

Two contract collisions found and resolved (LANE_C_NOTES):
- Biome ids: authored mouth/pharynx/cardia/pylorus; A's registry has six canonical ids and
  rejected all four. A is right — biome = look, segments[].name = anatomy. Conformed.
- Balance: my first enemies.js restated hp/size/speed/score that B owns in balance.js.
  Cut to a join table; index.js reads B's scores via scoreFor().

Caught live: A raised esophagus wave.amp 0.9 -> 1.4 mid-session, making the diaphragmatic
hiatus unflyable (2.33 vs the 2.5 floor). Widened 6.5 -> 6.8. The invariant also failed my
own aortic constriction's first draft (radius 7, symmetric squeeze) -> radius 9, directional.

No in-engine flythrough: ?stub=1&lvl= does not compose (snippet offered to F) and the stub
is single-segment, so it could only render beat 1. The pacing evidence is a kinematic model,
not a playtest — it proves L2 is flyable, paced and survivable, not that it feels good.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 08:53:34 +10:00

63 lines
4.5 KiB
Markdown
Raw Permalink 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 C (Levels & Encounters) — progress
## 2026-07-16 — Round 1: the data spine + the vertical slice
Landed:
- **Schema v1 ratified** (TECH.md §Level data schema, C-owned from here). Additive over F's
v0: `schema`, `par`, `next`, segment `name`, cross-section placement (`theta`/`rho`, rho as
a *fraction* of the safe radius), group `spread`, zone `span`, `pickup`/`gate` event types,
hazard params. STUB_LEVEL synced to v1 (only that const in F's file, per ROUND1 §Lane C.1).
- **`levels/index.js`** — registry + loader (node + browser, no THREE), **schema selfcheck**
(real; qa.sh's `--selfcheck` hook is now live) and a **pacing simulator** (`--sim`).
The selfcheck consumes the other lanes' contracts live: A's `world/biomes.js` for biome ids
and `wave.amp + wave.breathe`, A's `SKIN`, B's `flight/tuning.js` hull radius, B's
`combat/balance.js` for scores. C's registry owns composition and mirrors nothing.
- **`levels/enemies.js`** — enemy catalogue (fiction id → archetype) + pickup/hazard vocab.
- **`levels/difficulty.js`** — the single difficulty table + `applyDifficulty()` (authored,
not yet validated against play).
- **`L2_esophagus.json`** — the vertical slice, complete: 3600 units, 7 beats, 47 events,
65 enemies, 10 checkpoints, 3 samples, reflux-surge finale. `L1_mouth` / `L3_stomach`
skeletons (shape only, flagged SKELETON — the selfcheck downgrades content laws for them).
- **GDD refined in place** (bannered): per-level time targets replacing a flat "610 min",
esophagus roster, L2's three real constrictions as its setpieces.
- **Boss specs** (Guardian / Tapeworm / Blockage) as one-pagers in NOTES — phases,
telegraphs, windows, failure pressure. Deliberately three *different* pressures: coat race,
distance race, hull attrition + doom timer.
Verification (measured, `node web/js/levels/index.js --selfcheck|--sim`; qa.sh GREEN):
- selfcheck **3/3 valid, exit 0**; 6 warnings, all expected (skeletons undesigned, L4 not yet
registered).
- L2 pressure curve **0.27 → 2.14 → 4.35 → 0.69 → 4.53 → 6.35 → 4.40** (breather → pressure →
spike → breather → pressure → peak → finale).
- L2 par 3:30 against a measured **2:47 speedrun / 3:19 par-pace / 4:22 timid** run;
par.score 9000 of a 9400 kill budget (95%), computed from B's scores.
- All 10 checkpoint gaps within the ≤30 s death-cost law (max 28.8 s). Min clearance 2.73 at
the hiatus, against C's 2.5 floor.
- Booted `?stub=1&dbg=1` after the STUB_LEVEL edit: tube renders, zero console errors, and
the v1 module builds a valid world (`length 400`, `wallRho(0,0) = 8.5`, centre-line collide
`null`). **`world.hash()` is byte-identical before and after my edit (`1bdf001`)** — the
sync adds data fields only and provably does not move the geometry, so F's round-0 shot
still stands. No screenshot committed: it would be pixel-identical to round 0's, and per
charter a data lane's evidence is the pacing report, not a picture of an unchanged tube.
(No fps claim: the preview pane throttles rAF in the background — `rafTicks: 0` — so
framerate is unmeasurable here. Not a code fault; the render path is fine.)
Two contract collisions found and resolved (detail in NOTES):
- **Biome ids** — I had authored `mouth`/`pharynx`/`cardia`/`pylorus`; A's registry has six
canonical ids and rejected all four. A is right: biome = look, `segments[].name` = anatomy.
Conformed, and the constraint paid for itself (the cardia keeping the esophagus palette is
why the acid sea reveals itself in colour).
- **Balance ownership** — my first `enemies.js` restated hp/size/speed/score that B already
owns in `combat/balance.js`. Cut to a join table; `index.js` reads B's scores instead.
Caught live: A raised esophagus `wave.amp` 0.9 → 1.4 mid-session, which made the
diaphragmatic hiatus unflyable (2.33 free radius vs the 2.5 floor). Widened 6.5 → 6.8. The
invariant did its job on my own level twice — the aortic constriction also failed its first
draft (radius 7, symmetric squeeze) and became radius 9 with a *directional* squeeze.
Not done / next round: no in-engine flythrough — `?stub=1&lvl=` does not compose yet (snippet
offered to F) and the stub is single-segment, so it could only ever render beat 1. L2's
pacing evidence is a kinematic **model**, not a playtest: it proves the level is flyable,
paced and survivable; it cannot prove the hiatus *feels* good. That needs A's world + B's
controller in round 2 — and if one thing gets playtested, it should be s 26003000.