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

117 lines
7.6 KiB
Markdown
Raw 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 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`](../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-end** — `game: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).