Commit Graph

244 Commits

Author SHA1 Message Date
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
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
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
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
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
type-two
a74075cda5 Sprint 11 shepherd: C's venturi correction taken, D's two landmines defused
C AND D BOTH LANDED WHILE I WAS IN GATE 2, AND BOTH CORRECT ME.

VENTURI — reverted to axis 2.1 / gain 1.5. C reached the same physics I did,
independently (|dot| means an axis is a line, mod PI), and then went one step
further and caught my reasoning: -1.08 was never a proposed AXIS. It's
dirAt(40), the southerly's HEADING once the change settles. 2.1 is the GAP'S
ORIENTATION — site geometry, which doesn't move when the wind does. They're
different quantities that sit a hair over PI apart only because the gap was
BUILT along the southerly.

So "measured beats authored" was the wrong rule for this question: I'd have
swapped a geometry fact for a weather fact because they nearly coincide, and
the site's axis would silently mean the wrong thing the day someone authors a
storm from another quarter. "A number gathered from the wrong harness" caught
both of us on the same question from opposite sides in one sprint.

Gain reverted too, and that one I simply jumped: it's a balance lever, B's audit
calls it, and C's standing offer is to drop to 1.35 only if no $80 line survives.
B hasn't run. D then measured the site winnable off honest anchors at 98% — so
the funnel isn't killing the line and the gain stays.

D's LANDMINES, both mine, both in gate 2's path:
 · CALM_STORM loaded only because night one HAPPENED to be storm_01_gentle.
   Gate 2 rewrote every night entry. Drop gentle and windTime() throws every
   non-storm frame on "Cannot read properties of undefined (reading 'duration')"
   — naming neither storms, nor calm, nor the week. Now explicit in the preload,
   and the assert calls stormsToPreload() with a gentle-free ladder, because
   asserting it against the shipped NIGHTS passes either way. That's how it hid.
 · boot({site}) was a documented lie that failed INVERSELY — markers on the
   requested yard, world on the week's. Honoured through the week now (seek to
   that site's night), because a site without its night is half a game: no
   client, no brief, no storm, no bank. Plus opts.bank, so D can boot the corner
   block at the $237 they measured instead of night one's $80 and OFF THE JOB.

RATINGHINT — D is right that nothing reads it, and the naive fix is worse than
the bug: honest posts are built from site JSON, never touch adoptAnchor, so
their ratingHint was undefined — and `load > hw.rating * undefined` is
`load > NaN`, always false. The strongest anchors would have become UNBREAKABLE
while the carport became the only thing that can fail: the trap would look like
it works, for the wrong reason, and read as a successful playtest. Every anchor
carries a real number now (DEFAULT_RATING_HINT), pinned, so the wire is safe
before it's made. The wire itself is sail.js — B's — and the ruling is in THREADS.

