HardYards/SPRINT2.md
m3ultra 76421a7f86 Add Sprint 2 assembly instructions: wire proven modules into one playable storm
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 22:24:35 +10:00

7.9 KiB
Raw Blame History

SPRINT 2 — ASSEMBLY (instructions for Opus 4.8 lanes)

Sprint 1 verdict: every module is built and proven in isolation — 121/121 selftest asserts green on merged main — but the game is not assembled. main.js still drives the M0 placeholder capsule and stub wind. Sprint 2 is one thing: wire the proven modules into one playable storm. Read THREADS.md from your last entry down before starting; the integrator [I] entry lists what changed under you.

Decisions (made — stop waiting on them)

  1. Lanes run on m3ultra. Lane D's recommendation is adopted: the M1 Ultra (johnking@100.91.239.7) is an asset-build box you SSH to; GLBs get committed; the game never needs it at runtime. PLAN3D §0 is amended by this line.
  2. Sail-area problem (B's 70192 m² finding): fix the yard, not the physics. Lane A: move p1/p2 in to roughly (4.5, 5.5) and (4.0, 6.0), add a third post p3 near (0, 7), and register the trees' branch_anchor_* empties (E shipped them with rating_hint) as anchors. Target: at least three pickable quads in the 1845 m² range that can shade the garden bed, verified by a new a.test assert that enumerates quad areas. The huge quads stay possible — the load bars teaching "you cannot span the whole yard" is design working as intended.
  3. Flat-horizontal loophole: Lane C closes it with vertical gust structure. Real gusts aren't horizontal; add a per-gust vertical component (downdraft fraction in storm JSON, default ~0.25 of gust power, validated) so a horizontal plate carries real load. Lane B adds the assert: over 8 directions in storm_02 wind, flat-horizontal peak load ≥ 60% of flat-pitched peak (i.e. no longer a free lunch). Ponding stays out of scope (M4 water spike).
  4. repair/trim seam: Lane B conforms to Lane D's call sites (D landed first, duck-typed): add repair(i) (→ repairCorner with the spare's hw), trim(i, delta) (→ trimCorner) and cornerPos(i) -> Vector3 (live world position, fresh vector) to the rig object. Contract entries + b.test asserts.
  5. debris↔sail seam: option (b) — Lane B reads debris.pieces ({x,y,z,vx,vy,vz,r,mass}) inside sail.step() and applies impulses; momentum bookkeeping stays in the one integrator. Lane C freezes the pieces shape.
  6. House GLB: no re-cut. Lane A reads fascia_anchor_* positions out of house_yardside_v1.glb at load and places anchors there (data wins over the yard constants). E's 2.80 m fascia replaces the 2.6 m graybox number.

Lane A — assemble the game (this is the sprint)

