HardYards/PLAN3D.md
m3ultra 868ea5699d Correct PLAN3D asset inventory; log M0 contract additions
§2's paths were substantially wrong. Re-verified against the filesystem:
3D-STORE is at ~/Documents/Destroyulater/3D-STORE (its clean_glbs debris set is
real, just relocated), while 3D=models/, character_kit/, mixamo-fetch/ and FBX/
do not exist on this box at all. That last group is Lane D's whole animation
pipeline, so it's flagged loudly rather than quietly patched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 21:33:42 +10:00

20 KiB
Raw Permalink Blame History

SHADES 3D — execution plan & lane instructions (for Opus 4.8)

Port the proven 2D prototype (see prototype/, play it before writing code) to a 3D third-person game: you are a small person in a real backyard, rigging shade sails against a storm you can see coming, then running repairs in wind and rain.

Read this whole file before starting your lane. DESIGN.md is the design canon; this file is the build canon.


0. Ground rules (all lanes)

  • Stack — locked: three.js r175 vendored (copy ~/Documents/90sDJsim/web/world/vendor/ — three.core.js, three.module.js, addons) + zero-dependency Python stdlib server (server.py, http.server, no pip, no DB, no build step, no CDN, no npm). This is the proven house stack from 90sDJsim; do not relitigate it.
  • Where you run: on the M1 Ultra (johnking@100.91.239.7, Tailscale). All asset paths below are local there. Clone your lane from gitea: git clone git@gitea.partly.party:monster/shades.git ~/Documents/shades-lane<X> then work on branch lane/<x>. Push your branch; never force-push main.
  • Units: meters, +Y up, world origin at yard center on the ground. Player height ~1.7 m. The yard is ~30×20 m. GLB exports: Y-up, meter scale, *_v1.glb naming into web/world/models/.
  • Asset copies rule (house rule from DJsim): runtime models in web/world/models/ are COPIES. Canonical sources stay in the shared libraries — see §2 for the verified paths, several of which are not what this plan originally claimed. Never hand-edit a game-local GLB; rebuild at source and re-copy. Raw .fbx/.blend are gitignored (.gitignore enforces it); runtime asset is always .glb.
  • Rigged characters: follow 90sDJsim DEVMANUAL "rigged characters" rules — SkeletonUtils.clone() for instances, height-normalize by head-bone height, never blind setScalar. The Mixamo pipeline is ~/Documents/mixamo-fetch/ (README inside; wishlist → fetch.cjs); already-fetched clips sit in ~/Documents/FBX/ and ~/Documents/3D=models/animations/.
  • Testing: the game must run headless-testable. web/world/selftest.html loads the sim modules WITHOUT rendering and runs assert suites (fast-forward a 90 s storm in a loop, print pass/fail JSON to console + DOM). Every lane keeps its module importable there. NOTE: requestAnimationFrame pauses in hidden tabs — selftest must drive the sim with fixed-dt loops, not rAF.
  • Commit style: small commits on your branch, imperative messages, no binary churn (don't re-export unchanged GLBs).
  • File ownership is the merge strategy. Lanes only edit files they own (see lane sections). Shared contracts live in web/world/js/contracts.js — Lane A lands it in M0 and after that it changes only by agreement (note in THREADS.md).
  • Coordination: append-only log in THREADS.md at repo root — one line per landed feature / contract change / open question, prefixed with your lane letter.

1. What we're building (M0 → M3)

M0 — walkable yard (Lane A, serial, ~first). Skeleton everyone builds on: index.html + server.py + vendored three; terrain plane, house shell, placeholder trees/posts; third-person camera; contracts.js. Merged to main before other lanes' first merge (they develop against contracts + mock harnesses meanwhile).

M1 — the rig. Sail cloth sim attached to anchors with per-corner hardware; prep-phase UI (pick 4 anchors, cycle hardware, tension); calm-day wind makes the sail breathe. Character walks, camera follows, anims play.

M2 — the storm. Storm timeline: wind ramp + gusts (telegraphed), direction change, rain; corner loads → weakest-link breaks → cascade; flogging sail; player runs repairs (hold-E re-rig with spare); wind shoves player; debris (3D-STORE crates/tubs) tumbles through.

M3 — the game. Garden bed coverage scoring, prep timer, forecast card, end-of-storm aftermath screen, sound (wind/creak/flog/rain layers), and the full loop: forecast → prep → storm → aftermath. Optional: deploy via the 90sDJsim pattern (docker on dealgod VPS).

The 2D prototype (prototype/game.js) is the reference implementation for: gust scheduler shape, load = spring-stretch at corners smoothed, break = 0.4 s sustained overload, hardware tiers/prices, garden damage = f(uncovered), wind-change event, repair hold. Port the behavior, retune the constants.


2. Asset inventory (re-verified on m3ultra 2026-07-16 by Lane A)

This table was wrong in the first draft and has been corrected against the actual filesystem. Several libraries it promised are not on this box at all. Trust this table over any path you remember; if you find one of the missing libraries somewhere, say so in THREADS.md rather than fixing it locally.

Need Have Path
three.js r175 + addons (GLTFLoader, SkeletonUtils…) verified, now vendored in-repo web/world/vendor/ (copied from ~/Documents/90sDJsim/web/world/vendor/)
Player character (small person) 21 rigged peds ~/Documents/90sDJsim/web/world/models/peds/ (man_casual_01-03, woman_casual_01-02, elder, hivis worker…)
Rigged-character loading rules ~/Documents/90sDJsim/DEVMANUAL.md — Lane D: this is law
Locomotion anims ⚠️ thin ~/Documents/90sDJsim/web/world/models/peds/idle.glb, walk.glb are the only clips found. Run/Falling/Crouch-To-Stand are not on this box
Debris + yard clutter exactly as promised, new path ~/Documents/Destroyulater/3D-STORE/clean_glbs/ — BlueCrate_v2, BlackTub_v2, WhiteTub_v2, WoodenBin_v2, LibraryTrolley_v1, AirCon_v1
Scripted-Blender build idiom new path ~/Documents/Destroyulater/3D-STORE/build_booth_room_v23.py (also ~/Documents/3dstore/)
Trees, fence, house exterior, shed, garden bed, grass, shade-sail hardware GAP Lane E builds these. Lane A has graybox stand-ins at the right scale and positions in world.js — match those and they drop straight in
Blender installed /Applications/Blender.app
~/Documents/3D=models/ (characters, 32 anim clips, street-furniture incl. building_shell_01.glb, furniture, props-scenes) DOES NOT EXIST searched ~ and /Volumes + Spotlight. Nothing at this path or any similar name
~/Documents/character_kit/ (hum_character.glb, retarget pipeline) DOES NOT EXIST
~/Documents/mixamo-fetch/ (wishlist → fetch.cjs) DOES NOT EXIST closest is ~/Documents/mixamo_upload/ (Hum_M_1_mixamo.fbx/.obj) — an upload staging dir, not the fetcher
~/Documents/FBX/ (Running, Falling, Crouch To Stand…) DOES NOT EXIST only Unreal's own internal FBX dirs match the name

Consequences, unresolved — Lane D is the one this hurts. §5-D assumes the character_kit retarget pipeline and a library of Mixamo clips. Neither is here. The ped fleet and DEVMANUAL are real, so a walking character is still reachable, but Run / Falling / Crouch-To-Stand / crank / dig have to come from somewhere. Post in THREADS.md before burning a day looking for them.

Open question: PLAN3D §0 says the work happens on "the M1 Ultra (johnking@100.91.239.7, Tailscale)", but this box is m3ultra and already has ~/Documents/shades-laneB/ and shades-laneE/ checked out, so lanes are in fact running here. If the missing libraries live on that other machine, §0 needs a decision about where lanes run — not just a path fix.


3. Repo layout (target)

shades/
├── DESIGN.md, PLAN3D.md, THREADS.md
├── prototype/                  # 2D reference — do not modify, do not delete
├── server.py                   # stdlib static server + /selftest passthrough, port 8801
├── .claude/launch.json         # "shades3d" config → python3 server.py
└── web/world/
    ├── index.html              # game shell
    ├── selftest.html           # headless sim asserts (fixed-dt, no rAF)
    ├── vendor/                 # three r175 (copied, committed)
    ├── models/                 # runtime GLBs (copies)
    ├── audio/
    └── js/
        ├── contracts.js        # SHARED interfaces — Lane A owns, change by agreement
        ├── main.js             # boot + game loop + phase machine   (A)
        ├── world.js            # terrain, yard, props, lighting, sky (A)
        ├── camera.js           # third-person rig                    (A)
        ├── testkit.js          # assert/fixedLoop harness            (A)
        ├── tests/<lane>.test.js # ONE PER LANE — you own yours, nobody
        │                       # edits selftest.html (it just imports these)
        ├── sail.js             # cloth sim, anchors, hardware, loads (B)
        ├── rigging.js          # prep-phase UI + hardware economy    (B)
        ├── weather.js          # wind field, storm timeline, rain    (C)
        ├── skyfx.js            # storm sky, lightning, audio layers  (C)
        ├── player.js           # character, controls, anims          (D)
        ├── interact.js         # tool prompts, hold-E actions        (D)
        ├── debris.js           # tumbling objects, collisions        (C)
        └── hud.js              # meters, forecast card, aftermath    (A after M0, else propose in THREADS)

4. Contracts (the integration spine)

Lane A lands contracts.js in M0 with exactly these shapes (JSDoc-typed):

// Wind: THE shared primitive. Everything samples wind through this.
// weather.js implements; world/sail/player/debris consume.
wind.sample(pos /*Vector3*/, t) -> Vector3        // m/s, includes gusts & local effects
wind.gustTelegraph(t) -> {eta, dir, power}|null    // for HUD + grass wave + player audio cue

// Storm timeline (data, not code): storms are JSON so design can tune without code.
// web/world/data/storms/*.json: {duration, baseCurve:[[t,speed]...], gusts:{minGap,maxGap,pow...},
//   dirCurve:[[t,rad]...], events:[{t,type:"windchange"|"debris",...}], rain:{...}}

// Sail rig
sailRig.corners -> [{anchorId, hw:{name,cost,rating}, load, broken}]
sailRig.attach(anchorId[4], hwChoices, tension)
sailRig.step(dt, wind, t)                          // fixed-dt, deterministic
sailRig.coverageOver(rect) -> 0..1                 // ground-projected shade
sailRig.events -> emits {type:"break"|"repair", corner}

// Anchors (world provides; sail consumes)
world.anchors -> [{id, pos, type:"house"|"tree"|"post", sway(t)->Vector3}]

// Player
player.pos, player.carrying, player.busy
interact.register({id, pos, radius, holdSecs, label, canUse()->bool, onDone()})

// Game phases
game.phase: "forecast"|"prep"|"storm"|"aftermath"; game.on(phaseChange)

Determinism rule: sail.js, weather.js, debris.js take (dt, t) — no Date.now(), no internal rAF — so selftest can fast-forward storms.


5. Lanes (run these in parallel)

Lane A — World, shell & integration (start first; owns main.js, world.js, camera.js, hud.js, contracts.js, server.py, index.html, selftest.html)

  1. M0 skeleton per §3: copy vendor from DJsim, stdlib server.py (port 8801, --selfcheck flag runs a headless node-free check by printing the selftest URL; keep it dumb), launch.json, contracts.js, and a walkable graybox yard: 30×20 m ground with gentle height variation, house shell along north edge (graybox — building_shell_01.glb does not exist on this box, see §2), 2 placeholder trees, 2 posts, garden bed rect, boundary fence graybox. Third-person follow camera (orbit on RMB, shoulder-follow default). Directional sun + hemisphere light, calm sky. Merge to main fast — everyone rebases on this.
  2. HUD after M0: wind meter + arrow, corner load bars (world-anchored sprites), garden health, phase banner, forecast card, aftermath screen. Consume contracts only; mock data until B/C land.
  3. Integration duty: you are the merge shepherd. Rebase lane branches onto main in order (E-assets swap is a drop-in; B and C rebase next; D last), run selftest.html + a manual storm after each merge, log regressions in THREADS.md.
  4. Swap in Lane E's GLBs as they land (tree_v1, fence, shed, house when ready).

Acceptance: 60 fps on the M1 Ultra in graybox storm scene; camera never clips through house; selftest green after every merge.

Lane B — Sail simulation & rigging (owns sail.js, rigging.js)

  1. Port prototype cloth to 3D verlet: grid N=10 per side, nodes are Vector3, structural+shear+bend constraints, 5 relaxation iterations, fixed dt 1/60. Wind force per FACE (not node): F = area * clamp(dot(wind, normal))² * ρ along face normal + tangential drag — this gives real hypar behavior: a twisted quad sheds load, a flat one catches it. Prove it in selftest: assert twisted-rig peak corner load < flat-rig peak load, same storm.
  2. Corner load = smoothed stretch of springs incident to corner (prototype formula); hardware tiers/prices from prototype; break after 0.4 s sustained overload; broken corner frees the node → flogging is emergent. Sag under gravity when calm; tension parameter scales rest lengths (0.61.4).
  3. Rendering: BufferGeometry updated from nodes, double-sided, cloth material, per-vertex normal recompute each frame. Sail casts real shadows (this IS the shade mechanic — coverageOver() can raycast sun direction or ground-project).
  4. Rigging UI (prep phase): click anchor markers to assign corners (max 4, ring-order by angle), click corner to cycle hardware with budget, tension slider, spare purchase. Reuse prototype economy numbers as the starting tune.
  5. Selftest suite: flat-vs-hypar assert; cascade assert (break one corner at fixed t → neighbor load must spike ≥2×); determinism assert (two runs, same inputs → byte-equal load traces).

Lane C — Weather, sky & debris (owns weather.js, skyfx.js, debris.js, data/storms/)

  1. wind.sample(pos,t): base curve + scheduled gusts (port prototype scheduler: telegraph 1.5 s, ramp 0.8 s, hold ~2 s, fade) + direction curve with one mid-storm change + spatial variation (smooth noise over x,z so the yard isn't uniform; add tree wind-shadow discs downwind of trees — cheap, juicy). Storm definitions load from JSON (§4). Author storm_01_gentle.json, storm_02_wildnight.json to bracket difficulty.
  2. Rain: GPU-friendly instanced streaks (~24 k), angle/speed follow local wind, fade by intensity from storm JSON; splash rings on ground optional. Storm sky: darken + cloud dome scroll + rumble; one cheap lightning flash event type.
  3. Audio layers (files or synthesized WebAudio): wind bed pitched by speed, gust whoosh keyed to telegraph, rain layer, rope-creak keyed to max corner load (hook via sailRig.corners), fabric flog when any corner broken. Audio unlock on first input (browser rule).
  4. Debris: spawn events from storm JSON — pick from models/debris/ (Lane E copies 3D-STORE crates/tubs there in M0 week). Kinematic tumble along wind with bounce on ground plane, sphere-vs-player knockdown (1.5 s stagger, drop carried), sphere-vs-sail node impulse. No physics engine — hand-roll, ~150 lines.
  5. Selftest: gust telegraph always ≥1.2 s before ramp; wind.sample continuity (no frame-to-frame jumps >X); storm JSON schema validator.

Lane D — Player character & interaction (owns player.js, interact.js)

  1. Character: pick a ped from 90sDJsim/web/world/models/peds/ (copy into models/, per copies-rule; man_casual_01 or woman — small relative to 4 m posts reads "small person"). Load with DEVMANUAL rig rules. ⚠️ Clips are a live problem — read §2 before you start. character_kit/, mixamo-fetch/, FBX/ and 3D=models/animations/ are NOT on this box. The only clips found are peds/idle.glb and peds/walk.glb. Walking is reachable from those; Running / Falling / Crouch-To-Stand / crank / dig are not sourced yet. Raise it in THREADS.md rather than hunting.
  2. Controls: WASD relative to camera, walk/run (shift), acceleration + turn smoothing, anim state machine (idle/walk/run + one-shot interact). Gamepad optional. Slope clamp to terrain height.
  3. Weather effects on player: movement slow factor from local wind speed + rain; gust shove = impulse along wind during gust hold (scale with speed²); knockdown state (from debris or extreme gust): Falling clip + get-up (Crouch To Stand), drop carried item.
  4. Interactions via interact.register: hold-E with radial progress — re-rig broken corner (needs spare, 2.5 s), tighten/ease turnbuckle at corner (±tension at ONE corner, 1.2 s — new vs prototype, makes corners individual), pick up/carry spare hardware from shed table (one item, hands-full rule).
  5. Selftest: state machine table test (no stuck transitions); interact radius respects busy/carrying flags.

Lane E — Nature & hardware assets (Blender, scripted; owns tools/blender/, models/)

The gap lane. House idiom: scripted Blender builds (see ~/Documents/Destroyulater/3D-STORE/build_booth_room_v23.py and racks_to_glb.py for the export pattern — note the corrected path, §2). One tools/blender/build_yard_assets.py that regenerates everything → clean/ GLBs; commit script + GLBs.

Deliverables (meters, Y-up, low-poly stylized to match ped fleet, baked or flat colors, <15 k tris each):

  1. tree_gum_01/02.glb — trunk + 2-3 canopy blobs as SEPARATE named nodes (trunk, canopy_*, branch_anchor_* empties at rig points) so Lane A can sway canopies in wind and Lane B can query branch anchor positions.
  2. fence_panel.glb, fence_post.glb (tileable), gate.glb.
  3. house_yardside.glb — single-story rear façade: wall, 3 fascia_anchor_* empties, gutter line, window, door. Doesn't need interior.
  4. shed_01.glb + shed_table.glb (spare-hardware pickup point).
  5. garden_bed.glb — raised bed + plant billboard tufts (damage states: full / tattered / dead as morph or 3 variants).
  6. sail_post.glb (raked steel post + footing collar), ladder_01.glb.
  7. Hardware close-ups (visible at corners): shackle.glb, carabiner.glb, turnbuckle.glb — tiny, but they're the fetish objects of the game; make them read at 1 m distance.
  8. models/debris/: copy + sanity-scale BlueCrate_v2, BlackTub_v2, WhiteTub_v2, WoodenBin_v2 from ~/Documents/Destroyulater/3D-STORE/clean_glbs/ (verified present, corrected path — §2); add tramp_01.glb (trampoline!) if time.
  9. Grass: NOT geometry — ship a 4-tuft billboard atlas texture; Lane A instances.

Acceptance: blender -b -P tools/blender/build_yard_assets.py regenerates all GLBs deterministically; each GLB loads in a viewer with correct scale vs a 1.7 m reference capsule (include ref_capsule.glb in the script).


6. Integration order & schedule

day 0:   A lands M0 skeleton → main          (B,C,D,E develop against contracts + mocks)
day 1-2: E drops first GLBs (trees/fence/house) → A swaps graybox
         B lands cloth in selftest, then rendered sail on main
         C lands wind+rain on main (sail immediately reacts — first magic moment)
day 3-4: D lands character + controls + weather effects
         C lands debris + audio; B lands rigging UI/economy
day 5:   M2 storm playable end-to-end; tune pass (everyone plays, THREADS notes)
day 6+:  M3: scoring, forecast/aftermath, prep timer, sound polish, storm JSON tuning

Merge conflicts should be ~zero if file ownership is respected. If you need to touch another lane's file: don't — post the need in THREADS.md instead.

7. Definition of done (M3)

  • Full loop: forecast card → timed prep (rig 4 corners on any anchors, budget $80, buy spare) → 90 s storm (gusts telegraphed, wind change, rain, ≥1 debris event) → aftermath (garden %, hardware losses, cash delta).
  • A flat drum-tight cheap rig MUST cascade-fail in storm_02; a well-twisted mixed rig with one mid-storm repair MUST be survivable. Verify in selftest AND by hand.
  • 60 fps on the M1 Ultra, no build step, python3 server.py and it runs.
  • Selftest green; THREADS.md tells the story; DESIGN.md updated where reality diverged from plan.