HardYards/PLAN3D.md
m3ultra 8338c0fa99 Add 3D execution plan, lane prompts, and THREADS log
PLAN3D.md: locked stack (vendored three.js r175 + stdlib server, DJsim
house style), verified asset inventory on the M1 Ultra, module contracts,
five parallel lanes (world/sail/weather/player/assets) with acceptance
criteria and integration order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:05:57 +10:00

305 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
(`~/Documents/3D=models/`, `~/Documents/character_kit/`, `~/Documents/3D-STORE/`,
`~/Documents/FBX/`). Never hand-edit a game-local GLB; rebuild at source and re-copy.
Raw `.fbx`/`.blend` are gitignored; 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 (M1 Ultra, verified 2026-07-16)
| Need | Have | Path |
|---|---|---|
| three.js r175 + addons (GLTFLoader, SkeletonUtils…) | ✅ vendored | `~/Documents/90sDJsim/web/world/vendor/` |
| Player character (small person) | ✅ rigged fleet | `~/Documents/90sDJsim/web/world/models/peds/` (man_casual_01-03, woman, elder…); canonical rigs `~/Documents/character_kit/` (hum_character.glb, female/); more in `~/Documents/3D=models/characters/` (53 people-normal + comical) |
| Locomotion anims | ✅ | `~/Documents/FBX/` (Running, Falling, Crouch To Stand, Reaction…), `~/Documents/3D=models/animations/` (Walk, Start Walking, Happy Idle, Injured Walk…, 32 clips) |
| Missing anims (climb ladder, crank/wrench, dig, hammer) | fetch | `~/Documents/mixamo-fetch/` — add to wishlist, run fetcher |
| Debris + yard clutter | ✅ perfect fits | `~/Documents/3D-STORE/clean_glbs/` (BlueCrate, BlackTub, WhiteTub, WoodenBin, LibraryTrolley…) + `~/Documents/3D=models/street-furniture/` (benches, streetlight, market_stall, food_cart, building_shell_01) |
| Furniture/props (granny-flat interiors later) | ✅ | `~/Documents/3D=models/furniture/`, `props-scenes/` |
| Trees, fence, house exterior, shed, garden bed, grass, shade-sail hardware | ❌ **GAP** | Lane E builds these (Blender is installed; house style is scripted builds — see `~/Documents/3D-STORE/build_booth_room_v23.py` for the idiom) |
| Blender / Godot / UE 5.7 | ✅ installed | `/Applications`, `/Users/Shared/Epic Games/UE_5.7` (UE/Godot NOT used for this — three.js only) |
---
## 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)
├── 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):
```js
// 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
(use `street-furniture/building_shell_01.glb` scaled, or graybox), 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: Walk +
Running + Happy Idle from the libraries; retarget/merge via character_kit
pipeline. Fetch "Climbing Ladder", "Turning Key/crank", "Digging" via
mixamo-fetch wishlist for M3.
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/3D-STORE/build_booth_room_v23.py` and `racks_to_glb.py` for the
export pattern). 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 `3D-STORE/clean_glbs/`; 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.