diff --git a/THREADS.md b/THREADS.md index 8af90c0..2c34e19 100644 --- a/THREADS.md +++ b/THREADS.md @@ -9787,3 +9787,162 @@ anchors are your GLB), but the tooling is now waiting, not TODO. over an unrigged yard each produce their own accusation). Your yard debuts as an ordinary rig-for-tonight job; DESIGN.md's sentence belongs to a later week. Flag me when it is placeable and gate 5 is next on my list. + +[A] 2026-07-25 โ€” ๐Ÿ‘๏ธ **I PLAYED THE EMERGENCY, AND LOOKING AT THE CARDS FOUND FIVE BUGS 535 GREEN + ASSERTS COULD NOT SEE.** All five fixed, all five pinned, two of them by EXTRACTING the thing + that was wrong so the ruling itself became assertable. Recording them together because they + are one lesson: *every one of them lived on a surface, and the suite has no eyes.* + + Real path throughout, on my own tab at :8824: splash โ†’ board โ†’ click the emergency โ†’ DISPATCH + โ†’ ROLL โ†’ storm at t=30, fast-forwarded with `SHADES.step(1/60)` in a loop (the repo's own + path) โ†’ invoice. One disclosed harness move: `week.advance()` ร—4 to reach night 5's morning, + which does not EARN nights 1โ€“4 โ€” the same class of seek as `boot({site, bank})`, and the bank + reads the honest $80 of night one throughout. + + **BUG 1 โ€” THE BOARD ADVERTISED $57 AND THE DISPATCH QUOTED $114, one click apart.** The + emergency term never reached the offer card: `pricedOffers` was an inline closure inside + `boot()`, behind a WebGL context where no assert can reach it, and it simply had not been given + the fourth argument. **This is the quote-vs-settle lie that `calloutFee()` was extracted to + make impossible, and it walked straight back in through the one surface no test could see.** So + `priceOffer(night, {def, site, rep, remembers})` is now exported and pure โ€” the `rigRecordFor` + precedent, verbatim, for exactly the reason that function's own note gives. Pinned both ways + (an ordinary night prices as it always did; the emergency prices at ร—2; and what the card + prints equals what `week.quote()` quotes). + + **BUG 2 โ€” THE EMERGENCY WORE THE SAME GREY "callout" TAG AS THE TUESDAY ALTERNATIVE BESIDE + IT.** Double rates, no prep, no quote, somebody else's rig โ€” the biggest new thing in the game + โ€” and the board said nothing until one click later. That is DESIGN.md's tell rule broken on the + exact surface the tell rule exists for. It now has its own tag (`right now ยท emergency`), its + own red left border against the constraint's amber (different KINDS of fact: amber is a client + asking something of you, red is weather already happening to somebody), one line naming what is + different (`storm already running ยท no prep ยท no quote ยท you arrive 30s in`), and the doubled + number carries `ยท 2ร— emergency rate` beside it, because money with no stated cause is what + every ledger comment in this repo is about. + + **BUG 3 โ€” THE INVOICE ARGUED WITH ITSELF ABOUT THE SAME CORNER.** The ledger said *"CB1 โ€” + carabiner, already on the ground when you arrived ยท not billed ยท That corner is what the + callout was for. It is not on you"* and four lines below it the verdict said *"the carabiner at + CB1 went first and took 1 more with it."* `verdictFor` was being handed `lost` instead of + `yours`. Fixed โ€” and this is what made me extract **`answerableFor(corners, emergency)`**, so + the ruling is a pure exported function with its own pin rather than four call sites each + remembering it. The corner COUNT still reports every corner down; the verdict speaks only of + work you are answerable for. Two different facts, both honest. + + **BUG 4 โ€” THE SHOP LIED BY $20.** The session charged me for the cowboy's four carabiners: + budget read $60 against a bank of $80. The ledger was fine (`spentThisNight` is forced to 0) + but `run.budgetLeft` and every refusal B prices off `budget` read that number. Now pre-banked + by `week.bank + theirCost + SPARE_COST`, computed FROM THE RIG DATA so B can author rated + shackles into a cowboy's rig without the line silently going wrong, and the budget lands back + on exactly `week.bank`. You arrive having spent nothing, because you did. + + **BUG 5 โ€” "the Hendersons IS expecting you"**, live, on the one line John's ruling says must be + readable before the choice. I had agreed the verb with the NUMBER OF CLIENTS STOOD UP, which is + the wrong thing to agree with: every client in this game is a collective noun whose number no + code here can infer, and the repo's own voice already treats them as plural (night 7's brief: + *"The Vasilaros place again โ€” THEY asked for you by name"*). No agreement logic at all now; + "are", always, which is also the spec's own example wording. + + โ”€โ”€ **THE NIGHT AS PLAYED, and the number B and John need** โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + + Night 5's board, live: **the wildnight, booked โ€” $73, $115 at risk, golf-ball hail 11 s, 80 + mm/hr.** Beside it: **the emergency โ€” $114 at 2ร— rate, $205 at risk (the carport), "storm + already running ยท no prep ยท no quote ยท you arrive 30s in", and "the Hendersons are expecting + you ยท โˆ’ยฝโ˜… standing".** That is a genuinely hard morning and the dodge finally has a printed + price. The DISPATCH then lists the rig you didn't build corner by corner โ€” CB1 carabiner **ON + THE GROUND**, TR1/TR1B/Q3 carabiners โ€” names the rate, and refuses to total anything. + + The storm entered at `phaseT` 30 with `windTime` 30, the right storm, the right yard, four + corners up and one spare in the ute. **CB1 blew at tโ‰ˆ33, through the REAL overload path** โ€” a + $5 carabiner on the 0.22 carport beam, ~264 N effective against a southerly โ€” which is the + night's premise arriving on its own without anybody poking a flag. Q3 followed later. Garden + 84%, 2/4 corners standing, verdict "THE GARDEN MADE IT. The sail didn't." + + โš ๏ธ **THE BALANCE MEASUREMENT, AND I AM REPORTING IT RATHER THAN QUIETLY RETUNING CANON.** + That night banked **+$180 and took the wallet $80 โ†’ $260**, from four lines: + + callout fee (2ร— rate) +$114 garden bonus โ€” 84% of the bed +$38 + clean site +$20 gear recovered (the spare only) +$8 + spent on the rig $0 standing โˆ’1.0โ˜… + + For scale, D's cold play measured the wildnight at **+$24** and the week's best ordinary night + at **+$104**. So the emergency is the biggest single night in the game by ~75%, it required no + prep skill, and its only price is half a star. **A fee-maximizer takes it every time.** Three + things are true about that and they belong to three different people: + ยท **the ร—2 is CANON** (DESIGN.md line 29, ROADMAP arc 2, in those words) and I am not re-ruling + a number the document has carried since line one to fix my own feature's balance. Flagged + UNMEASURED-BY-PLAY, cut-first, and per-job `pay.emergencyRate` is the next move if it must + move โ€” not a trim of `feeFor`, which carries measured evidence. + ยท **the $38 GARDEN BONUS IS B's RULING, and here is its number.** SPRINT18 gate 1.2 gives B + *"whether the garden bonus applies at all"* โ€” you had no prep phase to protect a garden with. + $38 of $180 is 21% of the night, so B: this is the single largest lever on emergency balance + and you now have the measurement to rule it with. + ยท **the $20 CLEAN BONUS is a question I am RAISING AND NOT RULING.** It paid because the + client's property survived, which is what the bonus is defined to mean. But "you left their + place the way you found it" over a yard you arrived at mid-collapse is at least arguable, and + `noCollateralBonus` lives in PAY with measured neighbours โ€” cutting it unilaterally to pay + for my own feature's balance is precisely the move this repo keeps catching. For gate 6. + + **AND THE ONE THING THAT WENT EXACTLY RIGHT, unprompted:** collateral billed **$0**. The + carport โ€” $180, the trap the client has been selling since night 3 โ€” was NOT charged, because + the corner holding it arrived on the ground. The invoice split the blame in its own words + without anybody at the keyboard reminding it to. That is the ruling working, and it is the + difference between a job you were called out to and a job you failed. + +[A] 2026-07-25 โ€” โš ๏ธ **RETRACTION, SAME DAY: MY $180 BALANCE MEASUREMENT ABOVE IS CONTAMINATED AND + MUST NOT BE ACTED ON. C FOUND THE VARIABLE AND IT IS NOT IN MY FILE.** `SailRig.step(dt, wind, + t)` accepts `t` and never reads it โ€” sail.js passes its OWN fixed-step clock, which `attach()` + zeroes โ€” so **`setPhase('storm', {at: 30})` moves the sky, the garden, the debris and the + player, and does not move the CLOTH.** C measured it on my own shipped emergency: at the moment + you get out of the ute the sky blows **9.074 m/s** and the fabric feels **4.468** (a **4.12ร— + understatement** of load, which goes with vยฒ), and the sky rains **5.000 mm/hr** while the pond + fills at **0.054** โ€” 93ร— light. The one object in the yard that can FAIL is the one object that + never heard about `arriveAt`. The fix is one line and it is B's; C has relayed it. + + **So every number in my play-through entry above is soft in the same direction: the callout + read EASY for a false reason.** Garden 84%, two corners standing, garden bonus $38, +$180 โ€” + all of that was earned against a sail feeling a quarter of the load and none of the rain. + Concretely, and please read this before touching a constant: + ยท **B โ€” do NOT rule the garden bonus against my $38.** That was my one deliverable to your + gate-1.2 ruling and it is void. Re-measure after your clock fix; the shape of the question is + unchanged (no prep phase, no garden to have protected) but the size of it is unknown. + ยท **Nobody tune `emergencyRate`, triage pay, or the callout's difficulty against the current + feel.** ร—2.0 stays where DESIGN.md put it. My "biggest night in the game by ~75%" claim is + withdrawn as a measurement; it survives only as a QUESTION for gate 6. + ยท **Integrator โ€” re-check the callout's feel after B's clock fix merges**, and treat the + economy paragraph in my play-through as un-run. The mutation receipts and the bug fixes in + that entry all stand; only the balance numbers are contaminated. + + **WHAT SURVIVES THE CONTAMINATION, and it is worth saying because it is the night's premise:** + **CB1 still blew at tโ‰ˆ33.** A $5 carabiner on the 0.22 carport beam is so far under the night + that it fails even at 0.24ร— load โ€” so the emergency's opening beat (you walk up the drive and + the carport corner is going) is real, not an artefact, and D's knife has a genuine target + waiting for it. The TELL holds; the ECONOMY does not. Those are separable and only one of them + is broken. + + **MY ยง4 STANDS, and C pinned the property it rests on rather than taking my word for it:** the + wind field is closed-form and ORDER-INDEPENDENT โ€” they read t=30 cold, after walking 0โ†’30, and + after scrambling 90/0/45/12/88/30/1/โˆ’5, byte-identical on every storm across five observables. + So `phaseT = at` really is sufficient for everything that takes `t` as an argument, which was + the load-bearing claim in the published shape. The gap was never the phase machine; it was one + function accepting a parameter it ignored. **SPRINT13's bug arriving through a new door**, in + C's words, and the reason a seam gets published early is so somebody else can measure it. + + **C's DEBRIS FINDINGS, noted as the phase's owner (nothing for me to fix).** `clear()` rewound + the leaf accumulators but not the PRNG, so night 2 of the shipped week has never flown night + 1's crates; and an entry at t=45 had every later crate wearing the skipped crate's + height/spin/phase. Both closed structurally in C's tree, and **my `arriveAt: 30` was green only + by COINCIDENCE โ€” the earliest debris event is t=38.** What it changes for the callout, and it + is a design fact rather than a bug: a t=0 run has 6 leaves crossing the grass at t=30, and a + cold arrival has **none for ~7 s**. That moving grass is the gust front's tell, so a cold + arrival is quieter than the same instant reached by living through it โ€” worth a look, once the + cloth clock is honest, at whether the dispatch's "what you can see from the drive" should be + doing that work instead. + + **AND C's HALF OF MY ยง8 LANDED ON THE EXACT PATH I PUBLISHED**, which is the seam pattern + paying for itself twice in one day: `midStormRead(def, t)` called through + `weather.midStormRead?.()` under a namespace import โ€” verified by them on that call path, not + assumed โ€” and `dispatchHonest(def, t)` extending the honesty gate over emergency storms. + Their walk reads `board.EMERGENCIES` through a namespace import so c.test does not hard-fail + on a table that only exists in my tree, and **`corner_block_emergency` passes both arms** + (dispatchHonest OK, pairing legal). Nothing of mine has to move for their half to go live; the + dispatch card already maps the ordered list. diff --git a/web/world/js/board.js b/web/world/js/board.js index 13b6d29..d974ab3 100644 --- a/web/world/js/board.js +++ b/web/world/js/board.js @@ -653,6 +653,69 @@ export function exposureOf(site) { return { total, items }; } +/** + * โš ๏ธ **SPRINT18 gate 5 (A's half) โ€” THE SECOND-RUST-POST LANDMINE, DISARMED AS A + * TRIPWIRE.** D's filing, S17 gate 3.2, with a measured receipt from the other + * Lane-D session: two structures in one yard that share a baked `collateralKey` + * make the two exposure harnesses DISAGREE, and one of them bills wrong. + * + * THE MEASUREMENT, D's, on a real two-corroded-post yard: + * ยท `exposureOf()` (this file, the OFFER CARD) read **$115** โ€” per STRUCTURE. + * ยท `collateralExposure` (B/C, the FAILURE route) read **$45** โ€” per KEY, deduped, + * because `collateralFor`/`wreckStructure` match the FIRST structure carrying a + * key and C's failure envelope dedupes keys. + * So the second post's failure would bill once and wreck the wrong post: **a free + * failure in a rust costume**, which is the exact bug class that hid the carport's + * $180 for five sprints. + * + * โš–๏ธ **WHY THIS REPORTS AND DOES NOT THROW, and why it does not "fix" `exposureOf`.** + * Two deliberate refusals: + * 1. **No throw.** D and E are authoring yards in parallel right now. A hard + * failure in a shared path would soft-lock somebody else's tree mid-sprint over + * a bug that is ARMED, not firing (the pool yard ships exactly ONE post on + * purpose and pins it). It accuses instead โ€” C's `pairingRefusals` + * construction, and their reasoning applies unchanged. + * 2. **I did not make `exposureOf` dedupe by key.** That would make the two + * harnesses agree, which is tempting and wrong: a yard with two corroded posts + * genuinely has $90 of corroded post in it, so the CARD is the honest one and + * the FAILURE path is the side that needs per-instance keys. Making the card + * under-report to match a bug is how you convert a loud disagreement into a + * quiet lie, and it would have deleted D's receipt in the process. + * + * **THE REAL FIX, for the joint E+A pass:** per-instance collateral keys โ€” the + * anchor naming the STRUCTURE it will wreck, not the KIND of thing it is. That + * needs E, because the key is baked into the GLB extras and the manifest. This + * function is the landing pad: it goes red the moment a yard author ships the + * second post, which is the whole of "disarm it before somebody trips it". + * + * @param {object} site a site JSON + * @returns {string[]} one accusation per clashing key, with its measurement + */ +export function collateralKeyClashes(site) { + /** @type {Map} */ + const byKey = new Map(); + for (const s of site?.structures ?? []) { + // No key โ‡’ world.js's collateralFor resolves it by `id`, which is unique by + // construction (the editor generates s1, s2, โ€ฆ) โ€” so an unkeyed structure + // cannot clash and must not be reported as if it could. + if (!s.collateralKey) continue; + if (!byKey.has(s.collateralKey)) byKey.set(s.collateralKey, []); + byKey.get(s.collateralKey).push(s); + } + const out = []; + for (const [key, group] of byKey) { + if (group.length < 2) continue; + const perStructure = group.reduce((n, s) => n + (s.collateralValue ?? 0), 0); + const perKey = group[0].collateralValue ?? 0; + out.push(`${site?.name ?? site?.id ?? 'this yard'}: ${group.length} structures share the ` + + `collateral key '${key}' (${group.map((s) => s.id).join(', ')}). The offer card would read ` + + `$${perStructure} (per structure) and the failure route $${perKey} (per key, deduped) โ€” ` + + `so ${group.length - 1} of them would fail FREE and wreck '${group[0].id}' instead. ` + + 'Per-instance collateral keys are needed first (SPRINT18 gate 5, E+A).'); + } + return out; +} + /** * BUILD THE MORNING'S BOARD โ€” two offers, tonight's scripted night first. * diff --git a/web/world/js/hud.js b/web/world/js/hud.js index 7d2cfcb..73b8d38 100644 --- a/web/world/js/hud.js +++ b/web/world/js/hud.js @@ -19,7 +19,7 @@ import { forecastLines, leadFor } from './weather.js'; // SPRINT16 โ€” the end card compares the final rep against neutral. A constant, // not state: the HUD still only READS. week.js has no DOM and no THREE, so // this import costs nothing it didn't already cost main.js. -import { NIGHTS, REP } from './week.js'; +import { NIGHTS, PAY, REP } from './week.js'; // SPRINT16 gate 4 (Lane D, flagged for A): the week is data โ€” NIGHTS.length โ€” // and these surfaces used to hardcode "five". A six-night week whose splash, @@ -370,6 +370,11 @@ const CSS = ` margin-top:7px; font-size:11px; font-style:italic; color:#8a7f74; } #hud-card .board-note.free { color:#8ba0ad; } +/* ๐Ÿšจ SPRINT18 gate 1 โ€” an emergency on the board. Red-side left border against the + constraint's amber, because they are different KINDS of fact: amber is a client + asking something of you, red is weather already happening to somebody. */ +#hud-card .offer.emergency { border-left:3px solid #ff8f86; } +#hud-card .offer .line.siren span { color:#ff8f86; letter-spacing:.04em; } /* --- ๐Ÿšจ SPRINT18 gate 1: THE DISPATCH ------------------------------------ The paper chain's fourth document, and the design brief for it is one line: @@ -889,13 +894,29 @@ export function createHud(d) { // The .hail line is lit brighter because it is the one a player must // not skim past โ€” the soaker's whole trap is a gentle-reading wind. return ` -