selftest 306/0/0 (was 304).
2026-07-17 17:51:30 +10:00
type-two
4095df8b10 Lane B S11: audit the venturi, land setWorld/setBudget, revive the harness
site_audit's headless false-negative is fixed by the browser front-end
(A's loadSite/createWorld landed in main — that was all it waited on), and
fixing it surfaced a worse bug underneath: the sweep built its wind from the
STORM def alone and never called setVenturi, which main.js:424 does at every
site load. The venturi is SITE data, so every site_02 audit ever run — including
the numbers quoted at C in SPRINT10 — was of a corner block with no gap. The
SPRINT6 p1=7.4kN failure inverted: there the tool called a good site unriggable,
here it called a mean site cheap.

- sweep.js: setVenturi on both winds (the gap doesn't switch off for the settle);
  both front-ends pass the site's funnel and print it, so a funnelled run is
  legible as one.
- sweep.selftest.js (new, wired into b.test.js): the auditor gets an auditor.
  Funnel must strictly raise every corner; a funnel-less site must be
  byte-identical. Verified it fails when the setVenturi calls are deleted.
- rigging.js: setWorld(world) + session.setBudget(n)/setAnchors() — A's two asks.
  Markers are rebuilt (the anchor set changes across sites) and disposed; stale
  picks die with the old yard. Deletes A's "one private touch" into this module.
- dev_rigging.html: dead since SPRINT10 (called createWorld without a site, which
  now throws) and nothing noticed, because a dev page has no selftest to go red.
  Fixed, dressed, and given the site switch it should have had (N = next yard).

Verdict for C/A, in THREADS: site_02 PASSES with the funnel on — 64 of 66 lines
affordable, cheapest $20. The funnel kills nothing, so the gain does NOT need
dropping and E's tree stays put. It's a reach problem, not a gain problem: the
throat's radius is 5 m and the bed centre is 6.08 m away, so the gap screams over
the carport at +50% and reaches the garden at +0.0%.

selftest 300/0/0 (296 + 4 new).
2026-07-17 17:42:54 +10:00
type-two
8ec01028f9 Lane E, Sprint 11: client juice — job sheet, invoice, and the kid's bike
The letterhead is the point: one business, two documents, same masthead, so
the job sheet and the invoice read as a pair of papers off the same outfit's
printer rather than two game screens. DESIGN.md has opened with "you run a
small landscaping outfit" since day one and it has never been on the glass.

tools/jobsheet/ — paste-ready for Lane A between CUT markers, purely additive
(.card/.row/.ledger untouched), rendered to four PNGs before handover, each
state addressable as ?v=. The ledger already WAS an invoice; it just never
said so. The line worth keeping is .void: a bonus that was on the job sheet
and didn't survive the night, struck through in the same column as the money
that did — a bonus you never watch fail is one you never learn to protect.

Two design calls left as A's with the reasoning beside them, same as the
carport's $180: the business name (proposal: HARD YARDS) and the ABN
(sequential digits on purpose — a plausible one could be a real business's).

bike_kid_01.glb — 1.15 x 0.33 x 0.74 m, 700 tris, lean baked in because a
bike doesn't stand up on its own. Two bugs caught by LOOKING at the render,
neither catchable by the existing suite: a comment claiming a step-through
above a diamond frame, and a fork with negative rake that drew head tube and
fork as one pole through the front wheel. Both passed the Blender verify —
a bounding box can't answer "is this the right shape".

The lean is an export-time axis fact (+Y in Blender is -Z in three.js), so
e.test.js now pins it in browser coords. Proved the assert fails: rebuilt at
lean=0 and Blender still said PASS while the selftest went red.

selftest 299/0/0. 32/32 GLBs byte-identical across three full factory runs.
2026-07-17 17:39:58 +10:00
type-two
8b75674d7e THREADS: Sprint 11 gate 2 — the job sheet, the clean bonus's honest price, and two bugs only looking caught 2026-07-17 17:38:03 +10:00
type-two
6bcc2540cf Sprint 11 gate 2: the job sheet — five nights become five jobs
DESIGN.md has opened with this since line 3 — "every callout is a different
site, a different client" — and the game had the site and not the client. The
forecast card is a job sheet now and the aftermath is an invoice.

THE WEEK, once it was asked to name a client, mostly wrote itself: four nights
are the same yard, so they're one client on retainer while they're away (the
Hendersons, in Cairns from Friday — SPRINT11's own line). Night 3 is a
different SITE, so it is necessarily a different CLIENT: a short-notice callout
to a corner block you've never rigged, on the night the change comes early.
That isn't flavour bolted on, it's what `site: site_02_corner_block` already
meant, finally said out loud.

BUDGET Y IS THE FEATURE, not the letterhead. DESIGN.md's brief is "client wants
X, budget Y, forecast Z" and this card had Z alone. The fee has existed since
Sprint 8 and the player has never seen it until the money was already spent —
you chose what to rig without knowing what the job was worth, which is a reveal,
not a decision. The sheet quotes base + garden + clean up front, and a test pins
that a perfect night pays exactly what the sheet advertised: a job sheet that
over-promises is the game lying on paper.

THE CLEAN BONUS is the new money, and it's the one number in week.js that isn't
measured. It gives the invoice three axes that mean three different things —
you turned up, their garden lived, you didn't wreck the place — and it's NOT
gated on winning, because a lost night where you broke nothing of theirs is a
different fact and deserves its own row. It also makes the carport bite twice:
180 + 20 = 200, inside E's own "over ~250 is a silent game over" ceiling.
Measured: it adds ~$100 to a competent week (385 -> 485), which makes
BROKE_BELOW's known runaway worse. I did NOT pay for it by quietly trimming
feeFor or gardenBonusMax — those carry measured evidence, and re-tuning someone
else's evidence to fund my own feature is the move this repo keeps catching. If
gate 3 says the bank runs away, this is the lever to cut first and it's data.

TWO BUGS, BOTH CAUGHT BY LOOKING AT THE CARD, neither by a test:
 · "THE HENDERSONS" over "The Backyard — Your own place". The blurb and the
   letterhead called each other liars on screen. The blurb gave way: "your own
   backyard" is a leftover from when the game had one yard, and DESIGN.md never
   wanted it. The line's job survives — night 1 is the tutorial because you know
   this yard, now from having worked it for years rather than owning it.
 · C's `lead` is a 0..1 haze dial (confidence = 1 − lead), not "nights out". I
   read it as nights, passed 1, and the sheet advertised "forecast confidence
   0%". Now 0.6, because C's own hail rule calls L > 0.55 too distant to promise
   ice, and tomorrow is exactly that. C: the slot is mine, the number is yours.

Lane C's forecastLines(def, lead) finally has a caller above 0 — two sprints
after they built it. Lane E: .letterhead / .brief / .jobsheet / .tomorrow are
structure only and yours to style.

selftest 304/0/0 (was 300).
2026-07-17 17:37:18 +10:00
type-two
f6d60c5a9b Lane D: verified A's gate-1 on lane/a — enum + carport bill land; ratingHint residue, CALM_STORM landmine
Played lane/a 0f288be. Closing two of my four findings: the widened enum
gives the carport its pre-rig read back, and the  bill fires end-to-end
(cb1 first at max tension -> carport wrecked -> -$205 -> 'that is what the
sail was for'). Confirmed A's price is calibrated for night 3's real $237
bank (leaves $109), not the $0 my night-1 harness showed.

Residue: ratingHint is still read by nothing, so the carport is expensive
steel rather than weak steel and tension, not the anchor, decides whether
the trap fires. Needs a ruling since E's tests pin those ratings.

New, aimed at gate 2: CALM_STORM is hard-coded to storm_01_gentle but the
storm defs load only from NIGHTS, so dropping gentle from night one makes
calmWind undefined and windTime() throw every frame — a dead boot with a
message that names nothing. A is about to rewrite every night entry.

Night 3 remains soft-locked; lane/b has not moved.
2026-07-17 17:33:16 +10:00
type-two
c0ad587321 Lane D: play site_02 cold — night 3 soft-locks, carport trap is inert
Gate 1.3, four sprints owed. Played the real path (nights 1-2-3, mouse).
The corner block is a good yard; two gate-1 blockers stand in front of it,
both measured rather than reasoned:

- Night 3 is unplayable. rigging.setWorld is undefined, so main.js:450's
  optional call is a no-op and createRiggingUI's markers (rigging.js:254)
  stay bound to the boot-time backyard. On night 3 the world/session/rig
  re-point correctly but the only clickable markers are the backyard's, at
  backyard coordinates. Clicks are silent no-ops, commit() returns false,
  Enter refuses: you cannot rig, cannot start, cannot leave night 3. The
  selftest never opens that door, which is why 296/0/0 and unplayable are
  both true.

- The carport trap has no teeth. anchor.ratingHint and anchor.collateral are
  written by world.js:556-557 and read by nothing; sail.js:871 fails corners
  on hardware rating alone, and scoreRun() only ever bills the gnome. 46 m2
  hung off the carport through the funnel pays +$82 at garden 83%.

Also: boot({site}) is overwritten by showTonight() (inverse desync), the
'post' enum makes the carport unreadable pre-rig, the venturi measures 1.5-1.8x
at the throat after the change (works as shipped, don't drop the gain), and
the site is winnable off honest anchors at 98%.

THREADS-only; no code touched on lane/d this sprint.
2026-07-17 17:25:09 +10:00
type-two
0f288beff3 THREADS: Sprint 11 gate 1 rulings — venturi (and a correction to my own probe), enum, carport price 2026-07-17 17:20:59 +10:00