TURNCRAFT/docs/UPDATE_B.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

6.4 KiB
Raw Blame History

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):

  1. Overlapping colliders double-applied carry (medium)resolveColliders applied 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 via groundedOn/carryVelocity. Fix: ground on exactly one collider (highest supporting top), apply carry once.
  2. 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).
  3. 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/):

  1. KinematicCollider.velocityAt(x,y,z): Vec3 allocates 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 overload velocityAt(x, y, z, out?: Vec3).
  2. 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 applyCarry needs 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 world and Lane D's machines exist; pass getColliders: () => machines.flatMap(m => m.getColliders()).
  • Fixed loop order: machines.update(dt) then player.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, because PLAYER has 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 velocityAt out-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 velocityAt in voxels/sec — Lane B already rides anything matching KinematicCollider.
  • 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.