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>
5.3 KiB
5.3 KiB
LANE D — Machines, Interaction & Quest — HANDOFF
Owns src/machines/**, src/interact/**, src/demo/machinesDemo.ts,
demo-machines.html. npm run typecheck is clean. Demo runs with zero code
from other lanes.
Public API (the 10-line surface)
// src/machines
createMachines(world: IVoxelWorld, questPos?: Partial<QuestPositions>): MachineSet
// MachineSet = { machines, quest, tonearmA, getColliders(), update(dt),
// getObject3Ds(), attachPlayer(player) }
Quest // .isRepaired(node) .canPowerOn() .repair(node) .count() .isWon() .pos
QUEST_ITEMS // { stylus: 11 (chrome), fuse: 22 (glass) } — blocks Lane C parks in the parts bin
QuestPositions, NODES, Platter, Tonearm, Fader, Button
// src/interact
new Interaction(world, player: IPlayerView, machines: MachineSet, hotbar: Hotbar)
// .startMining()/.stopMining() .place() .use() .update(dt) .getObject3D() (selection box)
new Hotbar() // .slots .active .setActive(i) .activeBlock() .add(id,n) .consumeActive(n) .onChange(fn)
raycastVoxel(...) / raycastColliders(...) // DDA + analytic, exported for reuse
Done (all acceptance criteria verified — see LANE_D_update.md)
- Platters ×2: rotating cylinder collider,
velocityAt = ω×r(tangential, zero-y, zero when stopped). Exponential spin-up/brake. Runtime-measured 33→8.52 v/s @ rim / 2.08 v/s @ r=10, 45→11.57 v/s. Blue/black records with a procedural groove+label canvas texture ("TC-001 · TURNCRAFT"), strobe-dot rim, chrome spindle. Emitsplatter:stateon play/speed/pitch change. - Tonearms ×2: rigid arm pivoting about a rear pedestal; cue → inward track
(~4 min) → auto-return. Headshell is a moving kinematic aabb ("needle plow")
with real finite-difference velocity. Deck A starts with an empty stylus
socket;
setStylus(true)reveals the needle. Deck B starts fitted. - Faders: pitch (retunes its deck), 4 channel faders, crossfader. Pushed by
the player walking into the sled (needs
attachPlayer), 11 detents, orsetValue()for a UI. Crossfader is gated on its slot voxels being clear and repairscrossfaderon the first full slide once clear. - Buttons: start/stop (E or walk-press), 33/45 rockers, cue lamp (glow +
event), master power (inert/dark until
canPowerOn(), then glows; press = finale). - Interaction: eye-ray DDA voxels (Amanatides & Woo, verified no-tunnel) +
analytic ray↔cylinder/aabb, nearest wins. Break (0.4 s hold → hotbar +
block:break), place (blocked by player/collider/occupied +block:place), use (routes stylus & fuse via hotbar+quest, elsemachine.onInteract()). Selection wireframe. - Quest: pure state machine over
SIGNAL_NODES;signal:repairper fix,game:winexactly once on the fifth, both platters auto-start on win.
Contract friction / deviations (for the integrator)
createMachinestakes a 2nd argquestPos?: Partial<QuestPositions>. The brief's Provides line showscreateMachines(world); D6 + INTEGRATION both require quest positions, so I made it optional —createMachines(world)works (LAYOUT-derived defaults viadefaultQuestPositions()), and integration passes realQUEST_POS.QuestPositionsis defined insrc/machines/quest.ts— Lane C'ssrc/worldgen/questPositions.tsshould export aQUEST_POSof that shape (I do not import Lane C).MachineSet.attachPlayer(player)is an addition beyond the brief. Faders (walk-to-push) and walk-press buttons need the player view, which doesn't exist atcreateMachinestime. Integrator: callmachines.attachPlayer(player)right after constructing the Lane B player (step 6.5). Without it, faders still work viasetValueand buttons via E; only walk-interaction is inert.- Quest item blocks (
QUEST_ITEMS): the stylus pickup ischrome(11) and the fuse pickup isglass(22). Lane C should place these as the parts-bin pickups. Stylus fit =useon Deck A's headshell holding chrome; fuse = break the blown fuse atpos.fuseBox, thenusenearby holding glass. - Hotbar change events use
hotbar.onChange(fn)(not the core bus — the bus has no hotbar events by contract). Lane E's HUD subscribes to that. - Emissive pulse hook: I don't touch Lane A materials. The master power /
cue lamps swap their own emissive material; Lane E's global emissive boost
(
setEmissiveBoost) is orthogonal. - 45-mode: kept available at all times (INTEGRATION smoke-test 2 rim-launches
at 45 pre-win). DESIGN §6's "45 unlocked at win" is treated as flavor. Flag
Platter.unlocked45exists if you want to gate it.
Stubbed / out of scope (by contract)
- No player physics, no voxel meshing, no booth geometry, no audio/UI rendering. I emit events; Lane E renders hotbar/lights/sound from them.
- The demo's
MockWorld, naive block viz, andMockPlayerare// DEMO MOCKand never used in integration. - Demo exposes
window.TURNCRAFT_D(debug handle:machines/quest/step(n)); harmless, demo-only.
Verify
npm run dev → open /demo-machines.html. Header comment in
src/demo/machinesDemo.ts lists exactly what to press for each criterion.
Note: in a hidden/background tab browsers pause requestAnimationFrame, so
the sim appears frozen — use TURNCRAFT_D.step(n) from the console to advance,
or just focus the tab.