/** * garden.js — the garden model, one shared copy. [Lane A] * * SPRINT13 gate 1.2 — B's export ask, granted. These were module-locals in * main.js, which made the audit's garden prediction impossible without copying * three constants "C is about to change this very sprint" (B's words) — the * exact drift sweep.js exists to prevent on the winnability side. Now main.js * and tools/site_audit read the SAME model, so "same number, two harnesses" * is structural rather than aspirational. B: the TEMPORARY COPY in * garden_probe.html can die now. * * Nothing here changed in the move — this is a mechanical lift. The one thing * that DID change this sprint is the comment on GARDEN_DRAIN, because the * gate-1 rulings resolved what it is for. */ /** * How fast an unprotected garden dies, in HP per second at full exposure. * * SPRINT13 gate 1.4 RULING: **this constant does not move.** My old comment * here said "the lever that actually needs moving is the shadow geometry, not * this" — C measured the geometry and it was never wrong (probe2: raycast and * rasteriser agree at 1.0000). What was wrong was the LINE the yard offered: * the canonical p1..p4 covers 25% of what hail sees, and no drain value can * separate a 25% line from a bare bed because the drain scales BOTH sides. * The separation now comes from anchor placement (backyard_01's p5, its * `separation` block, and the a.test assert that flies it), which is where it * always had to come from. * * What 0.9 actually owns is the week's bare-bed arc — the difficulty curve of * neglect, measured: gentle 97.6 · southerly 83.7 · buster 82.6 · wildnight * 35.7 · icenight 0.0. Move this and all five nights retune at once; the * wildnight bare bed must stay under the win line (< 50) for the pinned * separation target to mean anything, and the early nights must stay * survivable-by-default or night 1 stops being a tutorial. Retunes go through * THREADS with that table, not through this number quietly. */ export const GARDEN_DRAIN = 0.9; /** * Decision 13's weights. Hail is what actually kills a garden and cloth honestly * stops it (stones fall ≤20° off vertical; rain leans 73° in a gale and walks * straight under the sail — Lane C's numbers). Rain is demoted to a drizzle of * damage so the night still costs you something when it isn't hailing. * * SPRINT6 gate 1 lists these first among the balance levers. */ export const HAIL_WEIGHT = 5.0; export const RAIN_WEIGHT = 0.25; /** * The garden: the thing you are actually protecting, and the only score that * matters. Deliberately not inside hud.js — the HUD reads, it doesn't decide. * * It keeps its damage split by CAUSE, and that isn't bookkeeping for its own * sake: the aftermath verdict is the game's entire feedback channel, and a * verdict that guesses teaches the wrong lesson. A garden that only knows it * went from 100 to 39 cannot tell a player whether their hardware let go or * their perfectly-held sail simply wasn't over the bed — and those are opposite * mistakes with opposite fixes. * * @param {{ setPlants(state: string): void }} world */ export function createGarden(world) { let hp = 100; let state = 'full'; let byHail = 0; let byRain = 0; return { get hp() { return hp; }, get state() { return state; }, /** HP lost to each cause this run. The verdict reads these. */ get damage() { return { hail: byHail, rain: byRain }; }, reset() { hp = 100; state = 'full'; byHail = 0; byRain = 0; world.setPlants('full'); }, /** * Decision 13, and the two terms stay SEPARATE on the way in rather than * being pre-summed by the caller — that sum is exactly the information the * verdict needs and can never recover afterwards. * * @param {number} dt * @param {number} hail 0..1 sky.gardenHailExposure(bed, t) * @param {number} rain 0..1 sky.gardenExposure(bed, t) */ step(dt, hail, rain) { const dHail = HAIL_WEIGHT * hail * GARDEN_DRAIN * dt; const dRain = RAIN_WEIGHT * rain * GARDEN_DRAIN * dt; const total = Math.min(hp, dHail + dRain); if (total > 0) { // Attribute proportionally, so the split still adds up on the last tick // when the garden bottoms out at 0 mid-step. const scale = total / (dHail + dRain); byHail += dHail * scale; byRain += dRain * scale; hp -= total; } const next = hp > 66 ? 'full' : hp > 33 ? 'tattered' : 'dead'; if (next !== state) { state = next; world.setPlants(next); } }, }; }