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

4.5 KiB
Raw Permalink Blame History

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.