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

138 lines
7.0 KiB
Markdown
Raw Permalink 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
**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 (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).