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>
7.0 KiB
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 IMachines with KinematicColliders —
plus the interaction layer (raycast, break/place, hotbar model) and the
"Bring Back the Mix" quest state machine.
Provides
createMachines(world: IVoxelWorld): MachineSetwhereMachineSethasmachines: 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
IMachinebase helper: owns a THREE.Group, colliders, interact zones. Machines are built from simple Three.js primitives (cylinders, boxes) with MeshStandardMaterial matching block tints fromblocks.ts(visual consistency with the voxel world; grab tints viablockDef).
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 withPLATTER.spinUpSeconds(the Technics lurch). Rotate the record+platter group. - Collider: one
cylinderKinematicCollider(radius, halfHeight matching platter+record stack);velocityAtreturns tangential velocity ω×r (zero y). When stopped, velocity zero. - Emits
platter:stateon 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
aabbcollider 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
stylusnode.
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 needsblock:break), adds block to hotbar,world.setBlock(AIR), emitblock: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_NODESrepair 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:repairwith 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;
velocityAtreturns ω×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:winfires exactly once; platters auto-start on winnpm run typecheckclean;HANDOFF.mdwritten
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).