diff --git a/README.md b/README.md index ad07813..2aae89b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ -# SHADES — rig it, then survive the night +# HARD YARDS — rig it, then survive the night + +*(repo/product history: the project was codenamed SHADES through Sprint ~8; +the shipped game is HARD YARDS. Internal docs and the gitea repo name still +say shades — that's the same thing, not a second game.)* Geometry/tower-defense: you're a small person rigging shade sails against Australian storms. three.js r175 (vendored, no build step) + a zero-dep diff --git a/THREADS.md b/THREADS.md index 20f0066..b7fb916 100644 --- a/THREADS.md +++ b/THREADS.md @@ -6529,3 +6529,23 @@ anchors are your GLB), but the tooling is now waiting, not TODO. is per spec and I'd rather be slow than wrong, but on a yard you're iterating cold that is a real wait. If it bites, say so — the `FLY_CAP` (12) and the sweep are both knobs I can turn, and I'd rather hear it from someone using it in anger than guess. + +[I] 2026-07-18 — 🔍 **FRESH-EYES REVIEW (John's ask, run by Claude in the integration + checkout) + SIX FIXES LANDED ON MAIN.** Full read of canon + code + a played night 1 + + the whole suite. Selftest **409/0/0** (406 baseline + 3 new, every new assert + mutation-checked red-then-green in one sitting). The findings, fixed: + + | # | finding | fix | + |---|---|---| + | 1 | `sail.selftest.js` dumpPond assert was a TAUTOLOGY — `r_prev` returned its own argument, so `|dumped − dumped| < 1e-9` could not fail no matter what dumpPond returned | asserts `dumped === pondMass-before` exactly, vacuity-guarded (`before > 1`); `r_prev` deleted. Third instance of the looks-wired-isn't disease, this time in Lane B's own suite | + | 2 | **divergence never broke on the production path** — `load > rating` is false for NaN, so a diverged corner held FOREVER while NaN ate the spring network; `watchDivergence` (the throwing tripwire) is test-only and main.js never sets it | `_checkFailure` now breaks a non-finite corner instantly, `reason:'divergence'` on the event, load zeroed so the HUD reads a break, not NaN. New assert runs with watchDivergence OFF — the shipped path, poisoned node, 4/4 corners let go. Mutation: guard off → 0 breaks, red | + | 3 | pond per-node cap only enforced on the rain-add — once rain stopped, downhill consolidation could stack a basin node past `POND_MAX_KG_M2` with nothing left to trim it | clamp moved to POST-FLOW (runs every step, rain or not; over-cap water sheets off, not conserved). New assert: 2000 kg injected on the settled belly → 280 kg one step later, no tear (the no-pondDump guard proves it was the CLAMP, not the belly tear). Mutation: clamp off → 1999 kg, red | + | 4 | `debris.js` still carried the SPRINT2-era duck-typed `sail.nodes` seam + `applyToSail` — dead since decision 5 chose option b (`sail._applyDebris` reads `debris.pieces`), and a live DOUBLE-APPLY the day anyone exposed `.nodes` | deleted, with a comment saying where the real contact model lives. main.js may keep passing `sail:` — accepted and ignored, documented | + | 5 | **the fabric bet was unreachable** — `FABRIC`/`setFabric()` shipped SPRINT6, the sim honoured porosity all along, rigging.js:276 calls cloth "half of the wild night's only winnable line"… and no key, click or HUD line ever reached it. Every rig ever played was DEFAULT_FABRIC | **F cycles cloth/membrane in prep.** Panel line 2 shows the fabric, help line advertises F, splash teaches it ("cloth breathes, membrane stops the rain"), blurb announced on switch. Session-level assert: cycle round-trips, $0 both ways, summary carries it, garbage refused. Verified in the running game | + | 6 | main.js said `panel:false` in the comment and passed `panel:true` in the call, two sprints running | comment now tells the truth (B's panel IS the only prep UI; hud.js never took it over). README also retitled — the game shipped as HARD YARDS, the repo still said SHADES | + + **Follow-ups pushed to SPRINT15, not snuck in here:** node-position sanitation after a + divergence break (the freed corner integrates from NaN forces — break is detectable now, + cloth render after one is still ugly; B should own the heal), a job-sheet surface for the + fabric bet (the key exists, the FORECAST should argue with it — A), and membrane's + balance once rain's drain weight moves (rigging.js's own comment says where to start). diff --git a/web/world/js/main.js b/web/world/js/main.js index f05994a..c1d763d 100644 --- a/web/world/js/main.js +++ b/web/world/js/main.js @@ -740,8 +740,13 @@ export async function boot(opts = {}) { getSky: () => sky, }); - // Lane B's picking adapter. `panel:false` — their built-in panel is a fine - // bench, but hud.js owns the screen now, so the two shouldn't both draw. + // Lane B's picking adapter. `panel:true` is DELIBERATE and the old comment + // here ("panel:false — hud.js owns the screen") described an intent that + // never happened: hud.js never grew a prep table, so B's panel is the only + // prep UI there is. If hud.js ever takes prep over, flip this to false in + // the same commit — two panels drawing at once is the bug the old comment + // was worried about, and it was half right: the flag and the comment + // disagreed for two sprints and nobody could tell which one was the plan. rigging = await createRiggingUI({ scene, camera: cameraRig.object,