TURNCRAFT/docs/LANE_D_update.md
jing 5a39e3a947 TURNCRAFT: contracts, docs, and all five lane deliverables (pre-integration)
Lanes A (engine), B (player), C (worldgen), D (machines/quest),
E (audio/fx/ui) as landed, each with HANDOFF.md + update docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 20:50:56 +10:00

7.6 KiB
Raw Blame History

LANE D — Machines, Interaction & Quest — Progress Update

Status: complete. All brief tasks (D1D6) implemented, npm run typecheck clean, every acceptance criterion verified at runtime. Adversarially reviewed: 6 confirmed defects found (incl. one quest deadlock) — all fixed and re-verified. Ready for integration.

For Fable / the integrator. Companion doc: src/machines/HANDOFF.md (API surface + contract friction).


What I built

Everything that moves or responds in the booth, plus the interaction layer and the quest.

Area Files Delivers
Framework (D1) machines/machine.ts, machines/util.ts MachineBase, block-tint materials, procedural record texture
Platters ×2 (D2) machines/platter.ts rotating ride cylinder, velocityAt = ω×r, spin-up/brake, platter:state
Tonearms ×2 (D3) machines/tonearm.ts pivoting arm, cue→inward-track→return, needle-plow headshell collider, stylus socket
Faders + buttons (D4) machines/faders.ts, machines/buttons.ts, machines/rca.ts pitch/channel/crossfader sleds, start-stop/33-45/cue/power, RCA plug
Quest (D6) machines/quest.ts SIGNAL_NODES state machine, signal:repair / game:win
Assembly machines/index.ts createMachines(world, questPos?) → MachineSet (wires everything)
Interaction (D5) interact/raycast.ts, interact/interaction.ts, interact/hotbar.ts DDA+analytic raycast, break/place/use, 9-slot hotbar
Demo demo-machines.html, demo/machinesDemo.ts first-person free-fly harness, quest panel, bus logging

Public surface (for integration wiring): createMachines(world, questPos?) returns { machines, quest, tonearmA, getColliders(), update(dt), getObject3Ds(), attachPlayer(player) }; new Interaction(world, player, machines, hotbar); new Hotbar(). Full details in HANDOFF.md.

Acceptance criteria — verified at runtime (not just typecheck)

Drove the real machine/quest/interaction code (browser + a headless raycast test):

  • Platter velocityAt = ω×r — measured @33: r=10 → 2.08 v/s, r=41 → 8.52 v/s; @45: r=41 → 11.57 v/s; v·r == 0 (perfectly tangential); 0 when stopped. Matches the DESIGN targets (label ~2, rim ~8.6/11.6) exactly. Exponential spin-up/brake confirmed.
  • Tonearm tracks inward — headshell radius-from-spindle 42 (rest) → 35 (cue) → 24.6 (after 80 s tracking); headshell collider carries real velocity during the cue (the plow pushes).
  • Faders operable; crossfader gated — crossfader refuses to move while its slot holds dust; after mining the plug, one full slide repairs crossfader.
  • DDA raycast, no tunneling — 8/8 headless assertions: correct face/distance, maxDist respected, a shallow diagonal does not tunnel a wall, ray↔cylinder and ray↔aabb hit, and a nearer machine collider beats a farther block.
  • Quest end-to-endgame:win fires exactly once, both platters auto-start on win, power is refused until the other four are done, re-repairing a node is a no-op (exactly 4 repair events for 4 fixes).
  • No console errors across the session; npm run typecheck and vite build clean.

Adversarial review (multi-agent, this session)

Ran a 5-dimension review (platter physics · raycast/DDA · quest/interaction · faders/tonearm/buttons · contract compliance), each finding independently verified by a skeptic. It found 6 confirmed defects — all now fixed and re-verified through the real code paths, plus 1 refuted nit. This caught a bug my own runtime pass missed because I'd cleared the crossfader dust with a direct setBlock instead of mining it.

# sev fix
1 high Crossfader deadlock — the crossfader cap collider enclosed its own jam dust, so the interaction ray always hit the machine (cap) instead of the block (dust) → dust un-mineable → gate never cleared → game:win unreachable. Fixed by excluding Fader caps from the interaction ray (faders are push-operated, never aimed at). Re-verified: dust now mines via the real ray+hold path → crossfader repairs.
2 high Tonearm phantom collider — the arm was one AABB bounding pivot→head, ballooning into a ~700-voxel² box over the rideable record when diagonal. Replaced with a 3-segment AABB chain tiling the tube (~126 voxel²; a far ride point is now outside all arm colliders).
3 med Mixer channel-fader / crossfader coords were hardcoded — now derived from LAYOUT.mixer and pos.crossfaderSlot (keeps gate + geometry coincident).
4 low Platter.velocityAt returned a fresh array each call (physics hot path) — now writes a reused per-collider scratch (documented "consume immediately").
5 low Tonearm allocated a THREE.Vector3 per tick — hoisted to a module scratch.
6 low raycast allocated short-lived arrays per collider — rayAabb/rayCylinder unrolled.
refuted A stopped deck could re-emit a byte-identical platter:state; verified harmless (brief says "emit on play/speed change"), but I deduped it anyway.

Re-verified after the fixes: velocity exact (2.094 / 8.587, tangential=0), game:win once, both platters auto-start, crossfader repairs via real mining, tonearm still tracks inward, raycast 8/8, no console errors, typecheck clean.

What the integrator needs to know (3 friction points)

  1. createMachines takes an optional 2nd arg questPos: Partial<QuestPositions>. Pass Lane C's QUEST_POS; omit it and LAYOUT-derived defaults are used. QuestPositions is defined in machines/quest.ts — Lane C's worldgen/questPositions.ts should export that shape (I never import Lane C).
  2. Call machines.attachPlayer(player) right after building the Lane B player (a new step ~6.5 in INTEGRATION). Faders (walk-to-push) and walk-press buttons need the player view, which doesn't exist at createMachines time.
  3. Quest item blocks (QUEST_ITEMS): stylus pickup = chrome (11), fuse pickup = glass (22). Lane C should place these in the PCB-Depths parts bin.

No contract files were edited; no other lane's concrete code is imported; all positions derive from LAYOUT/PLATTER. One small addition to the event surface was avoided — hotbar changes use hotbar.onChange(fn) (the core bus has no hotbar events by contract); Lane E's HUD subscribes to that.

Suggested next steps (for Fable to assign)

  • Integration: wire src/main.ts per INTEGRATION.md; add the attachPlayer step; confirm Lane C's QUEST_POS matches QuestPositions.
  • Cross-lane check with C: my default quest/gear positions come from LAYOUT; once C's booth lands, verify the crossfader slot span, fuse-box voxel, RCA plug/socket, and headshell socket line up with C's actual geometry.
  • Cross-lane check with E: confirm Lane E listens to platter:state, signal:repair, game:win, machine:interact, fader:move, and renders the Hotbar via onChange.
  • Optional polish (not blocking): per-deck tonearm side-swing tuning once C's plinths exist; a unlocked45 gate if DESIGN's "45 unlocked at win" is taken literally (I kept 45 always-on for the INTEGRATION rim-launch smoke test).

Notes / repo state

  • Local tree is not a git repo (git status → not a repository). I did not init or push to ssh://git@100.71.119.27:222/monster/TURNCRAFT.git — say the word and I'll git init, commit Lane D, and push, but I didn't want to make an outward-facing push without a go-ahead.
  • Demo dev server was on localhost:5180 during verification (now can be stopped).