Gate 1: C's model confirmed to the decimal in play (backyard 61.3 exact, carport
82.6 exact, fascia 2.27 eff / t=1 settle tear, QA 88 reproduced). The $80-line
wildnight headline does not survive the real game: probe4/bench read venturi off
the storm def, the site def is where it lives — funnel on, q3+q4 break before
the hail and the line reads 39.8 vs cheap 38.5. Separation table for A's ruling;
the proposed target is currently satisfiable nowhere (bare never reads DEAD).
Gate 2: storm grade, wall (both QA sightings dead from in-yard), leaves (cap 7,
one-wind onset with the lean), lean-under-grade, A's spawn fix (160 px clear on
receipts), hail sliver dead twice over. Plus two sightings: last night's sail
haunts the next prep across a site change; hidden-canvas boot NaNs the camera.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Leaves: a pooled handful (cap 7) streaming with the wind from 8.3 m/s — the
same number D keys the player's lean on, so the yard and the body start
telling one story at one speed. Count rides a 2.5 s speed EMA so it doesn't
strobe; own seeded rng (seed ^ 0x1eaf) so the pool never shifts the crate
sequence; bit-identical under identical (dt, t) streams (asserted).
debris.clear() rewinds the layer. dev_skyfx flies the debris layer now and
warms it to the scrub point, so ?t=50 shows the leaves t=50 would have.
D's aftermath sliver, exactly as diagnosed on lane/d: the hail InstancedMesh
at count:0 with frustum culling and depthWrite off still draws instance 0's
identity matrix — a 5 cm always-on-top speck at the origin. mesh.visible now
gates on n > 0, sky.hail joins the public fx object, and a whole-storm assert
demands hidden-at-zero / shown-when-falling in both directions (gated on
floor(1300 x intensity) >= 1: a burst's first frames honestly draw nothing).
THREADS: gate-2 wrap entry — D's judging unblocked, A's M key live.
Selftest 340/0/0 (+2 tests). Mutation-checked: dropped stepLeaves, a 3 m/s
threshold, and an always-visible hail pool all go red.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two QA sightings from in-yard eye height, both geometry, both reproduced on
dev_skyfx before touching anything. (1) The band stopped AT the camera's
horizontal plane, but the ground's true horizon sits ~0.6 deg below it (the
dip), so a bright sky sliver showed under the base — the floating slab. The
band now overshoots to ~12 deg below the equator; the ground occludes the
overshoot by depth, and it survives scale.y's 0.3 rise floor. (2) The arc-end
fade (sin^0.75) left the outer tenth at ~0.36 alpha — the hard vertical seam.
Now sin^1.6 with the arc widened 2.2->2.6 rad so the solid core keeps its
span while the ends drop under 0.12.
Verified by eye on dev_skyfx (southerly t=24, front 0.51): base meets the
ground line, no sliver, no seam. Selftest 338/0/0 (+1 test pinning both in
numbers). Mutation-checked: the old thetaLength and the old exponent each red.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
QA played the southerly at 65 km/h under a noon-blue sky with razor midday
shadows. The arithmetic said why: sky, sun and hemi all multiplied the weather
by the author's palette (darkness 0.5 on the southerly), so the dial could
zero the weather's say — 0.69 storminess became a 0.35 nudge. The grade floors
it (storminess x lerp(0.5, 1, darkness)): a full gale now goes at least half
way to slate on any palette while the wild night keeps its authored black.
Plus the two things nothing keyed at all: the sun disc lerps toward slate and
sun.shadow.radius lerps 1->7 on storminess (PCFSoft, so it is real blur).
Deterministic — pure in t, same curve the rain uses, exposed as sky.stormGrade
above the render gate for headless reads. dispose() hands back intensity,
colour and radius exactly.
Verified by eye in dev_skyfx: southerly t=55, 18.5 m/s reads slate weather.
Selftest 337/0/0 (+1 test). Mutation-checked: the old formula and a dropped
radius line both go red.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One flag plus a gain write, but the flag is the point: a mute pressed on the
splash screen, before the first gesture builds the audio graph, is remembered
and honoured by unlock(). main.js already feature-detects sky.setMute, so the
M key lights itself up on this landing. audio.masterGain getter added so the
assert reads the real bus, not the flag back.
Selftest 336/0/0 (was 335, +1 test). Mutation-checked both ways: a no-op
setMute and an unlock that forgets a pre-unlock mute both go red.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The gate-1 diagnosis the sprint chartered, inverted by measurement: the shadow
geometry is exact (probe2: raycast and rasteriser agree 1.0000/1.0000), the
canonical measuring lines just don't cover the bed against vertical hail, and
site_02's tr1+q1+q3+q4 at exactly $80 already separates 91.5 FULL vs 35.7 bare
(sep 55.8, 0/4 lost) with no model change. Full findings + separation-target
proposal in THREADS.
Re-verified after the authoring session died un-pushed: all headline numbers
reproduce; the bench drives RiggingSession.commit(rig) -> rig.attach(), not the
bypassed-attach seam D's landmine names; balance.test's canonical shade-cloth
peaks reproduce per-anchor to the last digit. One fix over the original staging:
probe3/probe4 labelled per-corner peaks in plan order, but rig.corners is ring
order — labels now come from corners[k].anchorId (values were always true; the
$80 line's two RATED corners read swapped). Selftest 335/0/0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Done/green on lane/d: wind lean, ladder knockdown fix, label, E clip
contract, C/B diagnoses. Gate-1 verdict and gate-2 visual judging blocked
on C (still at integration commit).
Documented a measurement trap before it becomes a false alarm: a headless
garden-separation harness showed zero separation, which looks like 'the
sail casts no shadow'. It's an artifact — (1) sky.step's third param is
named 'world' so world.sail is the passed {sail:rig}, not the global; (2)
session.commit() bypasses onCommit->rigSail->rig.attach, so the sail is
never built and every rig scores as bare (the hp-36 constant skyfx.js:803
warns cost gate 0 two sprints). Garden must be measured via real play or
the full attach path. No baseline numbers posted — a bypassed measurement
is worse than none.
Wired the wind lean as a root pose per E's analysis. Keyed on windBase not
raw speedAt (measured: raw strobes 8-18 flips/min, windBase 0-2 per storm).
Verified live at 71 km/h, leanDir dot wind = 1.0. gate 2.4 wind-clips line
is done with zero new clips: brace and stagger already shipped/wired, lean
is root. Open question to E: is the WindPosture additive worth authoring.
The QA pass's biggest player finding: 'stands bolt upright and unbothered'
at 65 km/h. E rendered the two Mixamo lean candidates and rejected both on
screen, and proved the deeper reason a clip can't do this: _rotOnly drops
Hips.quaternion from every clip at load, so a baked lean is discarded, and
a clip's lean axis is fixed while the wind's bearing swings. So the lean is
the ROOT, the same machine as the knockdown pitch and climbY lift.
sim (player.sim.js): sim.lean 0..1 + sim.leanDir, eased over leanSecs.
Magnitude off windBase (the ~4s EMA), NOT raw windSpeed — measured: raw
strobes the posture 8-18x/min across the storms, windBase settles it to 0-2
changes per 90s. Sustained wind = posture; the gust is already the
stumble/knock event. Suppressed on your back (pitch owns the body), braced
(TakeCover is its own hunch), and up a ladder. Bands land on the storm
rating ladder: night 1 upright, 2-3 lean, 4-5 in the gale.
view (player.js): a foot-pivot tip toward leanDir, max leanMaxRad (~17deg,
a third of a knockdown's pi/2), composed under the yaw. It's the else branch
of the pitch tip by construction — the sim guarantees lean=0 whenever
pitch>0, so they never fight.
Three asserts: magnitude scales with sustained speed, a lone gust does NOT
snap you over (windBase inertia), and it's downwind + suppressed when
floored/braced/aloft. Mutation-checked: forcing lean to 0 reddens two.
Verified live at 71 km/h — leanDir dot wind = 1.0, the figure visibly
braces. Selftest 340/0/0. This is the root lean E asked me to wire first;
whether their WindPosture additive follows is a look-at-it-together call.
SPAWN FRAMING. The QA pass: "a pole dead-centre through the player on every
single first impression." Measured, not eyeballed — player spawns at (0,6)
looking up the yard, and post p3 stands at (0,7), one metre behind, dead on the
view line. spawnYawFor() sweeps outward from the ideal frame (camera opposite
the bed, so the player stands in front of what they protect) and takes the
first yaw that clears every obstacle. Per YARD, off site data: site_02 spawns
elsewhere with its posts elsewhere, so a hand-tuned constant would frame the
corner block by luck.
Two numbers here are measured and both were wrong first. Clearance 0.6, not 1.1
— my first pass asked 1.1m and produced a WORSE frame than the bug, swinging
105° to stare at a fence, because the greatest distance any obstacle can have
from the camera->head line is its own distance from the player, and p3 is only
1m away, so 1.1 was unreachable by construction. maxOff 90° — a frame turned
more than a quarter-circle off the bed is no longer a frame of the bed, so the
"roomiest" fallback is bounded to the arc. The assert reproduces the 105° from
the real yard and pins that the cap brings it to 68°.
AFTERMATH CLOTH-SWALLOW. "The dead draped sail can swallow the camera whole."
The camera has collided with the house since Sprint 2; the sail was just never
in the list. While up it hangs above head height and nothing notices — but a
FAILED sail lies in the yard at head height, the one moment you most want to
look at it. refreshCameraSolids() adds the cloth to the set, called from both
rebuilds (loadSiteInto makes new world.solids, rigSail makes a new cloth; either
alone strands the camera on a disposed mesh). Its bounding sphere recomputes
every frame, so the raycast reads live geometry. Verified live: camera solids
9 -> 10 the moment a sail rigs, and the cloth is in the set.
Added camera.solids getter so that was checkable at all — "did the sail join
the set" was otherwise a claim with no seam. And hoisted sailView's declaration
with rig/rigging: loadSiteInto's boot-time call reads it before any cloth
exists, which is a TDZ throw (page goes blank) that the same block already
documents for rig/rigging.
selftest 339/0/0 (was 338).
D's mixed-hardware exhibit, measured on the corner block carport quad:
unbreakable (audit's view): cb1 peaks 3122 N
fuse (cb rated + q carabiner, $70): q3 blows at the change, sail sheds,
cb1 peaks only 1169 N — holds, pays +$147
uniform rated ($120): sail stays intact, cb1/cb2 both blow at 1602 N > 1430
The cheap corner is a genuine fuse — paying more breaks the beam, paying less
saves it. Ruling: it's real, physical, legible depth. Keep it.
The hole is in the tool: auditSweep flies unbreakable hardware, so it prices
every corner to its unbroken peak and marks D's winning quad
"cb1 3.1kN->NONE, affordable:false" — unwinnable. Third face of the SPRINT6
trap (unriggable -> cheap -> unwinnable), each time because the tool modelled
less than the sim. It's a false FAIL not a false PASS, so it never called the
site unshippable — but it hides the best-value line from guidance.
Fix folded into the post-C re-audit (fly breakable hardware, detect the shed):
the "best net" claim is garden-inclusive and moves when C's model lands, so
measuring it now measures a number that changes. Flagged, not rushed.
D measured it live: interact.js gated the shed-table spare on !player.carrying
alone, never reading the count, so a player who bought zero walked off with
unlimited free shackles mid-storm — the spare economy the ladder cost is
balanced against, free on the spare side, on the public deploy.
My half of the cross-lane seam: RiggingSession gains sparesRemaining (getter)
and takeSpare(). `spares` is the shed-table count, so takeSpare() decrements it
— which also fixes a second bug for free: main.js:720 refunds
session.spares * SPARE_COST as "a spare you never had to use", so you were being
refunded for spares you'd already spent. Decrementing makes that honest with no
change to main.js.
Wiring stays A's (thread the session into wireYardActions) and the gate+consume
stays D's (two lines, posted in THREADS, D offered). Asserted: take-without-buy
fails, table runs dry, consumed spare no longer refundable.
rigging.selftest 21/21.
Four of the QA/D "smalls", one of which was not small.
rig.t never reset between nights: main.js builds one SailRig at boot and
re-attach()es it each night while step() runs in every phase, so the clock ran
on and night 2 sampled its storm from wherever night 1's aftermath left off —
30-50% weaker, past the end of the curve, and offset by however long the invoice
was on screen. A determinism break. Fixed in attach() (zeroes t and _acc);
asserted as "two identical nights, one rig -> byte-equal traces", mutation-checked.
standing-kN preview (D's "$60 blind"): the prep panel shows each corner's
still-air load vs its effective rating, and a summary line, so the tension dial's
static cost is visible before you pay. At 1.4, p4 reads 1.62/1.2 kN !! RIPS AT
REST. session.standingLoads() measures on unbreakable hardware (measuring load,
not rehearsing failure). Cross-checks D's in-play 46m2 number (1.41 kN) exactly.
Panel honesty (D): prep list prints effective rating with the hint shown as its
discount (cb1 0.12/0.3 kN×0.22) — the weak-link arrow now explains itself.
setHardware() throws on non-shop hardware (incl. lookalikes — the shop compares
by identity) instead of silently no-opping; player-reachable failures still
return {ok:false}. balance.test documents the old silent-fail bite.
BLOWN label collision (hud.js — A's file, flagged in THREADS): screen-space
de-collision stacks overlapping corner bars. Verified by projecting cb1/cb2's
real positions through a swept camera — 6/72 viewpoints collide (all ~12m, where
the QA played), thresholds catch exactly those.
Gate 1.2 and the fuse-line stretch stay held on C's geometry fix + A's garden
export. Withdrew my night-1 flag (the card says it's the tutorial night by
design); the ice-night win-line question stands.
selftest 339/0/0 (335 + 4 new).
- The aftermath 'ghost sliver' QA blamed on my ladder is C's hail
InstancedMesh at the origin: count:0 but frustumCulled:false +
depthWrite:false leaves instance-0 drawn as a 5cm always-on-top speck.
Fix is mesh.visible = n>0 or scale-to-zero when intensity is 0. skyfx.js.
- The shed-table spare is free and infinite: interact.js gates only on
!carrying and never reads rigging.session.spares. Took a spare 3x with 0
bought. Deletes the spare economy the ladder cost is balanced against.
Cross-lane: the count lives in B's session, main.js wires
wireYardActions without it. Needs A to thread rigging in; B owns the
economy. Flagged both. Should land before gate 4 (public difficulty hole).
SPRINT13 gate 2.6 pool, both found by the integrator's QA and both silent
(no error, no red, just a mechanic that stopped):
- The ladder left the game if the wind took you over while carrying it.
knockdown() -> drop() nulls player.carrying but nothing told the ladder,
so state.carried stayed true: the mesh hid (visible = !carried), pos()
returned null so there was nothing to walk to, and canUse gated on
!carried so it could never be picked up again. Swept the whole yard post-
knockdown: no usable ladder offer anywhere, while ladder_place still said
'it's by the shed'. On the corner block that silently ends the site's
thesis (cb1/cb2 are bracket work). Fix: reconcile against the player's
hands every frame in update() and let the ladder fall flat where it was
lost, findable and re-takeable. Verified in the running game.
- The place label hardcoded 'the fascia' — a lie on a carport beam. Now
keyed on anchor.type (a checked enum since Sprint 11): house -> the
fascia, carport -> the carport bracket, unknown -> the bracket (vague but
never false). And it stops claiming 'by the shed' once the ladder is
dropped in the yard.
Two new asserts against the REAL createLadder, not the fake: the drop-
reconcile and the label. Mutation-checked — reverting the reconcile turns
both red on the right assertions. Selftest 337/0/0 (335 + 2).
Gate 2.4 asked for lean/stagger/brace clips staged by wind band. The answer,
measured twice, is that the asset shouldn't exist.
1. The lean cannot be a clip. _rotOnly drops Hips.quaternion from every clip at
load (player.js:53, applied :239). Parsed the shipped pack to confirm rather
than trust the read: 17 clips, 195 channels each, 64 survive. A lean baked
into a clip's hips is discarded. And stripping it is right — the wind's
bearing swings (the change; site_02's venturi) and a canned clip has one
fixed lean axis, so only the sim can aim it. Same trick as the knockdown and
climbY. The lean is D's root pitch; thresholds proposed off Beaufort 7
("inconvenience felt when walking against the wind" = 13.9 m/s), which lands
storm_01 never leaning, storm_03 gusting through the line, storm_02 in gale.
2. Mixamo has no wind animations. Searched the real API, not assumed: shiver
returns zero, brace returns eleven ledge-hang clips, wind returns Catwalk
Turn. The factory header already recorded this in Sprint 3.
Rendered the two real candidates on a skinned rig, twice each — as authored and
as the game shows them. Pushing is a squat shoving a couch. Leaning is a man
waiting for a bus. Both rejected. Both would have passed every check we own:
they load, they're metre-scale, their nodes survive. Dims can't answer "is this
the right shape" — the bike rule, catching a whole feature this time.
Brace and stagger already ship (TakeCover, StumbleBack, Reaction). D should wire
the root lean first; I'll hand-author a WindPosture additive into the gap we can
both see, if one remains. Spine rotation survives _rotOnly, so it would land.
Evidence, harness and repro in tools/character/windclip_candidates/, plus the
four pipeline traps I hit in order so the next lane doesn't.
selftest 335/0/0, unchanged — no asset added, no shipped file touched.
The URL is public, so this is the first thing a stranger meets.
ENTER, PINNED. The exploit shipped because the rule lived in a keydown closure
inside boot(), behind a canvas and a WebGL context — nowhere a test could
reach. That's WHY it went out: not carelessness, no seam. `enterCommits(phase,
cardOpen)` is a value now, asserted exhaustively over PHASES so a sixth phase
is covered the day it appears, and the safe default (may not advance) is the
one it gets. Mutation-checked: reverting the guard reddens forecast, storm and
aftermath.
SPLASH. A cold visitor used to land on a job sheet — an invoice-shaped card
from a business they'd never heard of, quoting money for a job nobody had
explained, controls revealed later on prep's help line. It's a good SECOND
card. One more .card, not a menu system: there is nothing to configure. Built
on E's letterhead because the masthead IS the game's name. boot({splash:false})
for benches and D's harness.
P PAUSE — and the test is the story. The pause lives in frame(), frame() is
only called by rAF, and rAF DOES NOT FIRE IN A HIDDEN TAB. My first probe drove
the real loop and reported success: simT advanced 0 while paused... and 0 while
running. It was measuring a frozen tab, and it would have passed no matter what
I wrote. So the rule is a value: accumulate(acc, raw, paused). Paused DRAINS
the accumulator rather than carrying it — a kept `acc` spends time that elapsed
while you were paused on the first frame after resume, which is a free sixtieth
of a second of storm and exactly how two runs of a deterministic sim come to
disagree. Storm-only, and cleared at every phase change: a pause flag left set
where P is inert would be a soft-lock built out of a comfort feature.
M MUTE — wired, honest, and not advertised yet. C's bus has no tap: createAudio
holds `master` inside skyfx's closure and exposes only unlockAudio(). Asked in
THREADS. `sky.setMute?.(on)` behind a key the splash promises would be D's
night-3 soft-lock with a keycap on it — `rigging.setWorld?.(world)` sat in this
same file for a sprint doing nothing, silently, because ?.() on a missing method
looks exactly like a call. So the UI asks before it advertises, and M lights up
by itself the day C lands the tap.
TOUCH NOTICE. The naive check is the bug: `(pointer: coarse)` asks what the
PRIMARY pointer is, so a touchscreen laptop answers coarse and gets locked out
of a game it can play — worse than the dead canvas it replaces. `(any-pointer:
fine)` asks whether a mouse exists at all. Detection errs toward letting people
in; unknown browsers get the game, not a lecture.
DEV LINE. Out from under B's anchor panel (both were top-left, typesetting on
each other), and off unless localhost or ?dev=1 — a stranger on partly.party was
reading "60 fps · storm 12.3s · debris 4" on the front door.
selftest 338/0/0 (was 335).
Posting before export, per SPRINT13 gate 2.4 ('agree the clip names and band
thresholds with D in THREADS before you export').
The headline is a correction to the brief, measured not argued: _rotOnly drops
Hips.quaternion from every clip at load (player.js:53, applied :239), so a lean
baked into a Mixamo clip is discarded. Parsed the shipped GLB to confirm — 17
clips, 195 channels each, 64 survive. The lean has to be a root pitch in D's
sim, aimed at wind.dirAt(t), because the wind's bearing swings and a clip's
lean axis can't. Same trick as the knockdown and climbY.
Thresholds anchored on Beaufort 7 ('inconvenience felt when walking against the
wind') = 13.9 m/s, which lands storm_01 never leaning, storm_03 gusting through
the line, storm_02 in gale all night. Not tuned to get that.
Mixamo has no wind clips — searched the real API, not assumed.
Gate 1 evidence, posted early because C's model fix and A's export are both
downstream of it.
garden_probe.html flies the sim's own garden loop (skyfx gardenExposure +
gardenHailExposure, stepped the way main.js does) per rig line and prints the
garden HP the sim would report. Browser-only by necessity: skyfx needs
`document` (node throws), so garden prediction can never live in audit.mjs.
The finding, every corner held on rated shackles (the sail's best case):
night 1 gentle no hail bare 97.6 best 99.1 sail worth +1.5
night 2 southerly 0.7 bare 83.7 best 90.1 sail worth +6.4
night 3 buster 0.7 bare 82.6 best 90.1 sail worth +7.5
night 4 wild night 1.3 bare 35.7 best 60.5 sail worth +24.8
night 5 ice night 1.4 bare 0.0 best 27.7 sail worth +27.7
The separation IS hail size, exactly as A's GARDEN_DRAIN comment predicted:
cloth blocks 100% of 1.3+ stones, 74% of pea hail, and rain at 73 degrees walks
under and does nothing. No value of GARDEN_DRAIN can separate a good rig from
none — it scales both by the same factor.
Also found: my own audit's cover% is the SUN shadow, which C's skyfx comment
already called "a number about nothing" during a storm. On the wild night it
predicts the garden BACKWARDS (r = -0.42): the best garden line is p1,p2,p3,p4
at 25% cover / $65 / 60.5 HP, beating a 71% cover / $120 line at 52.8 HP. D's
"$20 rig banked 39/45" and the QA's +$97 are the same fact — I sorted the table
by the wrong quantity.
Flagged to A: the ice night's garden cannot be saved at any price (best 27.7 vs
WIN >= 50), and night 1's sail is worth 1.5 HP.
The probe carries a TEMPORARY marked copy of main.js's private garden constants;
asked A to export the model so audit and sim share one copy. Nothing ships
against the copy — the probe is evidence, not a feature.
Gate 2.4 asks E to agree names+thresholds with D before exporting. Filed:
- Blocker E can't see from a JING5 clone: build_player_anims.py's source
libraries (~/Documents/FBX, 3D=models/animations, mixamo-fetch) do not
exist on this laptop. All three are on ultra. The pack ships built but is
not rebuildable here.
- The asked-for clips aren't on disk anywhere (only Stumble Backwards and
Taking Cover are lean-adjacent, both already shipped), and new Mixamo
fetches need John's manual browser login. Brace and stagger are ALREADY
shipped and wired (TakeCover on KeyC, Reaction/StumbleBack on gusts) —
the lean is the only genuinely new posture.
- Bands measured at the player spawn across all five storms:
calm <8.3 / lean 8.3-17 / gale >=17 m/s, hysteresis +/-1.5. 8.3 m/s is
30 km/h, the same number C's leaves start at. Band ladder lands on the
storm rating ladder.
- Signal is sim.windBase (existing ~4s EMA), not raw wind.speedAt: raw
strobes at 8-18 band flips/min, windBase gives 0-2 per 90s storm.
Sustained = posture, gust = event. Deviation from the prompt's literal
wording flagged to A with the table.
Gate 3.3: collateralFor('gutter') returns {90, 'the gutter'} — canonical in
backyard_01.json beside the reasoning, GLB extras as fallback, per E's
collateralKey chain. House wreck wired on the carport's swap contract
(no-op until lane/e's GLB merges; window_glow state copied). site_02 stays
null: no house, no gutter. hud.js bars + main.js verdict now read
rating × ratingHint, matching sail.js's failure line (B's flag).
selftest 319/0/0.
Gate 3.2: bike_kid_01_v1 on backyard_01's south fence, rotYDeg 180 so the
baked -Z lean lands ON the fence — bars measured to the rail (0.286 m
reach, z 9.69 -> grip at 9.98), tyres 0.31 m clear, between posts. Unpriced
by ruling: the sim can't knock a bike over, and billing an unseen event is
the lie the invoice kills; the windward-fence pose is the honest one.
a.test pins lean direction, standoff band, and the ruling. 318/0/0.
audit.mjs's hand-typed backyard dump goes full-precision: the 2-dp rounding
moved t2 ~5 mm against t1's shelter cone and swung its ice-night peak
4.8 -> 9.3 kN — the node and browser front-ends disagreed by one whole
winnable line until the positions matched to the float. Reconciled by
measurement (node on full floats reproduces the browser exactly).
Re-audit verdict (THREADS, full tables): every night still passes, no retune
proposed — E's numbers stand. backyard: hints move exactly one number (night
2 honest $30 -> $40; fascia appears in zero bed-covering quads so its 0.35
is audit-invisible). site_02: 64/66 -> 23/66 affordable, every cheap carport
line dead, cheapest now $40 honest steel, cheapest >=70% line $45 through a
$30-rated carport post — the trap is priced, not fenced off.
Selftest 319/0/0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gate 3.1: the CUT block from tools/jobsheet verbatim — letterhead, billto,
brief, sched, .void, AMOUNT DUE, terms. Docket numbers shared across the
pair (JOB/INV 1040+night); week.js nights carry an addr. Cards scroll when
taller than the window (safe center + overflow). Both rulings adopted and
pinned by DOM asserts; selftest 317/0/0.
Every windchange event gets an in-world tell: a dark bank standing on the
horizon in the quarter the new wind comes FROM, rising across the 12 s
before the change and clearing after the swing, plus a low distant rumble
layer in the audio. Soft-edged via vertex alpha (the hard band read as a
floating rectangle on the bench). Deterministic off (dt, t): progress is a
pure function of t, azimuth comes from dirAt at a fixed post-change
instant. Night 2 teaches the tell at ~18 s; night 3 shows the same wall at
t=6, which is 'looks like night 2 and isn't' made visible.
dev_skyfx.html: the skyfx bench — one storm, bare ground, scrubbable
clock, so the tell can be judged by eye without playing to night 3.
Two new asserts in c.test.js (window, monotonic rise, direction, mesh
orientation, changeless storm stays clear, bit-exact determinism).
Selftest 322/0/0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five new asserts (Lane E 67 -> 72; selftest 315 -> 320):
- price exists, sits between gnome and carport, keyed to the anchors'
collateral string, wreck carries the same bill and names its twin
- the torn gutter is DOWN in the wreck and UP in the intact house — negative
control run: rebuilt with the ground run at eave height, selftest went red
('gutter_down tops out at y=2.68', 1 failed / 319 passed) while the Blender
verify PASSed the same bad file (plan position unmoved, no box span changed);
restored build round-tripped to identical hashes
- wreck height equals house height (a house does not fall when its gutter does)
- no fascia_anchor_* survives into the wreck
- house pair added to the shared ground-plane test
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fascia anchors have said collateral:"gutter" since Sprint 6 and nobody
priced one — collateralFor('gutter') returns null, backyard_01's house is a
free failure. Proposal rides in the asset like the carport's did: 90, between
the gnome (25) and the carport (180), with the band reasoning next to the
constant. A rules the number.
house_yardside_wrecked_v1: same origin/footprint/facade (shared _house_facade
helper — intact house BIN chunk byte-identical to the committed GLB, only the
three new price extras differ), fascia torn through the anchored span, left
run hanging by its last bracket, right run kinked ON the grass, downpipe out
of plumb, offcuts flat on the lawn. No fascia_anchor_* empties survive — you
cannot re-tie to a ripped eave.
collateral_key is new and explicit: unlike the carport, the structure and the
thing you take have different names, so the key names which collateral string
the value prices.
All 33 GLBs byte-identical across two full factory runs; the house pair also
byte-identical between --only and full runs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
sail.js _checkFailure now fails a corner on load > hw.rating * anchor.ratingHint
(read live from the anchor — dress() mutates in place). Consequence asserted,
not the formula: on site_02's carport line (uniform rated shackles, funnel on,
12 s calm settle) a cb corner blows before any q corner — at DEFAULT tension
(t~29 s into the storm, honest posts hold) and at max. In-suite control pins
the attribution: the same rig with every hint forced to 1 holds 4/4, so the
break is the hint, not the load. Mutation-checked: unwiring the line fails both.
rigging.js weak-link (D's #7): summary.weakest reduces on rating * ratingHint
via _effRating, not bare hw.rating — uniform hardware now flags the genuinely
worst steel instead of the first click. Mutation-checked.
site_audit prices the anchor, not just the steel: tierFor(peak, hint) needs
h.rating * hint >= peak, both front-ends carry ratingHint through (browser off
world.anchors, node dump extended with the live-dumped hints), rows print the
hint. sweep.selftest asserts hints reprice without touching peaks and land
over-ceiling corners in unholdable. Mutation-checked.
Selftest 319/0/0 (315 + 4).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second harness on B's post-ratingHint audit: measures what the STORM
delivers at every dressed anchor — peak speedAt, tPeak, dynamic pressure
(downdraft folded back in), dose, and Pa/hint (the wind-side failure-order
prediction under load > rating x ratingHint). No cloth, no rig, no tension:
independent by construction.
Browser front-end dresses the yard the way the game does (createWorld +
dress) and wires venturi + tree shelters exactly as main.js:446-447 does.
envelope.selftest.js wired into c.test.js, every assert written to fail if
a wiring line is deleted (sweep.selftest's pattern). Selftest 320/0/0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>