- Workshop (WORKSHOP_CARTRIDGE): five-stage cartridge assembly at Deck A — seat/square, crimp four tag-wires, torque screws, ride-the-arm counterweight balance, needle-drop diagnostic with per-fault audio + scope. Relay-synced (per-field co-op merge: held screw + carried wire survive remote state). - Glow-Up (G1-G5): 32px atlas with per-voxel variants, selective LED bloom (quality-gated), screen-print decal system + party flyers, mixer/PCB worldgen density pass, record groove-sheen side texture. - 10 confirmed multi-agent review fixes, incl. co-op screw-stomp soft-lock, ride-snap collider-identity (magnet feet / eaten record-fling), workshop SFX exact-match map (rca_seated hijack), beam ride colliders to the head, double-crimp guard, WIRING INCOMPLETE diagnosis mode, skate-abort timer, completedState wiring, per-tick material churn, trackingHeavy platter drag. - Crossfader playtest fix: slew-limited sled (3.4 v/s) + ribbed grip caps with amber index — the sled reads as a heavy handle, not a teleporting wall. - Demo harnesses: machinesDemo hold-key wiring, playerDemo seesaw phase continuity, audioDemo incomplete fault button. - Workshop sync: exact 1.0 screw endpoint gets its own emit signature. Verified: typecheck + vite build clean, live solo quest smoke, two-client co-op relay smoke (simultaneous torque, no rewind, exact convergence). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
11 KiB
Lane B — Player & Physics — Update for Review
Round 2.1 (2026-07-14, resumed session) — closed the real-geometry loop. Round 2 verified ride-snap against a faithful mock seesaw (the account ran out of tokens before the real
Workshop.tsarm existed). That arm now exists, and it's wired intomachines.getColliders()→ the player, so I verified ride-snap against the actual geometry: a headless harness drives the realws_ride*segments (beamY/thetamath,RIDE_SEGMENTS=6, 0.6-tall slabs) throughPlayerController. Result — the realistic balance ritual (weight swept 0↔10) rides 99.6 % grounded,maxZoff0.000 (no slingshot),maxYerr0.023, every ungrounded tick re-caught in 1 tick; even an impossible instant-slam only floats you upward, never falls or flings. Regressions re-confirmed (disc circle 20.00–20.11, horizontal platform 99.7 %, flat-floor false-stick fix holds →groundedOn=null). Whole-projecttsc --noEmitclean. No code changes were needed — the Round-2 ride-snap already handles the real arm with ~25× margin; this round is the missing cross-lane verification + doc update (INTEGRATION_NOTES physics section now cites the real-arm numbers).
Round 2 (2026-07-14) — Physics groundwork for the Headshell Workshop. New briefs landed (
WORKSHOP_CARTRIDGE.md,GLOWUP_ART.md). Glow-Up is a Lane A/E art pass — not mine. The Workshop leans on Lane B for one thing: Stage 4 has you standing on the tonearm as it seesaws under your feet — the brief says "Lane B's … resolution handles the ride — verify no slingshot on tilt." That's riding a vertically-moving platform, exactly the limitation I flagged last round. I did that slice.What I found (tested, didn't assume): I built a faithful seesaw mock (a tilting aabb-segment chain like
tonearm.tswill use) and drove it headlessly. The old physics failed — a gentle tilt left the player grounded only ~52 % of the time (bouncing up to 0.7 above the arm); a fast tilt flung them off onto the floor. Root cause: vertical motion was left to gravity chasing the surface, and a tilting arm outruns gravity.What I fixed: added ride-snap in
PlayerController.resolveColliders— a rising arm grounds by penetration; a descending/tilting arm snaps the player back onto its top across a ≤RIDE_SNAP(0.75 v) per-tick gap, gated so it never fights a jump. (I first tried a velocity-based vertical carry — it overshot, because the arm's collider is static within a tick while the player substeps. Ride-snap avoids that entirely.)Verified live: seesaw ride now glued at the realistic tonearm pace (grounded 100 %, feet on the slab) and holds glued to ~2 rad/s / ~24 v/s surface speed — ~8× past anything a counterweight can drive; beyond that it slides off gracefully (no explosion). All prior mechanics re-verified intact (disc circle 20.01, fling 10.2, platform, auto-step, no wall tunneling). No lateral slingshot at any rate.
Self-review caught 2 more: an adversarial pass on the ride-snap diff found a false-stick (a platform flush with the voxel floor kept carrying you after you stepped off onto solid ground) and a fly-mode lifecycle gap — both fixed (gate ride-snap on not already on voxel ground + clear ride state on fly) and re-verified: flush-floor now reads
groundedOn=null, riding over a real gap still readsplatform.Files this round:
src/player/PlayerController.ts(ride-snap; +rideColliderstate),src/demo/playerDemo.ts(seesaw mock + Seesaw/Teleport-to-arm controls),docs/INTEGRATION_NOTES.md(new "Physics — riding tilting … platforms" section — the one the Workshop brief tells its dev to read),src/player/HANDOFF.md,.claude/launch.json(added aturncraft-laneBport so my demo server doesn't collide with the parallel session's). Stayed in my lane — did NOT touchtonearm.ts/interaction.ts/audio (those are the Workshop session's edits).Heads-up for Fable:
src/audio/testSignal.ts(the Workshop session's WIP Stage-5 file, untracked) currently failstsc— not mine, flagging it so a repo typecheck isn't mistaken for a Lane B regression. My files are clean.Next: ride-snap is done and documented; the Workshop dev just moves the arm colliders each tick and Lane B carries the player.
RIDE_SNAPis a one-liner if a wilder tilt ever needs more envelope. Round 1 (the base controller) follows.
Lane B — Player & Physics — Update for Review
Author: Lane B · Status: ✅ complete, verified live, self-reviewed
Scope touched: src/player/**, src/demo/playerDemo.ts, demo-player.html,
.claude/launch.json (dev-server config for the demo). No writes to src/core/,
src/main.ts, or any other lane's directory.
TL;DR
First-person controller with Minecraft feel and the signature record-riding
mechanic is done. npm run typecheck is clean, the standalone demo runs with no
console errors, and all 10 brief acceptance criteria are verified by driving the
physics in a browser (not just compiling). I then ran a 20-agent adversarial
review, which confirmed 6 issues (from 15 raised); I fixed 3 in code and
documented 2 as core-contract friction for the integrator. Re-verified after the
fixes — no regressions.
Deliverables
| File | Role |
|---|---|
| src/player/PlayerController.ts | The controller. Implements IPlayerView. |
| src/player/collision.ts | Swept per-axis AABB-vs-voxel resolution + auto-step. |
| src/player/input.ts | Scriptable InputState + pointer-lock InputController. |
| src/player/index.ts | Public surface (what integration imports). |
| src/player/HANDOFF.md | API + tuning + friction detail. |
| src/demo/playerDemo.ts + demo-player.html | Standalone demo (all mocks marked // DEMO MOCK). |
Public API (per the brief, exactly):
const player = new PlayerController(world /* IVoxelWorld */, {
getColliders: () => machines.flatMap(m => m.getColliders()),
camera, domElement, spawn: [x, y, z],
});
player.update(FIXED_DT); // call each fixed tick AFTER machines.update(dt)
player.teleport(v); player.setFlying(b);
// IPlayerView for Lanes D/E: position (feet), eye, lookDir, onGround, groundedOn
Acceptance — verified live
Driven headlessly through a debug handle in the demo (deterministic update()
stepping), so these are measured, not eyeballed:
| Criterion | Measured result |
|---|---|
| Walk / sprint / jump / fly | ✅ all correct |
| Auto-step 1-voxel ledge | ✅ climbs staircase to y=6 |
| 2-voxel wall NOT stepped | ✅ blocked at x=29.7, never climbed |
| No tunneling (400× sprint-jump into wall) | ✅ never crosses x=30 |
| Disc ride = clean circle, no rim drift | ✅ radius holds at 20.00 over 2s |
| Near-center calm / rpm45 rim > sprint | ✅ carry 0.42 vs 11.31 (sprint=5.6) |
| Fling on jump off rim | ✅ ~10.2 v/s, airborne |
| Oscillating AABB platform, no fall-through | ✅ groundedOn=platform, y=1.0 |
player:step / player:landed fire |
✅ 6 steps over 10.5 voxels / impact 26.5 |
npm run typecheck clean, no console errors |
✅ |
Adversarial review outcome (self-run, 20 agents, 6/15 confirmed)
Fixed in code (re-verified, no regressions):
- Overlapping colliders double-applied carry (medium) —
resolveCollidersapplied surface carry per-collider, so two overlapping rideable colliders (e.g. a future fader sled crossing a platter rim) would translate the player by the sum and report only the last one viagroundedOn/carryVelocity. Fix: ground on exactly one collider (highest supporting top), apply carry once. - Keyboard input wasn't gated by pointer-lock (medium) — WASD/F drove the character while unlocked (only mouse-look was gated). Fix: gate key handlers on lock state, mirroring mouse-look. Integrator note: in-game, movement now requires clicking to lock first (standard FPS behavior).
- Footsteps sampled a single center column (low) — at a bridge/ledge edge the center column can be air while a neighbor supports the foot, silently dropping the event. Fix: sample the full footprint (same cells grounding uses). Verified: walking a bridge edge now fires footsteps where it fired none before.
Documented as core-contract friction (needs an integrator/core decision — I did
not edit src/core/):
KinematicCollider.velocityAt(x,y,z): Vec3allocates per sample (low) — while riding I sample it up to 8×/frame (~480 arrays/sec), brushing CONTRACTS §8. Clean fix is a core out-param overloadvelocityAt(x, y, z, out?: Vec3).- Vertical platform carry is intentionally dropped — fine for v1 gear (+Y
platters, horizontal faders/tonearm). Only matters if Lane D ships a
vertically-moving rideable collider; then
applyCarryneeds a Y sweep.
(9 findings were refuted on verification — e.g. "getters leak live arrays" and "held-jump re-emits landing"; details in the review, both judged non-issues.)
Integration notes for main.ts (per docs/INTEGRATION.md step 6)
- Construct after Lane A's
worldand Lane D's machines exist; passgetColliders: () => machines.flatMap(m => m.getColliders()). - Fixed loop order:
machines.update(dt)thenplayer.update(dt)— the player reads the current collider positions/velocities that tick. - The controller owns the camera's transform + FOV each tick; don't also move it.
- Local feel constants (accel/friction/step/bob) live at the top of
PlayerController.ts, not in core, becausePLAYERhas no such fields. If you want them centralized, promote them to a new non-core config file.
Suggested next steps (for Fable to weigh)
- Contract tweak decision: approve the
velocityAtout-param overload above? It's a one-line core change that removes the only hot-path allocation. If yes, I'll wire Lane B to the buffered form once core changes. - Cross-lane: Lane D should confirm cylinder colliders are +Y-axis only and
expose platter/fader/tonearm colliders with
velocityAtin voxels/sec — Lane B already rides anything matchingKinematicCollider. - Optional polish I left out of scope (not in brief): crouch, coyote-time jump, ladder/vent climbing for the speaker-mesh (DESIGN §4.2) — flag if wanted.
Repo / git
This working copy is not yet a git repo (git status fails). I did not run
git init, branch, or push to ssh://git@100.71.119.27:222/monster/TURNCRAFT.git
— initializing the shared repo and landing five lanes is an integration decision,
not Lane B's to make unilaterally. Everything above is on disk under
/Users/jing/TURNCRAFT, ready to commit on the Lane B branch when you say go.