main.js boot(), in order; keep each step behind a small commit:

  1. Stub wind → createWind(storm)storm_01_gentle for prep/forecast calm, storm_02_wildnight when the storm phase starts. Call wind.setSheltersFromTrees(...) after the yard builds, per C's ask.
  2. Placeholder → await createPlayer(scene, world, cameraRig, {wind, interact}) (async boot; D says same first three args). Delete the placeholder factory.
  3. Sail: const view = await createSailView(rig); scene.add(view); view.update() per frame after rig.step(dt, wind, t). B's THREADS entry has the exact shape.
  4. createSkyFx({scene, camera, wind, sun, hemi}) + unlockAudio() on first input; createDebris({heightAt: world.heightAt, onHitPlayer: player.knockdown}), debris.setModels() from models/debris/ (glob the dir).
  5. Yard dressing: swap graybox house → house_yardside_v1.glb (decision 6), shed + shed_table (D's spare pickup), sail posts as sail_post_v1.glb rotated about rake_pivot, fence set, grass billboards off textures/grass_atlas.png.
  6. Anchor rework per decision 2.
  7. HUD: loads in kN (B's units note), per-corner bars vs rating, wind meter + gust telegraph banner, garden % (wire rig.coverageOver(world.gardenBed) → HP → E's plants_full/tattered/dead visibility swap), phase banner, forecast card (storm JSON summary: peak wind, change time), aftermath screen (garden %, corners lost, budget delta).
  8. Phase machine: forecast (show card, Enter) → prep (rigging UI live, budget $80, optional timer OFF this sprint) → storm (90 s, storm_02) → aftermath. Acceptance: python3 server.py → rig a sail with the mouse, press Enter, watch storm_02 try to kill it, repair a corner mid-storm, see the aftermath screen. 60 fps during the storm on this box. Selftest stays green after every merge.

Lane B — sail in the world

  1. Decision 4 API (repair/trim/cornerPos) + asserts.
  2. Decision 5: consume debris.pieces in step(); assert momentum is conserved within tolerance on a crate-through-sail scenario.
  3. Prep-phase picking adapter: RiggingSession → clicks. Raycast against Lane A's anchor markers (A exposes world.anchorMarkers if you need meshes — agree in THREADS), corner cycling + tension dial + spare purchase, HUD summary line from summary(). This unblocks A step 8.
  4. Joint tuning session with C (their THREADS ask): retune cloth ρ against real m/s storms; then re-run the §7 gate against REAL wind (current assert used the stub) — flat cheap rig cascades in storm_02, twisted mixed rig + one repair survives. Log tuned constants in THREADS.
  5. Small fix: coverageOver() rays start at heightAt(x,z), not y=0 (your nit).
  6. Decision 3 assert (flat-horizontal no longer dominant) once C lands vertical gusts.

Lane C — weather in the game

  1. Decision 3: vertical gust component in storm JSON + validator + asserts.
  2. Decision 5: freeze and document debris.pieces shape in contracts.js.
  3. Rain must react to the sail: cheap occlusion — sample rig.coverageOver cells or raycast a handful of drops so the garden visibly stays dry under cloth. (Coordinate the API with B; don't ray-test every drop.)
  4. Storm tuning session with B (see B-4).
  5. skyfx: verify light restoration on dispose() inside the real main.js scene — A will call you if teardown flickers phase transitions.
  6. Consider retiring weather_demo.html once main.js hosts the storm — your call, it stops earning its place when the game IS the bench.

Lane D — player in the storm

  1. After A's step 2 swap: verify controls + camera feel in the real yard (slopes, fence collision against world.solids), tune walk/run speeds against yard scale, make gust shove + knockdown fire from real wind and real debris hits.
  2. Wire the spare pickup loop end-to-end: shed_table pickup_anchor → carrying → repair(i) consumes it (B's decision-4 API). The §7 "one mid-storm repair" scenario must be playable by hand.
  3. Interaction prompts follow cornerPos(i) live (flogging corner = moving prompt).
  4. Kill dev_player.html only when the main game covers everything it proves.

Lane E — polish targets (small sprint)

  1. Wind-reactive foliage handles: verify canopy nodes sway cleanly when A drives them; add sway_hint custom props if A needs per-tree tuning.
  2. Sail cloth texture (512² weave/UV-stripe atlas) so the membrane reads as fabric, + a simple tear decal strip for M3.
  3. Storm dressing set: wheelie bin (debris, mass_hint), washing line, garden gnome (collateral scoring bait). One script run, same acceptance as before.
  4. Contact-sheet the assembled yard from A's camera for DESIGN.md.

Order & gates

gate 1: A steps 1-4 land  → the yard has weather, a person and a live sail
gate 2: B-3 picking + A-8 → the loop is playable start to finish
gate 3: B/C tuning + D-2  → §7 scenario passes BY HAND and in selftest

Definition of done = gate 3 + THREADS updated + selftest green + a 90 s storm_02 run captured (screenshots or video) for the log.

For John (human-only items)

  • Mixamo login on the M1 for D's M3 wishlist (tools/character/mixamo_wishlist.txt — Climbing Ladder, Turning Key, Digging). Not blocking this sprint.
  • Two dev servers float around: another session holds :8801; this repo's launch.json now uses :8809. Kill either freely; server.py --port N works.