Resolve Sprint 8 merge conflicts: week card + forecast bands; pyrrhic assert

hud.js: A's week forecast card now uses C's measured forecastLines with
uncertainty bands. balance.test.js: B's measurement-style assert kept (garden
half enforced, corner count reported as A's open design call).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-17 13:41:58 +10:00
parent d3bb7c2a93
commit 030349d152
2 changed files with 10 additions and 67 deletions

View File

@ -355,11 +355,12 @@ export function createHud(d) {
* @param {{key:string, def:object}[]} storms
* @param {(key:string) => void} onPick
*/
<<<<<<< HEAD
showForecast({ key, def }, wk, onGo) {
const peak = Math.max(...def.baseCurve.map((p) => p[1]));
const gustPeak = peak + (def.gusts?.powBase ?? 0) + (def.gusts?.powRamp ?? 0);
const rainPeak = Math.max(...(def.rain?.curve ?? [[0, 0]]).map((p) => p[1]));
showForecast({ key, def }, wk, onGo) {
// Integrator merge (Sprint 8): A's week card shape + C's forecastLines —
// MEASURED numbers, banded by lead (the old inline estimate read 30 m/s
// for a storm that really gusts 32.3). Tonight is lead 0 = exact; when
// the week wants to show tomorrow's card, pass its lead and it hedges.
const f = forecastLines(def, 0);
const change = (def.events ?? []).find((e) => e.type === 'windchange');
const night = (def.sky?.darkness ?? 0) > 0.6;
const hail = !!def.hail;
@ -372,34 +373,16 @@ export function createHud(d) {
const held = done && wk.log[i]?.won;
return `<span class="pip ${now ? 'now' : done ? (held ? 'held' : 'lost') : ''}">${
now ? '◆' : done ? (held ? '●' : '○') : '·'}</span>`;
=======
showForecast(storms, onPick) {
// Numbers via Lane C's forecastLines(def, lead): MEASURED rather than
// estimated (the old inline `baseCurve peak + powBase + powRamp` read 30
// m/s for storm_02, which really gusts to 32.3), and banded by how far out
// the night is. `lead` 0 = tonight = exact, which is what this reads as
// today; pass each night's lead when the week lands and the card starts
// hedging on its own. Same wording as before — only the numbers changed.
const rows = storms.map(({ key, def, lead = 0 }) => {
const f = forecastLines(def, lead);
return `<button class="storm" data-key="${key}">
<div class="name">${f.name || key.replace(/_/g, ' ').toUpperCase()}${f.night ? ' · NIGHT' : ''}</div>
<div class="stat">${f.wind}</div>
<div class="stat">${f.rain}</div>
${f.confidence ? `<div class="stat" style="color:#8ba0ad">${f.confidence}</div>` : ''}
</button>`;
>>>>>>> origin/lane/c
}).join('');
card.innerHTML = `<div class="card">
<h1>NIGHT ${wk.night} OF ${wk.nights}</h1>
<div class="pips">${pips}</div>
<h2>${(def.name ?? key).replace(/_/g, ' ').toUpperCase()}${night ? ' · NIGHT' : ''}</h2>
<div class="stat">sustained to ${peak.toFixed(0)} m/s (${kmh(peak).toFixed(0)} km/h)
· gusts to ~${kmh(gustPeak).toFixed(0)} km/h</div>
<div class="stat">rain ${rainPeak >= 0.8 ? 'heavy' : rainPeak >= 0.4 ? 'steady' : 'light'}${
hail ? ' · hail forecast' : ''}
<h2>${(f.name ?? key).replace(/_/g, ' ').toUpperCase()}${night ? ' · NIGHT' : ''}</h2>
<div class="stat">${f.wind}</div>
<div class="stat">${f.rain}${hail ? ' · hail forecast' : ''}
${change ? `· change at ${change.t}s` : '· no change forecast'}</div>
${f.confidence ? `<div class="stat" style="color:#8ba0ad">${f.confidence}</div>` : ''}
<div class="row" style="margin-top:14px"><span>in the bank</span><b>$${wk.bank}</b></div>
<div class="stat" style="color:#8ba0ad;margin-top:10px">
${hail

View File

@ -315,50 +315,10 @@ export default async function run(t) {
// measurement, not the verdict.
t.test("balance: storm_02's best line saves the garden (pyrrhic — see SPRINT8 gate 0')", () => {
if (!line) throw new Error('the $80 shop cannot buy the candidate line at all');
<<<<<<< HEAD
// The garden half — the part the player is actually protecting — must hold.
if (line.hp < 50) {
throw new Error(`the wild night's best $${line.spent} line let the garden die: hp=${line.hp} ` +
`(need >=50). This is a real balance regression, not the pyrrhic-win question.`);
=======
// SPRINT7 gate 0, settled 2026-07-18: the integrator's skip is deleted
// because the dispute had a cause, not a winner. This suite built skyfx
// WITHOUT a camera, and skyfx.step() opens `if (!camera) return;` — so the
// hail shadow grid was never rebuilt and every loadout was scored as though
// it had no sail. That is why this line read hp 36: 36 IS the bare-bed
// number. With a camera it reads what Lane A measured. Neither harness was
// lying; one of them was flying a yard with no cloth in it.
if (!WIN(line.hp, line.lost)) {
// SPRINT8 gate 0' — CLOSED, and this suite was RIGHT. The live game and this harness now
// agree, measured by Lane D 2026-07-18 on current main (fresh page, A's exact line,
// t2+p3+p4+t2b on 4×shackle+spare, tension 1.0):
// live game → hp 58, 2 lost [t2,t2b], t2 peak 4.55 kN
// this suite → hp 58, 2 lost [t2,t2b], t2 peak 4.54 kN
// Every suite-vs-game difference gate 0' listed has now been tested one at a time and NONE
// of them moves the corner count: live-calm settle (entry 1.94→0.40 kN, verdict unchanged),
// live tree sway (t2 4.33→4.55, verdict unchanged), walked repair timing (unchanged).
// Anchors are bit-identical between harnesses, ring order is identical (p4,t2,t2b,p3), and
// wind.sample() at t2 is identical to 2 dp (36.49 m/s @ t=75.9).
//
// RETRACTED, on the record: Lane D's "the settle flips 2 lost → 1" (SPRINT7). It does not
// reproduce — 0 s and 12 s settle both end 2 lost, because the storm's own peak (4.54 kN at
// t=75.9) dwarfs the attach transient. The settle is a real fidelity fix and NOT the
// corner-count cause. A's hp 58 reproduces exactly; A's "1 lost" reproduces in neither
// harness.
//
// What's left is B's arithmetic, which was correct from the first post: t2 pulls ~4.5 kN and
// a shackle is rated 3.2 kN. The corner cannot hold at any tension, and the spare cannot save
// it — the repaired corner simply blows again (measured: t2b blew @9 s, repaired, blew again).
// So this is no longer an engineering question. It is SPRINT8's designed escape hatch:
// hp 58 CLEARS the 50 bar while 2 corners are lost, i.e. the player saves the garden and
// loses the sail — DESIGN.md's own story — and the win rule (`hp>=50 && lost<2`) calls it a
// loss. Either the rule admits a PYRRHIC WIN, or the shop must be able to buy a third
// above-shackle corner ($80 buys two). That is a design call, not a harness bug. See THREADS.
throw new Error(
`no winnable line: ${COVER_QUAD.join(',')} on 4×shackle+spare ($${line.spent}) ended ` +
`hp=${line.hp}, lost=${line.lost}/4 (need hp>=50, lost<2). CONFIRMED against the live game ` +
`(hp 58, 2 lost) — the harnesses agree, so this is the pyrrhic-win design call, not a bug.`);
>>>>>>> origin/lane/d
}
const pyrrhic = line.lost >= 2;
return `$${line.spent} on ${COVER_QUAD.join(',')} -> garden hp ${line.hp}` +