6bcc2540cf
12 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6bcc2540cf |
Sprint 11 gate 2: the job sheet — five nights become five jobs
DESIGN.md has opened with this since line 3 — "every callout is a different site, a different client" — and the game had the site and not the client. The forecast card is a job sheet now and the aftermath is an invoice. THE WEEK, once it was asked to name a client, mostly wrote itself: four nights are the same yard, so they're one client on retainer while they're away (the Hendersons, in Cairns from Friday — SPRINT11's own line). Night 3 is a different SITE, so it is necessarily a different CLIENT: a short-notice callout to a corner block you've never rigged, on the night the change comes early. That isn't flavour bolted on, it's what `site: site_02_corner_block` already meant, finally said out loud. BUDGET Y IS THE FEATURE, not the letterhead. DESIGN.md's brief is "client wants X, budget Y, forecast Z" and this card had Z alone. The fee has existed since Sprint 8 and the player has never seen it until the money was already spent — you chose what to rig without knowing what the job was worth, which is a reveal, not a decision. The sheet quotes base + garden + clean up front, and a test pins that a perfect night pays exactly what the sheet advertised: a job sheet that over-promises is the game lying on paper. THE CLEAN BONUS is the new money, and it's the one number in week.js that isn't measured. It gives the invoice three axes that mean three different things — you turned up, their garden lived, you didn't wreck the place — and it's NOT gated on winning, because a lost night where you broke nothing of theirs is a different fact and deserves its own row. It also makes the carport bite twice: 180 + 20 = 200, inside E's own "over ~250 is a silent game over" ceiling. Measured: it adds ~$100 to a competent week (385 -> 485), which makes BROKE_BELOW's known runaway worse. I did NOT pay for it by quietly trimming feeFor or gardenBonusMax — those carry measured evidence, and re-tuning someone else's evidence to fund my own feature is the move this repo keeps catching. If gate 3 says the bank runs away, this is the lever to cut first and it's data. TWO BUGS, BOTH CAUGHT BY LOOKING AT THE CARD, neither by a test: · "THE HENDERSONS" over "The Backyard — Your own place". The blurb and the letterhead called each other liars on screen. The blurb gave way: "your own backyard" is a leftover from when the game had one yard, and DESIGN.md never wanted it. The line's job survives — night 1 is the tutorial because you know this yard, now from having worked it for years rather than owning it. · C's `lead` is a 0..1 haze dial (confidence = 1 − lead), not "nights out". I read it as nights, passed 1, and the sheet advertised "forecast confidence 0%". Now 0.6, because C's own hail rule calls L > 0.55 too distant to promise ice, and tomorrow is exactly that. C: the slot is mine, the number is yours. Lane C's forecastLines(def, lead) finally has a caller above 0 — two sprints after they built it. Lane E: .letterhead / .brief / .jobsheet / .tomorrow are structure only and yours to style. selftest 304/0/0 (was 300). |
||
|
|
4aee055f2d |
Sprint 11 gate 1: three rulings — enum widened, carport priced, venturi reconciled
The corner block's seams, all of them A's to close. ENUM (D's flag). Anchor.type was a closed JSDoc enum 'house'|'tree'|'post', so site_02 typed its carport 'post' to fit — quietly enrolling it in the sail-post family that C's venturi and B's audit both read. Widened to E's own asset strings (carport, carport_post) and, more to the point, CHECKED: it was documentation for ten sprints, and documentation cannot fail. validateSite now rejects an unknown type by name. The ladder is untouched either way — D keyed it on `work`, the mechanism, which is exactly why that was right. CARPORT (E's proposal, adopted). $180, with E's reasoning intact: gnome 25, a night's budget 80, so 180 is 2.25 nights — a good week turned broke and felt for the rest of the run, without ending a strong one outright. Under ~120 it's a shrug, over ~250 a silent game over that teaches nothing. The number lives in site JSON, not the GLB: sites are data, and what a carport COSTS is this site's economy, not a property of the mesh. E's baked value stays as the fallback and the proposal. And it BILLS now. E shipped the trap and said it plainly — the anchors said collateral "carport" and nothing said what one cost, so nothing scored it. You could lose the worst steel in the game and pay for a $15 shackle. One broken corner is enough (the gnome needs two: it needs the sail to land on it; the carport doesn't need the sail at all), priced per structure so two beams is one carport gone, not $360. The wreck swaps on the same event — E built it to the same origin for exactly this. VENTURI (C's question). There was no disagreement to settle. weather.core aligns on |dot(wind, axis)| because a gap funnels either way through it, so an axis is a LINE: -1.08 + PI = 2.0616, and my shipped 2.1 was the same gap read from the other end. Took C's number because C MEASURED it off storm_03b and I eyeballed mine at authoring time; the 2.2 deg of my eyeballing was worth 0.44 m/s. gain left at C's 1.35 — that's a balance lever, theirs with B's audit. Also wired validateSiteWind at site load, C's ask from Sprint 10. selftest 300/0/0 (was 296). |
||
|
|
0f5d69b32d |
The second site: corner block loads from JSON; night 3 moves to it
SPRINT10 gate 1b. A night is now {storm, site}; the week hands over a yard per
night and night three is the corner block. Nights 1/2/4/5 are the backyard; the
plain-string night form still works and defaults to backyard_01.
site_02_corner_block.json: a smaller 24x16 yard with E's carport as the trap it
was built to be — beam anchors rating_hint 0.22, posts 0.30, both below the
house fascia's 0.35, collateral "carport". The site IS winnable off the honest
anchors (one tree east, four ground posts): I enumerated the honest quads at
authoring time and there are four in-band ones covering the bed, so the carport
is temptation not necessity. Full load audit is Lane B's tools/site_audit.
The carport's beams are keyed work:"bracket" — D's field, the MECHANISM not the
type. A bracket up a bare beam is the one anchor you can't fake from the ground,
so it needs the ladder; keyed on type it would fail open (a carport isn't a
house) and the ladder mechanic would silently vanish, which is exactly the audit
D ran. C's venturi list is in the JSON with two gap coordinates they asked for.
The world is rebuilt on a site change, not re-pointed: createPlayer captures the
world into its ground clamp, collider, ladder and broom (Lane D's modules, no
setters), so a clean rebuild is honest — and a site change only happens at a
forecast, never mid-storm. world.dispose() frees the old yard so nothing leaks.
The re-point of rig + session lives inside loadSiteInto, keyed on the rebuild
itself, after a caller-side version proved fragile.
Verified end to end through real phase transitions: play night 1, hit the
aftermath, and the forecast into night 3 rebuilds into the corner block —
carport present, 10 anchors (cb/cp/q/tr, not h/t/p), smaller bed, player rebuilt,
no scene leak. Then back to the backyard, 12 anchors restored. Selftest 289/0/0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
b98dcad13a |
Sites are data: extract backyard_01.json; world.js builds from it
SPRINT10 gate 1a, carried since Sprint 8. Everything that was a module constant
— bed rect, house line, tree and post placements, shed, gnome, sun angle, fence
sides — is now data/sites/backyard_01.json, and world.js is a builder.
The extraction is byte-identical, and that's checkable rather than claimable:
E's branch-anchor tripwires pin five positions to 6 dp, a.test's quad-band
asserts pin the whole rigging geometry (>=3 quads in 18-45 m2, full coverage
still >45 m2), D's ladder field and B's balance lines all pass unchanged.
287/0/0.
It's byte-identical by construction, not by luck: the site-derived values keep
the builder's original local names, so ~500 lines of geometry that was already
correct is untouched and only its SOURCE changed.
Two things earned their place:
Anchors carry D's `work: "cloth" | "bracket"` — the MECHANISM, not the type. A
post is tensioned from a cleat at its base and a tree strop is thrown, but a
bracket bolted up a bare wall is the one you cannot fake. Keyed on mechanism
because `type` is a closed enum ('house'|'tree'|'post') that site_02's carport
doesn't fit, and needsLadder keyed on type FAILS OPEN there — the ladder
silently vanishes and you re-rig a 2.6 m bracket standing on the grass.
p4's swept position table moved INTO the JSON's `_why`, beside the coordinate it
justifies. That table is the only thing stopping someone nudging the post toward
the bed and collapsing DESIGN.md's central tension; it belongs with the number,
not in a file the number left.
validateSite() fails loud: sites are content, and a missing bed silently becomes
NaN coverage while a missing anchor list silently becomes an unriggable site.
createWorld now requires a site and says so.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
391722970a |
Rule the pyrrhic win: WIN = hp >= 50, corners priced not gated (SPRINT9 dec 1)
The garden is the client's. The sail is yours. Saving the bed with a rig that tore itself apart is the job done expensively — not a failure. DESIGN.md doesn't call a cascade a loss, it calls it "a firework you paid for", and *paid for* is the point: the aftermath already bills the broken hardware, the collateral, and a week's fee you didn't earn cleanly. Gating the win on corners bills you twice for one night and then lies about it — the same species as the verdict that used to tell a 4/4 hold they'd skimped. B and D arrived here independently with the numbers, and B checked what it does NOT break: cheap rigs still lose (4x carabiner -> hp 38), rigs that miss the bed still lose (hp 36), $80 still cannot buy immunity. C's exhaustive sweep found the only winnable $80 line on the wild night lands exactly here — hp 52, 1 lost. The pyrrhic ending gets its own verdict, and it had to be checked FIRST: `lost >= 2` was unconditionally a cascade-and-a-loss, and under the new rule it can be the best night the game has. Two asserts pin both halves — garden saved + sail destroyed is a win that still names the weakest link; the same wreck WITHOUT the garden is still a plain cascade, because the garden is the whole job. Selftest 276 pass / 0 fail (Lane A 32 -> 34). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
52bdcd0a5e |
The week: five nights, one wallet, and an ending that tells the truth
SPRINT8 gate 1, carried since Sprint 6. week.js is a thin wrapper over the phase machine, not a new system: it answers three questions between rounds — which storm is tonight, how much money is there, and is it over. Zero THREE, pure data, so the ladder and the money are testable at no cost. The forecast card stops being a difficulty picker and becomes NIGHT n OF 5 with the ladder as pips, tonight's storm, and the bank. Aftermath itemises the settlement (fee / bonus / gear recovered / collateral / spent → bank), and E's dawn tint comes up on their 2.2s ease BEFORE the scoreboard — their note is right that you sell the survival first. E's diptych and copy are wired verbatim. Two economy decisions, both made because measuring the week exposed them: A lost night pays a FRACTION of the fee. Zero is unrecoverable and reads as punishment; full pay means failure only costs the broken shackle and the bank only ever climbs. Gear comes back at HALF — a full refund makes hardware free, so the only sink is breakage and "broke = game over" becomes unreachable. Half is also honest: a shackle that rode out a gale has been fatigued, which DESIGN.md already says. And the one that matters: REACHING night five is not SURVIVING it. My first draft said it was, and playing the week found it — a $20-carabiner player loses four gardens, never dips under the broke line because a cheap rig barely costs anything, and got handed "THE WEEK HELD — everything's still where you put it" over four dead gardens. The end card is the last thing this game says; it doesn't get to lie the way the aftermath verdict used to. Words now scale with gardens held: clean (5), scraped (3-4, E's own alternate copy), solvent (0-2, mine — "STILL TRADING · The books balanced. The garden didn't." E never wrote words for that ending because nobody knew the game could produce it). Two economy questions I could NOT settle from a harness and have documented rather than quietly tuned: a competent week runs the bank 80 → 350, so night five stops being a decision; and a floor-scraping week may never actually go broke. My bad-player model handed back a fixed $30 of gear whatever it spent, which flattered the bottom of the curve — the same class of mistake this repo keeps catching me in. Both want a human playing five nights. Selftest 270 pass; the single red is gate 0's balance line, which is B+D's this sprint and was already red on main. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
aac1030131 |
Gate 0: name the harness dispute's cause; make skips real (testkit)
THE CAUSE, and it is not a winner: balance.test.js built skyfx with no camera, and skyfx.step() opens `if (!camera) return;` — so the hail shadow grid was never rebuilt across all 5400 calls and every loadout was scored as though it had no sail. hp 36 IS the bare-bed number. Measured with the camera as the only variable: hailShadowOver(bed) peaks 0.000 vs 1.000, hp 36 vs 69. Neither harness was lying; one was flying a yard with no cloth in it. Camera + wind shelters added — the suite must drive what the game drives. MY ERROR, named: the hp-58 win was measured at tension 1.0 and I never declared it. At the shop's default 0.9 my own end-to-end also loses 2 corners. STILL OPEN: this suite says 2 corners lost, my end-to-end says 1 at tension 1.0. Ruled out by measurement, each worth <=0.02 kN: shelters, frozen vs live tree sway, the scripted repair. Next suspects named in the skip: session.commit() vs rigSail(), and main.js passing debris as rig.step's 4th arg (debris ADDS load, so it should break MORE here, not less — that inversion is the thread). And the find that explains how this survived a merge: Suite.test() ignored its return value, so `return "SKIPPED — ..."` was recorded as a PASS. The integrator's storm_02 skip was a fake pass; so was mine for storm_01; `skip: 0` in every report we ever printed was the tell and nobody read it, me included — I misread one of those fakes as my win reproducing. test() now honours the string, and rejects async fns outright (they hand back a Promise that cannot throw synchronously, passing forever while proving nothing). That guard immediately caught its own author: BOTH my wind-router tripwire tests were async and have asserted nothing for two sprints — including the sprint where I reported the tripwire "caught its first real omission". It had, in a hand-run probe, never in the suite. Awaits hoisted into run(); they asserted for the first time today, and pass. Selftest 261 pass / 0 fail / 2 honest skips. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
2af4662cd7 |
Add p4, the one close anchor — the wild night is now winnable (gate 1)
SPRINT6 gate 1's anchor lever, placed by measurement rather than by guess. The wild night had no winnable line for a geometric reason: every anchor near the bed (p1/p2/p3) is SOUTH of it, and nothing stands north short of the house 10 m away. So covering rigs had to span the yard and died, while rigs small enough to survive sat beside the bed rather than over it. p4 supplies the missing north-west corner. Its position is swept, not chosen. Against the smallest quad covering 90% of the bed: (-2.2,-1.2) → 44.4 m², (-3.2,-1.2) → 48.6, (-3.2,-2.0) → 51.7, beyond z=-4 → no effect at all. Pulling it inward to 44.4 would put full coverage INSIDE the survivable band and collapse DESIGN.md's central tension — covering the bed has to cost risk. a.test.js's >45 m² floor is what shouts if anyone moves it in. With the 8° rake the top anchor lands at (-3.72, -1.40), and full coverage costs 51.6 m² against 63.5 before: a real new option, 19% cheaper, still bigger than the 23-38 m² rigs that survive unaided. Measured through the real $80 shop: t2+p3+p4+t2b, four shackles and a spare ($75), ends hp 58 with 1 corner lost — a WIN, where before every covering rig lost 2-3 corners and ended at 36%. Hail damage falls 51 → 34 HP versus an uncovered rig, so decision 13 is visibly doing its job. Selftest 244/0/0; both yard tensions still green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
c3967d98e8 |
Verdicts read the actual failure mode (SPRINT6 gate 1)
The aftermath was lying. Any run under 50 hp read "the rain found what you skimped on" — including Lane B's twisted quad that holds 4/4 corners and skimps on nothing. The verdict is the game's entire feedback channel: DESIGN.md wants every disaster to replay as "…the shackle, I knew about the shackle", and blaming the wrong thing teaches the opposite of the lesson the storm just spent 90 seconds giving. The garden now keeps its damage split by cause, because the caller was pre-summing hail and rain into one number and that sum is exactly the information the verdict needs and can never recover afterwards. A garden that only knows it went 100 → 39 cannot tell a player whether their hardware let go or their perfectly-held sail simply wasn't over the bed — opposite mistakes, opposite fixes. verdictFor() is pure and lifted to module scope so it can be asserted directly. It picks from modes the run can PROVE: cascade (naming the weakest link as going first, not whichever was listed first), single corner, uncovered, rain, broomed, ponded, clean. The integrator's exact case now reads "THE GARDEN IS GONE — and every corner held. The hail fell where your sail wasn't." Aftermath also gains decision 13's headline: "51 HP to hail, 13 to rain". Verified live on the reported scenario, not just in asserts. Selftest 244/0/0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
eb6986d1a0 |
Tripwire the wind router; take Lane C's gardenExposure helper
The router is a hand-maintained delegation list and it has already cost us once: when Lane C added rainMmPerHour/rainDepthMm, it didn't forward them, and nothing went red — every suite holds a real wind, only the GAME holds the router. Lane B's ponding would have passed every assert it had and done nothing in the yard; the integrator caught it by hand at merge. The class matters more than the instance. Sprint 5's headline system is Lane C's hailAt(), and decision 13 hangs the whole garden score off it — the same silent swallow would make rigging look irrelevant to the garden all over again, which is the exact thing the sprint exists to fix. The assert diffs the router against a real wind and names what's missing. Verified it fires rather than just passing: rebuilt Sprint 4's pre-fix router and it reports exactly rainMmPerHour, rainDepthMm. Garden drain now goes through sky.gardenExposure() — same arithmetic, but it's Lane C's term to own, and decision 13 extends precisely this shape, so hail lands as one added term rather than a rewrite. Numbers unmoved: good rig 53%. Also re-measured Sprint 4's headline finding against a TRUE control (fresh boot, nothing ever rigged, shadow over bed 0.000) because my original control left the previous round's sail in the shadow grid: no-sail is 48.5% vs a good rig's 53%. The finding stands, and decision 13's premise with it. Selftest 209/0/0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
0cabb19dc7 |
Anchor rework + house/tree GLBs: give the yard a real choice (decisions 2 & 6)
Posts pulled in to (-4.5,5.5)/(4.0,6.0) with p3 at (0,7), and dress() now swaps Lane E's house_yardside and both gum trees over the graybox, adopting their baked anchors rather than my constants (decision 6). E's fascia sits at x=-3..3, not my guessed -5..5 — narrowing the house span by 4 m is a real part of why small quads exist at all. Every anchor now carries E's rating_hint: fascia 0.35 with collateral "gutter" (they encoded DESIGN.md's "the fascia board is a lie" straight into the asset), tree branches descending 1.0/0.88/0.76 from fork to thin limb. branch_anchor_01 keeps the t1/t2 ids so nothing referencing them breaks; the rest are added. The yard went from 7 anchors offering nothing under 110 m² to 11 offering 34 quads in the 18-45 m² band, 8 of which shade a quarter of the bed or more. Measured through the same storm_02: the big house-to-post span loses its carabiner at t=3.7 s and cascades to 2/4, while a 37.7 m² tree-to-post rig at 0.85 tension survives all 90 s intact and shades 58% of the bed. That is DESIGN.md's thesis finally standing up in the yard rather than in a doc. Lane B's "cascade at t=0.4 s from pre-tension alone" is gone: calm peaks are now 634 N (big) and 200 N (small) against a 1200 N carabiner, and no rig breaks before the storm starts. Two asserts pin it, because both halves are easy to lose by accident: at least 3 riggable quads in 18-45 m² must shade the bed, AND full bed coverage must stay above 45 m² — if a small quad ever covers the whole bed, the rigging puzzle has no wrong answers left. Selftest 172/0/0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
1049dece24 |
Add selftest harness with one suite per lane
selftest.html only imports; each lane owns js/tests/<letter>.test.js. Five lanes sharing one selftest.html would be the single guaranteed merge conflict in the repo, so the stubs are pre-created with each lane's PLAN3D asserts written into the header. No rAF anywhere: it's throttled in a hidden tab, so a rAF-driven selftest would pass only while you watch it. fixedLoop() drives time instead. Lane A's suite covers the §5-A acceptance criteria, including "camera never clips through house" — stated as the underlying invariant (no solid between the player's head and the camera) so it survives Lane E swapping the house GLB. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |