Commit Graph

302 Commits

Author SHA1 Message Date
type-two
08f0e09ff0 Lane D: gate-1 and gate-2 verdicts — played through the real UI; bench venturi landmine
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>
2026-07-18 12:31:28 +10:00
type-two
b70699189c Lane C S13 gate 2.3 + D's sliver: ambient leaves from 30 km/h; hail pool hides at zero
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>
2026-07-18 11:51:26 +10:00
type-two
c2a19df7d5 Lane C S13 gate 2.2: the front wall grounds on the horizon, ends feathered
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>
2026-07-18 11:30:48 +10:00
type-two
a861151a4a Lane C S13 gate 2.1: the storm grade — the night finally looks like a night
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>
2026-07-18 11:21:53 +10:00
type-two
8a3dc323bc Lane C S13: sky.setMute(on) — the tap A's M key was waiting on
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>
2026-07-18 11:11:39 +10:00
type-two
a495fcdba6 Lane C S13 gate 1: the shadow model is right — the LINE was wrong; garden_bench proves it
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>
2026-07-18 11:00:09 +10:00
type-two
94624cddcc Lane D: status + garden-harness landmine note (parameter shadow + skipped attach)
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.
2026-07-18 01:52:10 +10:00
type-two
f3136093fc Lane D: THREADS — root lean landed, closes E's gate-2.4 handoff
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.
2026-07-18 01:35:02 +10:00
type-two
7758b65331 Lane D: the player leans into the wind (gate 2.4 — as a root pose, not a clip)
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.
2026-07-18 01:33:47 +10:00
type-two
6cf5b07f32 THREADS: Sprint 13 gate 3 + 2.5 landed; M-mute ask to C; gate-1 rulings parked on C's diagnosis 2026-07-18 01:22:31 +10:00
type-two
701403ab1d Sprint 13 gate 2.5: two camera fixes — spawn framing, aftermath cloth-swallow
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).
2026-07-18 01:18:29 +10:00
type-two
666b378740 Lane B S13: rule D's fuse line — depth to keep; audit is blind to it (flag)
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.
2026-07-18 01:14:37 +10:00
type-two
05337c2989 Lane B S13: consumable spares — close D's free-infinite-spares hole (count half)
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.
2026-07-18 01:09:51 +10:00
type-two
7048ad6482 Lane B S13: gate-3 smalls — rig.t was a determinism bug, + prep readouts
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).
2026-07-18 01:04:02 +10:00
type-two
7053623fde Lane D: diagnoses for C (hail sliver is skyfx, not my ladder) and B (free infinite spares)
- 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).
2026-07-18 01:01:51 +10:00
type-two
d7658fb7af Lane D: ladder survives a mid-carry knockdown, and the prompt names the right bracket
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).
2026-07-18 01:00:01 +10:00
type-two
91bc391633 Lane E, Sprint 13: no wind clip — the pipeline can't supply one, and the lean was never a clip
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.
2026-07-18 00:45:22 +10:00
type-two
6e5f4b5222 Sprint 13 gate 3: the front door — splash, pause, touch notice, Enter pinned
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).
2026-07-17 23:59:27 +10:00
type-two
d5c16534c1 Lane E: wind-clip proposal to D — the lean can't be a clip, names + Beaufort thresholds
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.
2026-07-17 23:42:32 +10:00
type-two
e839f5afe3 Lane B S13: garden_probe — the sail's value is the hail it catches
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.
2026-07-17 23:41:41 +10:00
type-two
8d0045bb29 Lane D: clip contract for E — measured wind bands, windBase signal, and the fetch blocker
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.
2026-07-17 23:40:28 +10:00
type-two
5859bc83fa QA pass: fix Enter storm-skip (deployed), log findings; Sprint 13 plan + prompts 2026-07-17 23:26:47 +10:00
type-two
c9a5658fc8 Add deploy_hardyards.sh: partly.party deploy per the games-VPS pattern 2026-07-17 21:39:09 +10:00
type-two
fee687d550 Retitle the game page: HARD YARDS (release name; SHADES stays the repo name) 2026-07-17 21:37:23 +10:00
type-two
688e821fcb Sprint 12 integration: merge all lanes (335/0/0), skyfx effective-rating creak, [I] entry 2026-07-17 21:17:45 +10:00
type-two
4dce82268b Merge remote-tracking branch 'origin/lane/d'
# Conflicts:
#	THREADS.md
2026-07-17 21:13:57 +10:00
type-two
28ef4ef138 Merge remote-tracking branch 'origin/lane/b'
# Conflicts:
#	THREADS.md
2026-07-17 21:13:57 +10:00
type-two
3df0d0c5d0 Merge remote-tracking branch 'origin/lane/c'
# Conflicts:
#	THREADS.md
2026-07-17 21:13:57 +10:00
type-two
aff0020148 Merge remote-tracking branch 'origin/lane/e'
# Conflicts:
#	THREADS.md
2026-07-17 21:13:57 +10:00
type-two
ec45e7b465 THREADS: Sprint 12 replay — trap fires at default tension, ladder trip live, weak-link honest, C's wall judged; fuse-line and night-2 cover discrepancies flagged 2026-07-17 21:12:33 +10:00
type-two
38b6fe9c87 Rule the gutter $90 (E's proposal adopted); teach collateralFor/wreckStructure the house; action B's effective-rating flag
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.
2026-07-17 20:45:49 +10:00
type-two
807de80969 Wire the kid's bike: site key + loader, gnome pattern; decline the $60
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.
2026-07-17 20:34:47 +10:00
type-two
2b1c8cf15e Re-audit both yards with hints live; fix the dump's 5 mm lie; THREADS tables
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>
2026-07-17 20:32:33 +10:00
type-two
3d89ce8fe8 Paste E's letterhead/invoice kit into hud.js; rule HARD YARDS + sequential ABN
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.
2026-07-17 20:27:18 +10:00
type-two
c9ca211a51 THREADS: first cross-harness readings vs B's wiring — prediction 1 confirmed, hints bit-identical, timing false-alarm pre-empted
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:26:56 +10:00
type-two
f9ce210218 THREADS: gate 3.4 landed — change-front tell in, D flagged to judge, lull deliberately not faked
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:25:25 +10:00
type-two
e5c0e3ffee skyfx: the change announces itself — a front wall in the sky (gate 3.4)
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>
2026-07-17 20:24:51 +10:00
type-two
a52e2377fb THREADS: gutter price proposal ($90) for A's ruling, wreck contract + wiring seam, negative-control receipt, ratings position for B/C's audit
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:24:17 +10:00
type-two
b7f065cd57 e.test: pin the gutter price band, the DOWN/UP swap truth, and anchor absence in the wreck
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>
2026-07-17 20:21:40 +10:00
type-two
4d1a2d7aa9 Gutter priced and wrecked: GUTTER_COLLATERAL=90 baked with reasoning; torn-gutter house wreck, carport pattern
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>
2026-07-17 20:21:24 +10:00
type-two
9700b40a30 Wire ratingHint into the failure threshold: the ratings are real (A's ruling)
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>
2026-07-17 20:19:01 +10:00
type-two
375e7a29ce THREADS: storm-side failure envelope posted (gate 2.4) — week measured, predictions for B's re-audit
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:12:11 +10:00
type-two
8208c80d78 storm_envelope: the wind side of the failure envelope, per anchor (gate 2.4)
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>
2026-07-17 20:10:17 +10:00
type-two
8decac0626 README: point at SPRINT12, document per-clone launch.json ports 2026-07-17 18:09:41 +10:00
type-two
e99f029206 Sprint 11 integration: merge all lanes, wire leadFor + setBudget, untrack launch.json; Sprint 12 plan + prompts 2026-07-17 18:08:32 +10:00
type-two
7791299ef0 Merge remote-tracking branch 'origin/lane/d'
# Conflicts:
#	THREADS.md
2026-07-17 17:59:01 +10:00
type-two
d194594817 Merge remote-tracking branch 'origin/lane/b'
# Conflicts:
#	THREADS.md
2026-07-17 17:58:25 +10:00
type-two
f696343fbf Merge remote-tracking branch 'origin/lane/c'
# Conflicts:
#	THREADS.md
2026-07-17 17:58:14 +10:00
type-two
f4f93bf621 Merge remote-tracking branch 'origin/lane/e'
# Conflicts:
#	THREADS.md
2026-07-17 17:58:03 +10:00
type-two
7f08ea0afa THREADS: shepherd — C's axis correction taken, D's landmines fixed, ratingHint ruled real 2026-07-17 17:52:15 +10:00