Commit Graph

272 Commits

Author SHA1 Message Date
type-two
ba062cf679 assetcheck/look.html: the verify that answers "is this the right shape"
Dims, tri counts and node names all pass on a bike leaning the wrong way and
a gutter that never fell. The Blender contact sheet is close but renders
through Blender's view transform — it showed this sprint's jacaranda as
near-white lavender when the material is #8C7FC0, so a colour call made there
is a call about Blender, not about the game.

Smallest thing that answers the question: the game's own vendored three.js,
daylight, a ground plane, and the 1.7 m ref capsule beside whatever you name.

    /tools/assetcheck/look.html?a=swing_set_01,tree_jacaranda_01&cam=10,4.5,22

It caught two things this sprint the numbers could not: the lilac reads lilac
in the real renderer (the thumb was lying), and the swing set's wreck lands on
+Z with a 2.9 m reach against a 0.95 m standing footprint — which is now baked
as a placement extra and asserted.

Sized at draw time, every time: a tab that boots hidden reports 0x0 and you
get a correct scene rendered into nothing. That cost me the first screenshot
of this very page, so it is in the MANUAL gotcha list where it already was.
2026-07-18 16:01:39 +10:00
type-two
b9b64604b3 Palette audit + two temptation props: honest extras, in the factory
SPRINT14 gate 3.1. The editor is about to offer these GLBs to an author by
name, which turns every silent field in them into a trap for whoever places
one. Audited the placeable set, fixed the gaps in the factory (nothing
hand-edited), and added the two props D needs to author a yard worth playing.

THE AUDIT, and what it found:

- Four `*_anchor` nodes carried no rating_hint at all — door_anchor,
  pickup_anchor, grip_anchor, window_light_anchor. adoptAnchor does
  `rating_hint ?? 1`, so silence there does not mean "unrated", it means
  RATED PERFECT: a shed door skin outranking a concreted post, conjured out
  of a missing field. They now deny it explicitly (`tie_off: false`).
- The carport's price resolved only by coincidence: collateralFor() matches a
  STRUCTURE whose site id equals the anchors' collateral string, and site_02
  happens to name its structure "carport". The editor will generate
  "carport_2" for the second one, and the trap would have gone free — the
  gutter bug reborn in the sprint meant to bury it. The GLB now carries
  `collateral_key`, like the house does for "gutter". Lane A: the runtime
  half is yours (collateralFor could fall back to it).
- The trampoline is unpriced, and now says so rather than omitting it.

Three new rules make the audit permanent, and each is proven able to fail:
no silent anchors, no collateral string nobody prices, no baked anchor_type
outside the checked ANCHOR_TYPE enum. Rule 2 found the carport gap on its
first run — an unfaked negative control.

THE PROPS:

- swing_set_01 (+ wrecked) — the palette's honest middle. It had a ceiling
  (gum fork 1.0) and a trap (carport beam 0.22) and nothing in between. Two
  apex anchors at 0.45, typed `swing_frame` (NOT `post`: the type string is
  what the player reads before committing, and "post" promises concrete),
  and a crossbar that is the most anchor-looking object in the game and is
  not one. Priced at $140 — proposal, reasoning baked beside it, A rules the
  number: gnome 25 < gutter 90 < swing 140 < carport 180.
- tree_jacaranda_01 — a second species whose LADDER is the feature.
  Gum: 1.00/0.88/0.76, climb freely, pay 24%.
  Jacaranda: 0.95/0.52/0.40, climb at all, pay 58%.
  That makes tree choice a real decision instead of "is there a tree".
  Unpriced by ruling, same as the bike: no limb-failure event exists to see.

Both wreck/orientation claims are measured in browser coords, not reasoned —
`wreck_falls_toward: "+Z"` is baked AND asserted against the mesh.

