# LANE D — Machines, Interaction & Quest **Read first:** `docs/DESIGN.md` (§5–§6), `docs/CONTRACTS.md`, everything in `src/core/`. **You own:** `src/machines/**`, `src/interact/**`, `src/demo/machinesDemo.ts`, `demo-machines.html`. **You may not touch anything else.** ## Mission Everything that moves or responds: the rotating platters + records, the tonearm, faders, buttons — all as `IMachine`s with `KinematicCollider`s — plus the interaction layer (raycast, break/place, hotbar model) and the "Bring Back the Mix" quest state machine. ## Provides - `createMachines(world: IVoxelWorld): MachineSet` where `MachineSet` has `machines: IMachine[]`, `getColliders(): KinematicCollider[]` (flattened, for Lane B), `update(dt)`, `getObject3Ds(): unknown[]`. - `Interaction` — constructor `(world, player: IPlayerView, machines, hotbar)`: raycast + break/place + use-key dispatch. - `Hotbar` — 9-slot inventory model (counts per BlockId, active slot). Pure model + events; Lane E renders it. - `Quest` — the signal-chain state machine. ## Consumes `IVoxelWorld`, `IPlayerView` (interfaces), `bus`, constants (`LAYOUT`, `PLATTER`, `PLAYER.reach`, `SIGNAL_NODES`). At integration you'll also read `QUEST_POS` from `src/worldgen/questPositions.ts` — code against a `QuestPositions` shape you define locally and have the demo supply mock coords; take real positions as a constructor arg, don't import Lane C. ## Tasks ### D1. Machine framework - `IMachine` base helper: owns a THREE.Group, colliders, interact zones. Machines are built from simple Three.js primitives (cylinders, boxes) with MeshStandardMaterial matching block tints from `blocks.ts` (visual consistency with the voxel world; grab tints via `blockDef`). ### D2. Platter machine (×2, the star) - Visual: layered cylinders at the spindle from `LAYOUT` — platter (`PLATTER.radius`, brushed dark alu, strobe-dot ring: small emissive red studs around the rim), slipmat, record (`recordRadius`, translucent blue on deck A / black on deck B, cream label with a procedurally-drawn canvas label texture — have fun: fake catalog number "TC-001 · TURNCRAFT"), chrome spindle pin. Fine groove rings via a canvas texture on the record top face. - Simulation: angular velocity → target RPM (`rpm33`/`rpm45` × pitch multiplier), exponential spin-up/brake with `PLATTER.spinUpSeconds` (the Technics lurch). Rotate the record+platter group. - Collider: one `cylinder` `KinematicCollider` (radius, halfHeight matching platter+record stack); `velocityAt` returns tangential velocity ω×r (zero y). When stopped, velocity zero. - Emits `platter:state` on any play/speed change. ### D3. Tonearm machine (×2) - Visual: chrome tube from the pedestal (LAYOUT-derived) arcing over the record, counterweight, headshell. Deck A's headshell has an EMPTY stylus socket until the quest fixes it (then a chrome stylus appears). - Behavior: rest position off-disc; when deck playing *and* stylus repaired, swings to outer groove then tracks slowly inward (one full traversal ≈ 4 min, then auto-return). The whole arm is a slow kinematic `aabb` collider chain (2–3 segments is enough); the headshell is the "needle plow" — its collider pushes the player along. - Interact on headshell with stylus block selected in hotbar → consumes it, repairs `stylus` node. ### D4. Fader & button machines - **Pitch fader** (per deck): sled (fader_cap visual) sliding in Lane C's canyon slot; player pushes it by walking into it (kinematic aabb that yields — clamp to slot, 11 detents); position → pitch multiplier 0.5–1.5, emits `fader:move`. - **Channel faders ×4 + crossfader**: same sled mechanic on the mixer face. Crossfader starts blocked by Lane C's dust plug — the machine checks the slot voxels are clear before it can move; when the player mines the dust (normal block breaking), first successful full slide repairs `crossfader`. - **Buttons**: start/stop plate (E or walk-press: interact toggles deck), 33/45 rockers, cue lamp, **master power switch** (rear of mixer; inert until other 4 nodes repaired — then glows via a swapped emissive material and one press triggers the finale). All emit `machine:interact`. ### D5. Interaction layer - **Raycast**: voxel DDA from eye along lookDir up to `PLAYER.reach` (Amanatides & Woo), tested against machine colliders analytically (ray-cylinder, ray-aabb); nearest hit wins → `RayHit`. - **Break** (LMB hold): breakable check via `blockDef(id).breakable`, break time ~0.4 s with a crack overlay hook (emit progress on bus? keep simple: Lane E just needs `block:break`), adds block to hotbar, `world.setBlock(AIR)`, emit `block:break`. - **Place** (RMB): active hotbar block onto hit face, blocked if it overlaps the player AABB or a machine collider, emit `block:place`. - **Use** (E): machine hit → `onInteract()`; block hit → quest checks (fuse socket, RCA plug, patch-bay socket). - Selection wireframe box on the aimed block (thin line box, Lane A-style visuals not required — a THREE.LineSegments you own). ### D6. Quest state machine - Tracks `SIGNAL_NODES` repair state; constructor takes quest positions. - Node fix logic: `stylus` (D3), `crossfader` (D4), `rca` — the loose gold plug is a small machine; interacting shoves it 1 voxel toward the socket (3 shoves, clunk-clunk-click) then repairs; `fuse` — interact fuse box with fresh-fuse block in hotbar (picked up by breaking the parts-bin fuse) after breaking the blown one; `power` — D4's switch. - Emits `signal:repair` with running count; when count == total, `game:win`, and: both platters auto-start, 45-mode unlocked. Quest state queryable (`isRepaired(node)`) for Lane E. ## Demo (`demo-machines.html`) A mock flat-world stage (simple `IVoxelWorld` mock + naive box renderer or just a ground plane — your call) with: both platters spinning (buttons for 33/45/stop), tonearm tracking a spinning record, all faders operable via click-drag AND a scripted "player AABB" you can drive with arrow keys to shove sleds and press plates (no Lane B import — a 20-line mock body), quest panel showing node states with buttons to simulate each repair path, raycast tester: click to break/place blocks on a small mock wall. Console logs every bus event. ## Acceptance - [ ] Platters: correct spin-up/brake feel; `velocityAt` returns ω×r tangential values (unit-test in demo: log at r=10 & r=41 vs expected) - [ ] Tonearm tracks inward over minutes; headshell collider moves with it - [ ] All faders/buttons operable; crossfader gated on clear slot voxels - [ ] DDA raycast: no misses/tunneling at reach 5; machine hits beat farther blocks - [ ] Full quest completable in demo via the real logic (mock positions) - [ ] `game:win` fires exactly once; platters auto-start on win - [ ] `npm run typecheck` clean; `HANDOFF.md` written ## Out of scope Player physics (B — you only consume `IPlayerView` + provide colliders), voxel meshing (A), booth geometry (C), all audio/UI rendering (E — you emit events; hotbar is a model only).