From 31a887bf7579ac8b9f244bedf601df66dca80d4f Mon Sep 17 00:00:00 2001 From: m3ultra Date: Thu, 16 Jul 2026 21:53:00 +1000 Subject: [PATCH] Log Lane C landing, contract note and per-lane asks in THREADS Also corrects the worktree-collision attribution: the checkout that moved HEAD off lane/a was mine, not Lane D's. Co-Authored-By: Claude Opus 4.8 --- THREADS.md | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/THREADS.md b/THREADS.md index 51ebb94..88e1aec 100644 --- a/THREADS.md +++ b/THREADS.md @@ -87,3 +87,95 @@ Format: `[lane letter] YYYY-MM-DD — note` §0's clone path is what people are actually using. If the four missing libraries above live on that other machine, this isn't a path fix, it's a decision about where lanes run. Flagging rather than guessing. + +[C] 2026-07-16 — ⚠️ **THE WORKTREE COLLISION WAS ME, NOT LANE D — please don't redirect D on my + account.** Before I'd realised Lane A was live in the shared checkout, I ran `git checkout -b lane/c` + inside `~/Documents/shades/`, and a few minutes later `git checkout main` + `git branch -D lane/c` to + put it back the way I found it. That second command is exactly the "HEAD moved off `lane/a` onto + `main`" symptom A describes, and the timing lines up. Nothing was lost — the delete reported + `was 8338c0f`, i.e. no commits had landed on my branch during the window — and A's read of the damage + is right, it just has the wrong lane's name on it. Lane C has been on `~/Documents/shades-laneC/` + since. (Lane D does also appear to be committing from the shared tree — `027fb99` landed on `main` + from there — so A's ask of D still stands on its own merits. I'm only disowning the checkout.) + Apologies for the noise; PLAN3D §0's clone rule earns its keep. + +[C] 2026-07-16 — **LANE C LANDED on `lane/c` — weather.js, skyfx.js, debris.js, 2 storms.** Rebased on + M0; `c.test.js` is live (19 asserts) and Lane A's selftest reads **37 pass / 3 skip**. The stub wind + can be retired whenever A likes — `createWind()` is a drop-in for `createStubWind()`. + · `wind.sample(pos,t)` / `wind.gustTelegraph(t)` per contract; `checkContract('wind', …)` clean. + · Gusts are a **precomputed timeline**, not an integrator. The prototype accumulated `gustT += dt`; + we can't, because sample(pos,t) is called by everyone at arbitrary t and out of order. Same + envelope though — telegraph 1.5 / ramp 0.8 / hold 1.7 / fade 1.0, straight off the prototype. + · Storms are **data**: `data/storms/*.json`, validated on load (throws loud — a typo in a storm is + a content bug and should not silently blow calm). Tune curves without touching code. + +[C] 2026-07-16 — **CONTRACT — one addition, backward compatible.** `wind.sample(pos, t, out?)` takes an + optional third arg: pass a Vector3 and it writes into it instead of allocating. Lane B, please use it + — per-face sampling on a 10×10 grid at 60 Hz is ~5k Vector3 allocations/sec otherwise. Two-arg calls + behave exactly as specified, and unlike the stub the returned vector is freshly allocated and yours + to keep (the contract's "clone before you store it" rule still holds for stub-era code, it's just no + longer necessary against the real wind). + +[C] 2026-07-16 — **ASKS, one per lane. All degrade silently — nothing here blocks a merge.** + · **Lane A** — trees don't shelter anything until you tell me where they are: + `wind.setSheltersFromTrees(world.anchors.filter(a => a.type === 'tree'))` after the yard builds. + Unset = no wind shadows, which is just a flatter yard. Also `createSkyFx({scene, camera, wind, + sun, hemi})` — it modulates YOUR lights and hands them back on `dispose()`, it doesn't own them; + and `createDebris({heightAt: world.heightAt})` so debris bounces off your terrain, not y=0. + skyfx needs `unlockAudio()` on the first click/keydown (browser rule) or the storm is silent. + · **Lane B** — ❓ **the debris-vs-sail seam is your call.** I have the impulse maths but not your + nodes: contracts exposes `sailRig.corners`, not the cloth. Two options — (a) I keep driving it and + you expose `sailRig.nodes` (array of `{x,y,z}`; I push them out of the sphere and let your verlet + turn that into velocity — written and duck-typed, it lights up the moment `nodes` exists), or + (b) you read `debris.pieces` (`{x,y,z,vx,vy,vz,r,mass}`) in `sail.step` and do it yourself, since + you own the integrator. I'd take (b) if you want the momentum bookkeeping in one place. Say which + and I'll match it. + · **Lane D** — `createDebris({onHitPlayer: (piece, impact) => …})` fires when something big enough + actually connects (impact = |v|·mass, threshold 25, so a tub rolling past your ankles doesn't + floor you). The knockdown state machine is yours per §5-D.3; I only report the hit. + · **Lane E** — I need `web/world/models/debris/{BlueCrate_v2,BlackTub_v2,WhiteTub_v2,WoodenBin_v2}.glb` + (your §5-E.8; A confirmed the sources are real at `~/Documents/Destroyulater/3D-STORE/clean_glbs/`). + Until they land debris renders as graybox boxes, so this is cosmetic, not blocking. + `debris.setModels({name: Object3D})`. Collision is one sphere per piece — radii in `MODEL_SPEC` in + debris.js assume a ~0.6 m crate; if you scale them differently, tell me rather than fighting it. + +[C] 2026-07-16 — **Lane B: tune cloth ρ against these, not against the stub.** Wind is in real m/s and + the stub is not (its 8→34 ramp is the prototype's pixel-ish scale wearing m/s units — A says as much + in `createStubWind`'s doc). `storm_01_gentle`: sustained peaks 6.5, worst gust 11.3 m/s (41 km/h) — + the sail should breathe and nothing should break. `storm_02_wildnight`: sustained peaks 20 (72 km/h), + worst gust 32.3 (116 km/h, BOM 'destructive'), southerly change swings 2.09 rad at t=55–59 with the + peak landing just after it. That change is the design: the corners that were slack all storm are the + ones that cop it. PLAN3D §7 (flat cheap rig must cascade-fail in storm_02; twisted mixed rig with one + repair must survive) is a **joint B+C gate** — I can't assert it without your cloth, so I've asserted + the wind half (`storm_02 is genuinely violent, storm_01 is not`). Ping me when sail.js lands and we'll + tune together; if storm_02 can't break a carabiner rig I'll raise the curve — that's a data edit. + +[C] 2026-07-16 — Notes on my own files, so nobody trips over them: + · `weather.core.js` imports **nothing** — no THREE, no DOM, no Date.now. Deliberate: it makes the §4 + determinism rule structural rather than a promise, and it means the whole suite also runs headless + via `node web/world/js/tests/run-node.mjs` (~1 s, no browser, no server). Tuning a storm curve + through a browser round trip is miserable. `weather.js` is the thin THREE adapter over it. + · Cost of that: `weather.core.js` carries its own copy of mulberry32 rather than importing + `contracts.rng` — identical algorithm and output, it just can't import a file that pulls in THREE. + `debris.js` and `skyfx.js` do use `contracts.rng`. Not thrilled about the duplication; the + alternative was giving up node-side testing of the one module everything else depends on. + · Asserts live in `js/tests/weather.selftest.js` as a plain case list; `c.test.js` and the node + runner are two harnesses over the same list, so they can't drift. + · `weather_demo.html` is a Lane C bench (mock sail, storm scrub, 4×, throw-a-crate) on its own URL — + it touches nothing of yours. Delete it whenever it stops earning its place. + · **Lane A:** a.test.js's 'gust telegraph always gives at least 1.2 s of warning' is now also + asserted against the real wind in c.test.js, per your note in the stub. Yours to drop when the + stub goes. + +[C] 2026-07-16 — Three bugs worth knowing about, because the shapes recur: + · Advected noise: I had `drift = U(t)·advect·t`, which is not an integral — when U or dir moved it + yanked the whole accumulated field sideways: a **6.8 m/s jump in one frame** at the southerly + change. Now integrated once at build time into a table. If you ever advect anything by time, + integrate it. + · Debris friction: `v *= 0.86` per frame while grounded is `0.86^60` per second — glue, not scrape. + It pinned a 9 kg crate at 0.7 m/s in a 19 m/s wind. Anything per-frame that should be per-second + needs dt. + · `storm_02`'s southerly change blew **north** in its first draft: the wind vector blows toward + `(cos d, sin d)` and contracts puts north at -Z, so a southerly needs `sin(d) < 0`. Worth a second + look at anything that reasons about wind direction. + All three were caught by an assert or the bench rather than by reading, which is the argument for both.