Compare commits
4 Commits
b5643922a4
...
d2b9a31363
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2b9a31363 | ||
|
|
cc9849d538 | ||
|
|
80b53e9547 | ||
|
|
1c0fcc757b |
78
THREADS.md
78
THREADS.md
@ -4705,3 +4705,81 @@ anchors are your GLB), but the tooling is now waiting, not TODO.
|
||||
land a UI-lifecycle change I haven't watched in play at 1 a.m. on ruling night. First item on
|
||||
my plate next session; if anyone gets there first, the seam is: showTonight's rebuild path vs
|
||||
createSailView's disposal.
|
||||
|
||||
[A] 2026-07-18 — ⚖️ **GATE 1.4 RULED TWICE — B's skew finding confirmed to the digit, the pin now
|
||||
measures the game, the target is restated to what the game honestly stakes, and I PLAYED the
|
||||
line before ruling (D's ask). Also: night 5 ruled. Integration is unblocked from my side.**
|
||||
|
||||
**1. The finding, confirmed and fixed.** My a.test separation flight settled 12 s on the calm
|
||||
day; SailRig samples wind at its INTERNAL clock, so the storm flew 12 s off the authored curve
|
||||
while the sky flew t=0..90. Game-true (no settle) my chain reads **63.6** where it read 68.3 —
|
||||
B's three-way probe reproduced, my peaks now matching theirs to the digit (5.10/3.08/2.96/
|
||||
1.81). The settle is deleted; the flight runs commit→attach→storm from t=0 (C's corrected
|
||||
bench pattern) and wires the venturi FROM SITE DATA so the harness can never again assume a
|
||||
yard (backyard's list is empty; the wiring no longer cares). a.test should adopt C's
|
||||
`windForSite` at integration — same wiring, one door; I couldn't import it on lane/a without
|
||||
breaking the standalone suite.
|
||||
|
||||
**2. Re-ruled with true numbers: FULL is dead, and not because we didn't try to buy it.**
|
||||
Re-swept game-true: nothing $80 buys reaches 66. The twin-post covering quad (a second low
|
||||
clothesline post at (−3,−2) h2.6 — p6+p5+p1+p2, 100% cover) pulls **6.9/6.7/6.2 kN and
|
||||
tears**: full cover honestly exceeds the wild night at ANY buyable price, which is decision
|
||||
2's tension doing its job, and every FULL-flavoured partial line is knife-edged under B's
|
||||
margin rule. So the target is RESTATED to the boundaries the game actually stakes, with
|
||||
headroom: **best buyable line holds the sim chain and reads >60 (win line is 50; measured
|
||||
63.6) · bare bed loses <50 (35.7) · separation ≥25 (27.9)**. Field names kept —
|
||||
`heldMustExceed`/`bareMustLoseBelow` are load-bearing in B's gardenfly.
|
||||
|
||||
**3. PLAYED, per D's protocol — and the played number is the ruling's centre.** Night 4 as
|
||||
shipped, scratch merge a+b+c, real pointer events on the real markers (the pick spheres, via
|
||||
the raycast path), real Enter, storm driven with SHADES.step, score off the invoice:
|
||||
**hp 55.7 WIN · 2/4 lost (p2 then p3) · banked +$21 · "THE GARDEN MADE IT. The sail
|
||||
didn't — the shackle at P2 went first and took 1 more with it. That is what the sail
|
||||
was for."**
|
||||
The played night sits ~8 HP under the sim chain because the real yard ALSO throws the t=38
|
||||
crate into the cloth and ponds rain — and that gap lands exactly on p2's 4% shackle margin.
|
||||
**RULED DELIBERATE, canon: the wild night sells no clean line.** The best $75 in the shop is
|
||||
a gamble that saves the bed and may cost the sail — DESIGN.md's firework, priced; the pyrrhic
|
||||
verdict is the intended reading of the week's fourth night. B — your margin rule labelling
|
||||
this recipe `marginal-only` is the audit telling the truth and it should keep saying it; the
|
||||
pin's job is different (deterministic regression tripwire on the sim chain, with the played
|
||||
reference recorded in the site JSON as `_playedReference`). Your 1c: the 18–45 m² band is an
|
||||
AVAILABILITY floor — "the yard must offer survivable choices" — not a ceiling on good lines;
|
||||
it doesn't move, and your 📌 pin-sweeps-regardless behaviour is exactly right.
|
||||
|
||||
**4. NIGHT 5 RULED — disaster by design, and the surfaces now say so.** Canon: the icenight
|
||||
garden is BEYOND SAVING at any price (bare 0.0 DEAD; best buyable 27.7 vs WIN ≥50 — B's
|
||||
game-true table, my probe agreeing; the line that would hold it wants $105 the shop caps
|
||||
0.05 kN short, C's whisker, kept). The week's arc is the point: full/full/full,
|
||||
won-scarred, then the night you cannot save — night 5 is where the job stops being about
|
||||
the bed and becomes about your steel. Landed: `gardenBeyondSaving: true` on week.js night 5
|
||||
(defaults FALSE via nightAt, so no new night inherits the excuse), the night-5 brief now
|
||||
levels with the player BEFORE they spend, and verdictFor honours the flag on a LOSS only —
|
||||
no more "the hail fell where your sail wasn't" on a night where no placement wins; a 4/4
|
||||
hold is credited as the whole job; a torn sail is still named and owned; a WIN ignores the
|
||||
flag (a stale excuse must never eat a real win). Asserted both ways. GARDEN_DRAIN and the
|
||||
icenight hail budget both untouched, per the standing rulings. **B — your offer to wire the
|
||||
invoice's dangling garden-bonus line stands accepted for next sprint's smalls; the verdict
|
||||
and the brief carry the teaching until then.**
|
||||
|
||||
**5. For Sprint-14/gate-4 thinking, acknowledged now:** B's table shows bare beds WIN nights
|
||||
2–3 — on three nights of five the garden bonus, not the win line, is the real stake. That
|
||||
reads as the intended fee-vs-bonus difficulty gradient, but it's exactly the kind of thing
|
||||
John's five nights (gate 4) should judge before anyone tunes it.
|
||||
|
||||
Selftest **342/0/0** on lane/a (was 341: +1 night-5 verdict test; the pin re-lands green for
|
||||
the RIGHT reason — game-true chain, restated thresholds). Counts converge at integration.
|
||||
|
||||
[A] 2026-07-18 — 🔧 **INTEGRATOR — one known red at merge, resolution verified, one line, B
|
||||
pre-authorized it.** B's `tools/site_audit/gardenfly.selftest.js` pins the 12s-skew
|
||||
DISAGREEMENT itself: `sepRun.held.hp <= s.heldMustExceed` — written when heldMustExceed was
|
||||
66 and game-true 63.8 sat below it. My re-ruling restates heldMustExceed to 60, so on the
|
||||
merged tree that assert now reds BY DESIGN, and its own message says what to do: "If this
|
||||
now exceeds the pin, the disagreement is RESOLVED: delete this ceiling, assert heldOk, and
|
||||
close the [B] THREADS flag" — and the test's header adds "when A re-rules, the constants
|
||||
below move WITH the ruling." It's B's file, so I did NOT land the edit on lane/a; the exact
|
||||
change is applied and verified on my scratch merge (commit message "apply B's in-file
|
||||
resolution", **357/0/0** with everything else green): replace the two-chain ceiling assert
|
||||
with `assert(sepRun.heldOk, ...)` and keep the skew-chain 68.4 measurement as a drift
|
||||
tripwire. Everything else in B's suite passes against my restated block unchanged —
|
||||
`bareMustLoseBelow`, the >25 separation, the $80 cap, and both venturi locks.
|
||||
|
||||
@ -115,39 +115,56 @@
|
||||
"",
|
||||
"WHY (5.5, -2) at h 2.6 and not somewhere kinder: the LOW top is the point - it twists the",
|
||||
"p1+p2+p3+p5 quad so it spills the gusts (small+twisted survives, DESIGN.md), and the",
|
||||
"position is swept against the $80 wallet:",
|
||||
"position is swept against the $80 wallet. NUMBERS CORRECTED to the game-true chain after",
|
||||
"B's 12s-skew finding (no phantom settle; the first sweep's hp column ran ~4 HP hot):",
|
||||
" (4.5,-2) h2.6 -> p1 3.35 kN, needs RATED, line costs $90 DO NOT",
|
||||
" (5,-2.5) h2.8 -> p5 6.20, p1 3.75 -> $90 DO NOT",
|
||||
" (5.5,-2) h2.8 -> p2 3.08, p1 3.06 (4% margin on shackle) - legal but knife-edged",
|
||||
" (5.5,-2) h2.6 -> p5 4.88, p2 3.14, p1 2.91, p3 1.82 -> $75, hp 68.3 FULL <- here",
|
||||
" (6,-2) h2.6 -> shadow drops, hp 65.8 tattered DO NOT",
|
||||
"The separation block below pins the recipe; a.test flies it. Full cover STAYS unbuyable:",
|
||||
"the covering quads (p1+p2+p4+p5 and up) still pull 6.4-6.9 kN and still tear - the",
|
||||
"smallest full-cover quad stays >45 m2 (a.test's floor holds), so the tradeoff decision 2",
|
||||
"protects is intact. And the ICE night still refuses the $80 wallet: same line needs $105",
|
||||
"there (p1 3.25, p2 3.22 vs a shackle's 3.20 - the same few-hundredths miss as C's q3),",
|
||||
"so night 5 stays the night the bank pays for, exactly as ruled in SPRINT9."
|
||||
" (5.5,-2) h2.6 -> p5 5.10, p2 3.08, p1 2.96, p3 1.81 -> $75, hp 63.6 WIN <- here",
|
||||
" (6,-2) h2.6 -> shadow drops DO NOT",
|
||||
"A second, LOWER-still or wider post cannot buy FULL either: the twin-post covering quad",
|
||||
"(p6 at (-3,-2) h2.6 + p5 + p1 + p2, 100% cover) pulls 6.9/6.7/6.2 kN and tears - full",
|
||||
"cover honestly exceeds the wild night at ANY buyable price, which is decision 2's tension",
|
||||
"doing its job. The separation block below pins the recipe; a.test flies it game-true.",
|
||||
"The smallest full-cover quad stays >45 m2 (a.test's floor holds). And the ICE night still",
|
||||
"refuses the $80 wallet: same line needs $105 there (p1 3.25, p2 3.22 vs a shackle's 3.20",
|
||||
"- the same few-hundredths miss as C's q3), so night 5 stays the night the bank pays for."
|
||||
]
|
||||
} ],
|
||||
|
||||
"separation": {
|
||||
"_why": [
|
||||
"SPRINT13 gate 1.4 — A's ruling on C's proposed target, AMENDED to the numbers the real game",
|
||||
"measures (ruled on D's funnel-corrected picture, not C's original headline). C proposed:",
|
||||
"'on a wild night the best buyable line reads FULL (>66) and a bare bed reads DEAD (<33)'.",
|
||||
"Adopted in spirit, amended in both halves:",
|
||||
" - The bare half becomes 'a bare bed LOSES the night (hp < 50, the win line)'. Nowhere in",
|
||||
" the shipped game does a bare bed read DEAD on the wild night (35.7, tattered) — D's",
|
||||
" check — and tuning storm_02's damage budget until a slogan lands is exactly the fudge",
|
||||
" gate 1 exists to kill. The felt boundary is the WIN line, and 35.7 fails it honestly.",
|
||||
" - The FULL half binds THIS yard, because the shipped week's only wild night is here",
|
||||
" (week.js night 4). C's passing example was site_02, whose wild night the corner block",
|
||||
" never plays — and with its venturi live the example dies anyway (D's landmine).",
|
||||
"This block is the pin: the recipe below is the measured best $80 line, a.test flies it",
|
||||
"through the real chain (session -> commit -> attach -> skyfx) every run, and B's audit can",
|
||||
"read the same recipe instead of inventing one. A target nothing checks is decoration —",
|
||||
"the repo's oldest lesson, and the reason this is data and not a THREADS sentence."
|
||||
"SPRINT13 gate 1.4 — A's ruling on C's proposed target, RULED TWICE and honest the second",
|
||||
"time. C proposed: 'on a wild night the best buyable line reads FULL (>66) and a bare bed",
|
||||
"reads DEAD (<33)'. First ruling amended the bare half (nowhere does a bare bed read DEAD;",
|
||||
"35.7 is tattered, and tuning storm_02 until a slogan lands is the fudge gate 1 exists to",
|
||||
"kill) and kept FULL — then B's audit rewrite caught that FULL was only ever met through the",
|
||||
"assert's own 12 s phantom settle (SailRig samples wind at its INTERNAL clock: game-true",
|
||||
"63.8/63.6, skewed 68.4/68.3). The geometry was re-swept game-true: nothing buyable reaches",
|
||||
"66 (the twin-post covering quad tears at 6.9 kN; every FULL-flavoured candidate is also",
|
||||
"knife-edged under B's 15% margin rule). So the target is RESTATED to the boundaries the",
|
||||
"game actually stakes, with real headroom, instead of pinning another knife edge:",
|
||||
" - the best buyable line HOLDS (0/4) and WINS COMFORTABLY: hp > 60 (win line is 50;",
|
||||
" game-true 63.6 - a red means the wild night's best line regressed, not noise),",
|
||||
" - a bare bed LOSES: hp < 50 (game-true 35.7),",
|
||||
" - the spread reads as most of a plant state: >= 25 HP (game-true 27.9).",
|
||||
"DESIGN CANON, written down on purpose: the wild night's garden is WON SCARRED, never FULL,",
|
||||
"and sometimes the win is PYRRHIC - the sail is spendable, the bed is not. PLAYED, per D's",
|
||||
"ask (real pointer events on the markers, real Enter, real invoice, night 4 as shipped):",
|
||||
"hp 55.7 WIN, 2/4 lost (p2 then p3), banked +$21, verdict 'THE GARDEN MADE IT. The sail",
|
||||
"didn't... That is what the sail was for.' The played number sits ~8 under the pinned",
|
||||
"sim-chain 63.6 because the real yard also throws the t=38 crate and ponds rain - and that",
|
||||
"gap lands exactly on p2's 4% shackle margin. RULED DELIBERATE: the wild night sells no",
|
||||
"clean line (B's margin rule labels this recipe marginal-only, which is the audit telling",
|
||||
"the truth) - the best $75 the shop has is a gamble that saves the bed and may cost the",
|
||||
"sail, which is DESIGN.md's firework, priced. The week's garden arc: full/full/full on",
|
||||
"nights 1-3, won-scarred night 4, beyond saving night 5 (week.js gardenBeyondSaving).",
|
||||
"The thresholds below pin the SIM CHAIN (rig+sky+garden, deterministic, no debris) as the",
|
||||
"regression tripwire; the played reference above is what the numbers must keep buying.",
|
||||
"B's audit reads the same recipe (heldMustExceed/bareMustLoseBelow are B's field names too -",
|
||||
"renaming them breaks gardenfly). A target nothing checks is decoration - the repo's oldest",
|
||||
"lesson."
|
||||
],
|
||||
"_playedReference": "2026-07-18 UI run, night 4 as shipped: hp 55.7 WIN, 2/4 lost (p2,p3), +$21",
|
||||
"stormKey": "storm_02_wildnight",
|
||||
"tension": 1.0,
|
||||
"line": [
|
||||
@ -156,8 +173,9 @@
|
||||
{ "anchor": "p2", "hw": "shackle" },
|
||||
{ "anchor": "p3", "hw": "shackle" }
|
||||
],
|
||||
"heldMustExceed": 66,
|
||||
"bareMustLoseBelow": 50
|
||||
"heldMustExceed": 60,
|
||||
"bareMustLoseBelow": 50,
|
||||
"separationAtLeast": 25
|
||||
},
|
||||
|
||||
"shed": { "model": "shed_01_v1", "x": 11.8, "z": 6.2, "rotYDeg": -90 },
|
||||
|
||||
@ -193,7 +193,7 @@ export function applyMute(sky, on) {
|
||||
*
|
||||
* @returns {{verdict: string, mode: string}}
|
||||
*/
|
||||
export function verdictFor({ hp, lost, win, dmg, pondPeak, pondDumped }) {
|
||||
export function verdictFor({ hp, lost, win, dmg, pondPeak, pondDumped, beyondSaving = false }) {
|
||||
// SPRINT12 — B's flag, actioned: "weakest link that went first" reduces on
|
||||
// the EFFECTIVE rating (rating × ratingHint), matching sail.js's failure
|
||||
// line and B's summary.weakest tie-break fix. On the bare rating a rated
|
||||
@ -217,6 +217,25 @@ export function verdictFor({ hp, lost, win, dmg, pondPeak, pondDumped }) {
|
||||
verdict: `THE GARDEN MADE IT. The sail didn't — the ${named} went first and took `
|
||||
+ `${lost.length - 1} more with it. That is what the sail was for.` };
|
||||
}
|
||||
// SPRINT13 gate 1.4 — A's night-5 ruling (week.js gardenBeyondSaving): on a
|
||||
// night whose data says the garden cannot reach the win line at ANY price,
|
||||
// every "you could have rigged better" sentence below is a lie, and the
|
||||
// uncovered/rain modes are the worst liars — they blame placement on a night
|
||||
// where no placement wins. This branch comes before every loss mode: the
|
||||
// garden's fate was sealed at the forecast; only the STEEL's fate was the
|
||||
// player's. Note it deliberately does NOT fire on a win — if a retune ever
|
||||
// makes the flagged night winnable, the flag is stale and week.js says to
|
||||
// delete it, not to trust this to paper over it.
|
||||
if (!win && beyondSaving) {
|
||||
if (lost.length >= 1) {
|
||||
return { mode: 'beyond-saving',
|
||||
verdict: `THE BED WAS BEYOND SAVING — no sail in the shop stops this much ice. `
|
||||
+ `The ${named} letting go is the part that's on you.` };
|
||||
}
|
||||
return { mode: 'beyond-saving',
|
||||
verdict: 'THE BED WAS BEYOND SAVING — no sail in the shop stops this much ice. '
|
||||
+ 'Yours held anyway. That was the whole job tonight, and you did it.' };
|
||||
}
|
||||
if (!win && lost.length >= 2) {
|
||||
return { mode: 'cascade',
|
||||
verdict: `THE SAIL LOST. The ${named} went first — and took ${lost.length - 1} more with it.` };
|
||||
@ -773,7 +792,8 @@ export async function boot(opts = {}) {
|
||||
*/
|
||||
const win = hp >= 50;
|
||||
const dmg = garden.damage;
|
||||
const { verdict, mode } = verdictFor({ hp, lost, win, dmg, pondPeak, pondDumped });
|
||||
const { verdict, mode } = verdictFor({ hp, lost, win, dmg, pondPeak, pondDumped,
|
||||
beyondSaving: week.job.gardenBeyondSaving });
|
||||
|
||||
return {
|
||||
hp,
|
||||
|
||||
@ -221,6 +221,10 @@ export default async function run(t) {
|
||||
assertEq(nightAt(4).storm, 'storm_02b_icenight', 'night five is the ice night');
|
||||
assertEq(nightAt(2).site, 'site_02_corner_block', 'night three moves to the corner block');
|
||||
assertEq(nightAt(0).site, 'backyard_01', 'the rest are the backyard');
|
||||
// SPRINT13 gate 1.4: the unsavable-garden flag is night 5's alone, and a
|
||||
// night without it is presumed savable — new nights can't inherit the excuse.
|
||||
assertEq(nightAt(4).gardenBeyondSaving, true, 'night 5 is beyond saving BY DESIGN — canon, ruled');
|
||||
for (let i = 0; i < 4; i++) assertEq(nightAt(i).gardenBeyondSaving, false, `night ${i + 1} is savable`);
|
||||
const w = createWeek();
|
||||
assertEq(w.night, 1); assertEq(w.bank, 80);
|
||||
assertEq(w.site, 'backyard_01', 'week.site tracks the ladder');
|
||||
@ -797,6 +801,39 @@ export default async function run(t) {
|
||||
assert(hailed.verdict !== rained.verdict, 'hail and rain deaths must not read identically');
|
||||
});
|
||||
|
||||
t.test("night 5's loss is TAUGHT, not blamed: beyondSaving rewrites the garden verdicts only", () => {
|
||||
// SPRINT13 gate 1.4, the night-5 ruling. Measured game-true (B's pay table,
|
||||
// my probe agreeing): the icenight garden cannot reach the win line at any
|
||||
// price — bare 0.0, best buyable 27.7. So week.js flags the night, and the
|
||||
// verdict must stop implying a better rig existed. The worst liar was
|
||||
// 'uncovered' ("the hail fell where your sail wasn't") on a night where no
|
||||
// WHERE saves it.
|
||||
const dmg = { hail: 90, rain: 5 };
|
||||
const held = verdictFor({ hp: 25, lost: [], win: false, dmg, pondPeak: 0, pondDumped: 0, beyondSaving: true });
|
||||
assertEq(held.mode, 'beyond-saving');
|
||||
assert(/BEYOND SAVING/.test(held.verdict), `says so plainly: "${held.verdict}"`);
|
||||
assert(/held|did it/i.test(held.verdict), 'and a 4/4 hold is CREDITED — keeping the steel was the job');
|
||||
assert(!/wasn't|skimp/i.test(held.verdict), 'no placement-blame, no skimp-blame');
|
||||
|
||||
// Losing your sail on the unsavable night is still yours to own.
|
||||
const shackle = { name: 'shackle', rating: 3200 };
|
||||
const torn = verdictFor({ hp: 5,
|
||||
lost: [{ hw: shackle, anchorId: 'p1', anchor: { ratingHint: 1 } }],
|
||||
win: false, dmg, pondPeak: 0, pondDumped: 0, beyondSaving: true });
|
||||
assertEq(torn.mode, 'beyond-saving');
|
||||
assert(/shackle at P1/.test(torn.verdict), `still names the steel that went: "${torn.verdict}"`);
|
||||
|
||||
// The flag defaults OFF and changes nothing anywhere else: the two garden
|
||||
// deaths keep their opposite sentences (mutation check — delete the branch
|
||||
// and the first assert above reds; pass the flag everywhere and THIS one does).
|
||||
const uncov = verdictFor({ hp: 20, lost: [], win: false, dmg: { hail: 70, rain: 10 }, pondPeak: 0, pondDumped: 0 });
|
||||
assertEq(uncov.mode, 'uncovered', 'a savable night keeps its honest placement lesson');
|
||||
// And a WIN on a flagged night ignores the flag — week.js says the flag
|
||||
// would be stale, and a stale excuse must never eat a real win.
|
||||
const win = verdictFor({ hp: 90, lost: [], win: true, dmg, pondPeak: 0, pondDumped: 0, beyondSaving: true });
|
||||
assertEq(win.mode, 'clean');
|
||||
});
|
||||
|
||||
t.test('a clean hold reads as a clean hold, and the broom gets its credit', () => {
|
||||
const clean = verdictFor({ hp: 96, lost: [], win: true, dmg: { hail: 2, rain: 2 }, pondPeak: 0, pondDumped: 0 });
|
||||
assertEq(clean.mode, 'clean');
|
||||
@ -1016,19 +1053,33 @@ export default async function run(t) {
|
||||
|
||||
// --- SPRINT13 gate 1.4: THE PINNED SEPARATION TARGET ----------------------
|
||||
//
|
||||
// The ruling (THREADS [A], on D's funnel-corrected picture): on the shipped
|
||||
// week's wild night — night 4, THIS yard — the best line START_BUDGET can buy
|
||||
// must read FULL (>66) and WIN, and a bare bed must LOSE (<50). The recipe is
|
||||
// DATA (site.separation) so B's audit reads the same one, and this flight is
|
||||
// the pin: real shop at the real budget, real commit()->attach() (D's
|
||||
// skipped-attach landmine — session.commit(rig) IS the attach path, verified
|
||||
// by C's probe2 agreement), real skyfx exposure, the real garden model out of
|
||||
// garden.js. Flown OUTSIDE t.test() because Suite.test cannot await — the
|
||||
// two async-test ghosts above are the precedent.
|
||||
// The ruling, RESTATED after B's skew finding (THREADS [A], twice — read the
|
||||
// second entry): on the shipped week's wild night — night 4, THIS yard — the
|
||||
// best line START_BUDGET can buy must HOLD and WIN comfortably (>60), a bare
|
||||
// bed must LOSE (<50), and the spread must read as most of a plant state
|
||||
// (≥25 HP), because the win line and the invoice are the game's real stakes.
|
||||
// FULL (>66) was the first ruling and it died honestly: it was only ever met
|
||||
// through this flight's own 12 s phantom settle — SailRig samples wind at its
|
||||
// INTERNAL clock, so a settled rig flies the storm 12 s off the authored
|
||||
// curve while the sky flies t=0..90 (B's three-way probe: game-true 63.8 /
|
||||
// skewed 68.4 / shape-only 64.3). The settle is GONE: in the real game the
|
||||
// rig does not exist before commit, and commit→attach→storm is one keypress
|
||||
// (C's bench correction, same day, same bug).
|
||||
//
|
||||
// The recipe is DATA (site.separation) so B's audit reads the same one, and
|
||||
// this flight is the pin: real shop at the real budget, real
|
||||
// commit()->attach() (D's skipped-attach landmine — session.commit(rig) IS
|
||||
// the attach path), real skyfx exposure, the real garden model out of
|
||||
// garden.js. Wind is wired the way main.js wires a site (venturi from site
|
||||
// data — backyard's list is empty, but the wiring must not ASSUME that — plus
|
||||
// tree shelters); switch to C's windForSite at integration, it is the same
|
||||
// wiring behind one door. Flown OUTSIDE t.test() because Suite.test cannot
|
||||
// await — the two async-test ghosts above are the precedent.
|
||||
//
|
||||
// Sway is frozen (balance.test's reasoning): the recipe is all posts, whose
|
||||
// real sway is constant anyway, so frozen here means "identical to the game",
|
||||
// not "conveniently calm".
|
||||
// not "conveniently calm". (C's landmine 2 — a frozen TREE under-reads — is
|
||||
// exactly why the pinned recipe staying post-only matters to this harness.)
|
||||
const sep = site.separation;
|
||||
let sepRun = null;
|
||||
if (sep && typeof document !== 'undefined') {
|
||||
@ -1039,7 +1090,6 @@ export default async function run(t) {
|
||||
});
|
||||
const trees = frozen.filter((a) => a.type === 'tree');
|
||||
const stormDef = await loadStorm(sep.stormKey);
|
||||
const calmDef = await loadStorm(CALM_STORM);
|
||||
|
||||
const session = new RiggingSession({ anchors: frozen }); // the REAL budget
|
||||
const shopLog = [];
|
||||
@ -1056,16 +1106,12 @@ export default async function run(t) {
|
||||
session.setTension(sep.tension);
|
||||
const spent = START_BUDGET - session.budget;
|
||||
const rig = session.commit(new SailRig({ anchors: frozen, gridN: 10 }));
|
||||
|
||||
const calmWind = createWind(calmDef);
|
||||
calmWind.setSheltersFromTrees(trees);
|
||||
let prepT = 0;
|
||||
for (let i = 0; i < Math.round(12 / FIXED_DT); i++) {
|
||||
rig.step(FIXED_DT, calmWind, prepT % 3); prepT += FIXED_DT;
|
||||
}
|
||||
// NO settle here, deliberately — see the header. The rig flies the storm
|
||||
// from t=0 on a fresh internal clock, exactly like a committed rig does.
|
||||
|
||||
const flyNight = (sail) => {
|
||||
const wind = createWind(stormDef);
|
||||
wind.setVenturi(site.wind?.venturi ?? []); // main.js:‘the funnel is SITE data’
|
||||
wind.setSheltersFromTrees(trees);
|
||||
const camera = new THREE.PerspectiveCamera(60, 1.6, 0.1, 400);
|
||||
camera.position.set(0, 2, 8);
|
||||
@ -1092,24 +1138,32 @@ export default async function run(t) {
|
||||
}
|
||||
}
|
||||
|
||||
t.test('PINNED: the wild night separates — best buyable line FULL and winning, bare bed losing', () => {
|
||||
t.test('PINNED: the wild night separates — best buyable line holds and wins, bare bed loses', () => {
|
||||
if (!sep) throw new Error('backyard_01 lost its separation block — the target is decoration again');
|
||||
if (typeof document === 'undefined') return t.skip('needs DOM (skyfx)');
|
||||
if (sepRun.err) throw new Error(`separation flight died: ${sepRun.err}`);
|
||||
assertEq(sepRun.shopLog.join('; '), '', 'the shop sold the whole recipe at the real budget');
|
||||
assert(sepRun.spent <= START_BUDGET, `recipe costs $${sepRun.spent} — must be buyable night 1`);
|
||||
assertEq(sepRun.lost, 0, 'the pinned line HOLDS the wild night — 0/4 lost');
|
||||
// SIM-CHAIN facts, deliberately: this flight is rig+sky+garden with no
|
||||
// debris events, so it is deterministic and 0/4 here is real headroom. In
|
||||
// PLAY the yard also throws the t=38 crate and ponds rain — the played
|
||||
// reference (site JSON _playedReference: hp 55.7 WIN, 2/4 lost, pyrrhic
|
||||
// verdict) sits ~8 under this chain, and that ~8 is exactly why the
|
||||
// thresholds below carry margin over the win line instead of hugging it.
|
||||
assertEq(sepRun.lost, 0, 'the pinned line holds this deterministic chain — 0/4 lost (play may cost the sail; the WIN must survive that)');
|
||||
assert(sepRun.held.hp > sep.heldMustExceed,
|
||||
`held bed reads ${sepRun.held.hp.toFixed(1)} — the target is FULL (>${sep.heldMustExceed}). ` +
|
||||
`Measured 68.3 at landing; a red here means a retune moved the wild night and the ` +
|
||||
`separation ruling needs re-litigating in THREADS, not a quiet threshold bump.`);
|
||||
assertEq(sepRun.held.state, 'full', 'and the plants SAY full — the state the player sees');
|
||||
`held bed reads ${sepRun.held.hp.toFixed(1)} — must stay >${sep.heldMustExceed} so the PLAYED night ` +
|
||||
`(measured ~8 HP under this chain through crate + ponding) keeps clearing the win line at 50. ` +
|
||||
`Sim-chain 63.6 / played 55.7 at landing. FULL (>66) was the first ruling and it was only ever met ` +
|
||||
`through this flight's own 12 s clock skew (B's finding). A red here means a retune moved the wild ` +
|
||||
`night: re-litigate in THREADS, no quiet threshold bump.`);
|
||||
assert(sepRun.held.state !== 'dead', 'the held bed is ALIVE — tattered is the wild night speaking');
|
||||
assertLess(sepRun.bare.hp, sep.bareMustLoseBelow,
|
||||
`bare bed must LOSE the night (<${sep.bareMustLoseBelow}) — measured 35.7 at landing`);
|
||||
`bare bed must LOSE the night (<${sep.bareMustLoseBelow}) — game-true 35.7 at landing`);
|
||||
// The felt spread, stated so a red prints the whole story:
|
||||
assert(sepRun.held.hp - sepRun.bare.hp > 25,
|
||||
`separation ${(sepRun.held.hp - sepRun.bare.hp).toFixed(1)} — rigging must matter by a full state, ` +
|
||||
`not a garden-bonus margin (measured 32.6 at landing)`);
|
||||
assert(sepRun.held.hp - sepRun.bare.hp >= (sep.separationAtLeast ?? 25),
|
||||
`separation ${(sepRun.held.hp - sepRun.bare.hp).toFixed(1)} — rigging must matter by most of a ` +
|
||||
`plant state, not a garden-bonus margin (game-true 27.9 at landing)`);
|
||||
});
|
||||
|
||||
t.test('sway() returns an absolute position, not an offset', () => {
|
||||
|
||||
@ -81,7 +81,23 @@ export const NIGHTS = [
|
||||
storm: 'storm_02b_icenight', site: 'backyard_01',
|
||||
client: 'the Hendersons', addr: '14 Kurrajong St — the backyard',
|
||||
brief: 'Less wind than last night and more ice, which is worse for the bed and easier on the rig. '
|
||||
+ 'Last night before they land.',
|
||||
+ 'Last night before they land. Nobody\'s pretending the seedlings ride this one out — keep '
|
||||
+ 'the sail on its feet and the ice off the windows.',
|
||||
// SPRINT13 gate 1.4 — A's ruling, DESIGN CANON: night 5's garden is BEYOND
|
||||
// SAVING, by design, at any price. Measured game-true (B's pay table and
|
||||
// A's probe, agreeing): bare bed 0.0 DEAD; best buyable garden 27.7; best
|
||||
// clean 15.3 — every line in the game sits under WIN's hp >= 50, wallet or
|
||||
// bank. The week's arc WANTS this: full / full / full, won-but-tattered
|
||||
// (night 4, the pinned separation), then the night you cannot save — the
|
||||
// icenight is where the job stops being about the bed and becomes about
|
||||
// your steel (the line that would hold it needs $105 the shop caps at
|
||||
// 3.20 kN shackles against 3.22/3.25 peaks — C's few-hundredths whisker,
|
||||
// kept on purpose). This flag exists so surfaces TEACH the loss instead of
|
||||
// implying a better rig existed: verdictFor reads it (main.js) and stops
|
||||
// blaming placement; the brief above levels with the player before they
|
||||
// spend a dollar. If a retune ever makes night 5 winnable, delete this
|
||||
// flag in the same commit or the verdict lies in the other direction.
|
||||
gardenBeyondSaving: true,
|
||||
},
|
||||
];
|
||||
|
||||
@ -106,6 +122,9 @@ export function nightAt(i) {
|
||||
addr: base.addr ?? null,
|
||||
brief: base.brief ?? null,
|
||||
pay: base.pay ?? {},
|
||||
// False by default on purpose: a night is presumed savable unless its data
|
||||
// says otherwise, so a new night can never inherit the icenight's excuse.
|
||||
gardenBeyondSaving: base.gardenBeyondSaving ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user