Selftest 376/0/0. Two consecutive full factory runs: 44 files byte-identical.
2026-07-18 16:01:27 +10:00
type-two
f02f91cabc Sprint 14 plan + prompts: the yard editor — sites are makeable 2026-07-18 14:50:26 +10:00
type-two
f13aad6411 docs/MANUAL.md: dev manual — tool map, asset + site cookbooks, earned gotchas 2026-07-18 14:35:20 +10:00
type-two
03c6cd1c09 Sprint 13 integration: merge all lanes (362/0/0), apply B's pre-authorized gardenfly resolution, [I] entry 2026-07-18 14:30:13 +10:00
type-two
edb3377818 Merge remote-tracking branch 'origin/lane/d'
# Conflicts:
#	THREADS.md
2026-07-18 14:26:16 +10:00
type-two
646efa7244 Merge remote-tracking branch 'origin/lane/b'
# Conflicts:
#	THREADS.md
2026-07-18 14:26:16 +10:00
type-two
68edc607e7 Merge remote-tracking branch 'origin/lane/c'
# Conflicts:
#	THREADS.md
2026-07-18 14:26:16 +10:00
type-two
9970abc474 Merge remote-tracking branch 'origin/lane/e'
# Conflicts:
#	THREADS.md
2026-07-18 14:26:16 +10:00
type-two
d2b9a31363 THREADS: integrator handoff — B's two-chain disagreement pin resolves by B's own instruction; verified 357/0/0 on scratch 2026-07-18 14:25:21 +10:00
type-two
cc9849d538 Sprint 13 gate 1.4: the played run is the ruling's centre — canon written, pin labeled sim-chain, THREADS
- Played night 4 on the pinned line through the real UI (D's protocol):
  hp 55.7 WIN, 2/4 lost, pyrrhic verdict — the ~8 gap to the sim chain is the
  t=38 crate + ponding landing on p2's 4% margin. Ruled deliberate: the wild
  night sells no clean line; the pyrrhic win is the intended reading.
- Site block records _playedReference; a.test pin re-labeled as the
  deterministic sim-chain tripwire whose margin is what keeps the PLAYED
  night over the win line. Band 18-45 ruled an availability floor (B's 1c).
- THREADS: full second ruling, night-5 canon, Sprint-14 notes.
2026-07-18 14:18:37 +10:00
type-two
80b53e9547 Sprint 13 gate 1.4: NIGHT 5 RULED — the garden is beyond saving BY DESIGN, and the verdict teaches it
- week.js night 5 carries gardenBeyondSaving: true with the measured case
  (bare 0.0, best buyable 27.7 vs WIN>=50 — B's game-true table, my probe
  agreeing) and the arc argument; nightAt defaults the flag FALSE so new
  nights can never inherit the excuse. Brief levels with the player up front.
- verdictFor honours it on a LOSS only: no more 'the hail fell where your sail
  wasn't' on a night where no placement wins; a 4/4 hold is credited as the
  whole job, a torn sail is still named and owned. A win ignores the flag (a
  stale flag must never eat a real win). Asserted both ways + mutation notes.
- GARDEN_DRAIN untouched, per the standing ruling; icenight hail budget also
  untouched — the loss is the design, so the surfaces now say so.
2026-07-18 14:11:58 +10:00
type-two
1c0fcc757b Sprint 13 gate 1.4 RE-RULED on B's skew finding: the pin measures the game now, and the target is restated
- a.test's separation flight had the phantom 12 s settle B caught (SailRig
  samples wind at its INTERNAL clock; the storm flew 12 s off the authored
  curve). Settle deleted — commit->attach->storm from t=0, C's corrected bench
  pattern; venturi wired from site data so the flight can never assume a yard.
- Game-true the line reads 63.6, not 68.3 — FULL (>66) was met by the harness,
  not the game, and the re-sweep says nothing buyable reaches 66 (the twin-post
  covering quad tears at 6.9 kN). Target RESTATED to the game's real stakes,
  with headroom: held >60 and 0/4 (win line 50; measured 63.6), bare <50
  (35.7), separation >=25 (27.9). Field names kept — B's gardenfly reads them.
- DESIGN CANON in the site block: the wild night's garden is WON SCARRED,
  never FULL; the week's arc is full/full/full, won-tattered, beyond-saving.
- p5 _why table corrected to game-true numbers (first sweep ran ~4 HP hot).
2026-07-18 14:11:58 +10:00
type-two
b53d4c50f7 Lane B S13: THREADS — audit rewrite receipts: agreement table, the 12s-skew finding, the re-posted pay table, two retune asks for A
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 13:52:06 +10:00
type-two
e1e14018bf Lane B S13: THE AUDIT REWRITE — the audit predicts the garden, on C's corrected picture
The scoring quantity is the FLOWN garden state now, not static cover%.
New gardenfly.js: windForSite (C's shared builder — venturi + shelters, one
copy in the repo) → real attach (throws on D's skipped-attach trap) → skyfx
exposure → garden.js, per candidate line; cover% demoted to a labelled
geometry diagnostic. audit.html flies every affordable line + bare bed and
judges the site's pinned separation block on the block's own storm; audit.mjs
(node) keeps fast winnability, gains p5 in its verified dump, and points at
the browser for garden truth.

On C's correction, three structural adoptions:
- windForSite everywhere (three harnesses independently mis-built site wind;
  no fourth copy);
- LIVE anchors via the re-pointable wind proxy — the audit's own frozen-sway
  remap was C's landmine 2 wearing my file's name (audit.html:69);
- the MARGIN rule (AUDIT.MARGIN = 0.15, one copy): every row prices twice —
  $hw holds vs $cleanHw with >=15% headroom — and only clean lines are
  winners; verdict code 'marginal-only' when the budget can only buy the
  knife edge (D's 39.8-TATTERED wild night, C's dead 91.9 headline).

Also game-true flight: the phantom 12s calm settle is GONE from the sweep
(commit->attach->storm is one keypress; the settle skewed every storm sample
12 s off the authored curve — SailRig samples wind at its INTERNAL clock).
That skew is not academic: it is a.test's separation-flight harness too, and
gardenfly.selftest now pins BOTH chains (game-true 63.8 tattered vs skewed
68.4 'full' on the pinned p5 line — the disagreement is flagged to A in
THREADS, not overruled here). The pinned recipe also sweeps regardless of the
18-45 band (it is 45.9 m² — the band predates p5; flagged to A).

Selftest 356/0/0 on the scratch merge (b+a+c@45bdc2d). Mutation-checked:
MARGIN=0 reddens the margin test (node); dropping siteDef from gardenfly's
windForSite call reddens the venturi test (browser, exact test named).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 13:50:15 +10:00
type-two
25eefccc69 Lane B S13: kill the marked fake — garden model comes from A's garden.js everywhere
garden_probe.html drops its TEMPORARY COPY of HAIL_WEIGHT/RAIN_WEIGHT/
GARDEN_DRAIN and flies main.js's own createGarden (imported from garden.js),
so the probe's hp/state/damage-split are the model's, not a re-derivation.
balance.test.js's local GARDEN_DRAIN/W_HAIL/W_RAIN copy re-points at the
garden.js exports (A's flag on balance.test.js:38) — a future retune now
moves this suite the day it lands instead of drifting silently.

Selftest 351/0/0 on the scratch merge (b+a+c) — the constants are identical,
so no number moved; the SOURCE moved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 13:50:15 +10:00
type-two
45bdc2d079 Lane C S13 correction: the bench flew site_02 funnel-OFF and tree-FROZEN; wild-night headline dies
D's live replay caught landmine 1: bench.js read def.wind.venturi off the
STORM def where a venturi never lives (it is SITE data, main.js:453), and the
probes never attempted it — every site_02 bench number was funnel-off, the
third harness to make B's Sprint-11 mistake. Verifying the fix exposed
landmine 2: the tools remapped anchors to a static sway:()=>pos, freezing the
gum tree whose sway is the dynamic load a tr1 rig eats (ablation on the $80
line's q4: 1.02 funnel-off/static, 1.03 funnel-on/static, 1.24 funnel-on/real
anchors). Backyard numbers matched D's UI to the decimal precisely because
p1..p4 are posts — both landmines are no-ops there.

Fix: windForSite(stormDef, siteDef, anchors) in weather.js is the one shared
wind builder (venturi + shelters, exactly main.js's wiring); all garden_bench
tools build wind through it, fly world.anchors themselves via a re-pointable
wind proxy, print the venturi in their headers, and drop the phantom 12 s
settle (commit->attach->storm is one keypress, and the settle skewed storm
sampling via SailRig's internal clock). c.test.js pins the helper with B's
strictly-raises assert against the real shipped funnel (mutation-checked).

Corrected numbers: $80 line wildnight 91.9 with q4 at 1.24-vs-1.2 rating - a
knife edge the real game falls off (D UI: q3+q4 break, 39.8 TATTERED; D's UI
is canonical there, and a corner over its rating is not a safe line either
way). Buster verdict stands (95.2 bench / 97.9 D live). Icenight 9 DEAD 2/4.
Bench still under-reads UI peaks ~5-15% on site_02 - residual in the pool,
stated in THREADS. Separation-target site_02 example withdrawn to A.

Selftest 341/0/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 13:08:21 +10:00
type-two
b5643922a4 THREADS: gate-1 rulings (separation target amended+pinned, p5, GARDEN_DRAIN stays), M live, garden.js export, phantom-sail filed 2026-07-18 12:46:08 +10:00
type-two
482803a480 Sprint 13 gate 1 RULINGS: p5 the clothesline post; the separation target, amended and PINNED
- backyard_01 gains p5 (5.5,-2) h2.6, honest steel NE of the bed: before it the
  yard could not beat tattered on the wild night at any price (covering quads
  need a fascia corner capped at 2.27 kN; the holding line ceilings at 61.3).
  Swept against the $80 wallet; position/height table in the site JSON.
- Separation target adopted AMENDED (ruled on D's funnel-corrected picture):
  wild night, best buyable line FULL (>66) and WIN; bare bed LOSES (<50).
  Pinned as site data (backyard_01.separation names the $75 recipe
  p5=rated + p1/p2/p3=shackle) and FLOWN by a.test through the real chain:
  shop at START_BUDGET -> commit -> attach -> skyfx -> garden.js. Measured
  68.3 FULL / 0 lost vs bare 35.7 (sep 32.6).
- Full cover stays unbuyable (covering quads still pull 6.4-6.9 kN), the
  >45 m2 floor holds, and the ice night still refuses $80 (needs $105 -
  p1/p2 miss their shackles by 0.05 kN) - night 5 stays bank-funded canon.
- New asserts: M reaches the real bus / is only advertised when real;
  anchor census now 13 (5 posts). Selftest 341/0/0.
2026-07-18 12:44:54 +10:00
type-two
7acee5437b Sprint 13: garden model exported as garden.js (B's gate-1.2 blocker) + applyMute(sky,on) as a value
- 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.
2026-07-18 12:44:37 +10:00
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