- createGarden / GARDEN_DRAIN / HAIL_WEIGHT / RAIN_WEIGHT move from main.js module-locals to garden.js, unchanged — the audit must predict the sim off the sim's OWN code, not a drifting copy. GARDEN_DRAIN's comment now carries the gate-1.4 ruling: the constant does not move; it owns the bare-bed arc. - M-mute application extracted to exported applyMute(sky, on): calls C's sky.setMute when the tap exists, reports honestly when it does not, so the selftest can fail the wiring instead of trusting a ?.() no-op.
104 lines
4.5 KiB
JavaScript
104 lines
4.5 KiB
JavaScript
/**
|
|
* 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); }
|
|
},
|
|
};
|
|
}
|