#ui had been an empty div since round 0 — E never ran. It has an owner now.
HUD (charter #1): coat/hull, heat, torpedo pips, speed/flow/throttle, boost,
score, combo, biome + segment name, and a progress rail with C's 10 checkpoints
ticked. Bus-only — zero imports from B, exactly as the charter asks. player:state
and combat:state arrive every frame and ARE the clock, so the HUD cannot tick
while the game isn't running.
60fps law: DOM is built once; the hot path is transform + textContent-on-change
only. Bars scale (scaleX), they never resize — a width:% write per bar per frame
is a layout pass. Torpedo pips rebuild only when ammoMax changes, not when ammo
does.
COMMS — on nobody's list. Argued for, then built. The GDD says "Star Fox arcade
flow" and there was nobody in the game but the player. Three-hander: voss
(attending, terse), park (resident, over-excited), adeyemi (immunology,
apologetic — and amber, the colour of the cells attacking you; his fault).
Deterministic round-robin per trigger key, no RNG. Priority ladder + per-key
cooldowns so the crew shuts up during a firefight.
It turned out to be more than flavour: it is the tutorialisation channel the GDD
asks for and L1 has no other vehicle for. The hazard lines teach what C's HAZARDS
registry note says the hazard IS ("ring gate. it opens on a beat — match the
beat, do not ram it"). Keys are the real HAZARDS ids, checked against
levels/enemies.js rather than guessed — all three L2 kinds get bespoke lines,
zero generic fallbacks. A level's worth of dialogue is now a data edit in LINES.
Three defects, all mine, all found by the stepped sim, none visible to the eye:
- The progress rail was dead. I read `level.length` — a field that does not
exist. The level carries segments, not a total. It now uses the runtime
world.length that B already divides into p.progress.
- My clock was fiction. Comms counted `+= 0.1` per timer tick and called it
seconds; a backgrounded tab throttles setTimeout to ~1s, so lines held ~10x too
long. It now reads performance.now().
- Every hazard warning had the LOWEST priority in the game. PRI has a key `warn`,
but say() is called with `warn_reflux_surge`, so PRI[key] was undefined and
fell through to the default of 1 — the surge screaming "GO. GO." could be
talked over by park admiring a specimen. Keys now resolve exact-then-family.
The evidence shot IS the fix: same seed, same frame, the warning now sits where
the checkpoint line used to be.
### -> Lane B — player:state stops emitting the moment you die
player.js:103 early-returns before the emit when !st.alive. So `alive` is
unobservable (E can only ever read true — the field is currently decorative), and
E cannot use player:state as a clock, because it stops exactly when the death UI
needs to run and through boot's whole 2.0s respawn window. Comms works around it
with its own wall-clock. The death/respawn feed-drop (charter #4) will not be
able to. Ask is in NOTES; not patching your file.
### -> Lane F — two contract items
I edited boot.js (~10 lines mirroring your own dynamic-import/try-catch/
console.info fallback) because #ui needed an owner and two modules nothing
constructs aren't worth much. Ratify or move it. Second: shot_sink.py cannot
photograph Lane E — it POSTs renderer.domElement.toDataURL(), and the HUD is a
DOM overlay, so the house evidence tool renders it invisible. This shot is a
composite (canvas -> 2D canvas, then #ui via an SVG foreignObject). Offer to lift
it into pipeline/ as DBG.shotUI() stands.
qa GREEN. Evidence: docs/shots/laneE/round2_hud_aortic.webp (s=1111, seed 7, live
hazard warn + x6 chain). Dev server guts-e on 8146.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All round-2 lane sessions were cut off 15:08-15:16 before NOTES/commits; the three prior
commits preserve their work verbatim. This commit is the wiring B was cut off inside of,
plus the run-state boot owes per ruling #4:
- combat/index.js: fiction-id -> archetype resolve via C's getEnemy() (the round's declared
ONE blocker), theta-array + spread fan-out for group spawns, hazards + pickups constructed
and updated (order: enemies -> weapons -> hazards -> pickups, so a torpedo can neutralize
a surge the frame it detonates), pickup score/samples onto the combat:state scoreboard,
combat.reset(fromS) for respawn.
- flight/player.js: kill/shove/refill — the API B's new modules call; kill ignores iframes
by design (boost-dodging through C's lethal acid wall is not a read). Deleted the
crestSpeed fallback guard per its own comment — A's law landed.
- boot.js: run state. checkpoint crossed -> remembered; death -> 2s -> respawn at it +
combat.reset() re-arms hazard timelines (pump deliberately does NOT rewind: no
double-spawns, collected stays collected); gate -> level:complete {stats, par} for E's
medal card. Exported step(dt) so the stepped-sim harness drives the REAL loop.
- ROUND2_INSTRUCTIONS: MID-ROUND STATUS box for the resuming lanes; f-progress entry.
Verified (deterministic stepped sim via boot.step, :8140): full L2 run fires all 47 events
with ZERO console warns (was: 11 spawn no-ops), all 5 hazard telegraphs, 2400 proximity
ticks, 14 pickups collected, finish 157s vs par 180 while surfing 94s of it. Death path:
throttle 0.7 into the finale -> killed by acid s=3386 -> respawn at Cardia Approach 2980 ->
surge re-arms -> second attempt escapes -> level:complete {deaths: 1}. Survivable AND
losable. Evidence: docs/shots/laneF/round2_L2_integrated_full_run.png. qa.sh GREEN.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wired B's player/combat + event pump, D's assets, C's level pick into boot.
Fixed qa ESM gate (node --check no-op, found by B) + added spline selfcheck.
TECH: world contract FROZEN v1.1, bus events ratified, colorspace + crest-speed
laws (CREST_FACTOR 1.6 — B proved surfing lost to throttle at 1.0). Stub complies.
Verified integrated build via 60s stepped sim: 9 draws, 0 errors, 0 asset misses;
one known gap (fiction-id spawn resolve) confirmed and assigned as B's top item.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>