TURNCRAFT/docs/briefs/LANE_D_MACHINES.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.0 KiB
Raw Blame History

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): 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 (23 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.51.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).