Selftest 275/0/0. balance.test: **$80 · shade cloth · p1,p2,p3,p4 ->
hp 63, 0/4 lost — CLEAN WIN.** First time the wild night has been
winnable without sacrificing the rig. C found the line and the $10 gap;
this spends both levers together, as they only work together.
Fabric is priced at $0 BOTH WAYS, and that is a finding, not laziness.
SPRINT6 asked me to price the difference; I measured it and there is no
price at which membrane is a sane buy — it is dominated:
buys: +26% hail, but only on the PEA-hail nights (hailBlockFor(0.7,
0.3)=0.74), which are the easy ones — on storm_02 (1.3) and the
ice night (1.4) a knit already blocks 100%, because a 2 cm stone
can't pass a 2 mm weave (C's ruling). Plus rain, at 0.25 of the
drain vs hail's 5.0 — ~5% of the damage.
costs: double the wind load, and it PONDS.
Charge for that and you've shipped a trap. Free, it's DESIGN.md's actual
words — "fabric choice is a forecast bet" — and the bet is real: cloth on
the windy nights, membrane when the hail is small and the wind is mild.
If A ever raises rain's weight, membrane earns a price.
Wired C's hailBlockFor into skyfx.gardenHailExposure, which nothing
consumed — without it the fabric choice would have been cosmetic, which
is the bug class this repo keeps finding.
I did NOT reproduce C's numbers, and the comment says so rather than
repeating them. C reported p1 dropping to 1.08 kN (under a carabiner's
1.2) with cloth + 0.40. I measure 1.27, and p1 never crosses under 1.2 at
any combo. The line still wins on a $5 carabiner — but because 1.27 vs
1.2 is a 6% overshoot and breaking needs 0.4 s SUSTAINED, not because the
corner got under its rating. The margin is TIME, not headroom, and it's
thinner than the table implies: anything that lengthens storm_02's gust
HOLDS could take it. Flagged for C in THREADS.
The old t2 quad stays as the sacrifice-play control (hp 56, 2/4 lost) —
now a worse bet the shop will happily sell you, rather than the wild
night's only outcome.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The water arc, carried since Sprint 3. Rain (Lane C's rainMmPerHour x the
exported RAIN_TIME_COMPRESSION, never hardcoded) lands on each node's
horizontal projection, runs down its steepest of 8 neighbours, and pools
where it can't get out. A flat sail's belly is a basin water flows into
and can't climb from; a hypar drains along its saddle ridge to the low
corners and off — so ponding cannot pincer §7, and measured on real yard
quads a flat rig holds 12 kg/m² vs a twisted rig's 1.7.
The 8-neighbour graph is load-bearing: a 4-way one can't follow the
saddle's diagonal ridge, so it trapped water in the gravity belly and a
hypar pooled as much as a flat sail. Steepest-GRADIENT descent (not
steepest drop) because a diagonal is √2 farther.
Gate 1, in asserts: a flat carport rig under a capped wind survives dry
(4/4) and dies wet (a corner at t=85s) — the control isolates water from
wind — and the broom saves it. Plus dump-on-break, dump-on-tension-up
(the turnbuckle counter-play), mass conservation, and a belly-tear safety
valve at 4 m of sag.
API for D and A, frozen in contracts.js: pondMass(), pondCentroid(),
drainPondAt(node, dt) -> kg-on-your-head. session.reset() for A's "play
again".
On D's tn-1.04 cliff: investigating it IS what surfaced the ponding load
regime. The 10 kN "spike" is real physics, not solver divergence — a
155 m² flat sail holding 2100 kg of water genuinely pulls ~21 kN, stays
finite, and tracks the water. So no physics-altering clamp (the
displacement clamp I tried moved the thesis 39->34%); instead an opt-in
`watchDivergence` tripwire that throws with a repro above 80 kN, live in
every selftest rig and never false-tripping, and a belly-tear that bounds
the runaway physically. Full writeup for D in THREADS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
RiggingSession already held every rule and was tested headless; this is
only the hands. Click an anchor to rig it, click again to cycle its
hardware, shift-click to pull it off for a full refund, [ and ] for
tension, S for the spare. RMB is left alone — that's the camera's orbit.
Enter stays Lane A's: the phase machine calls commit(), which goes
through main.js's rigSail() door as A asked.
It renders its own anchor markers because the yard has none to raycast
against — world.js builds posts and trunks, not pick targets — and
marker styling is prep-phase UI, so it belongs to this lane.
What you click is not what you see: the marker ring's tube is 5 cm, which
at yard distance is a couple of pixels and genuinely unhittable. Picking
goes against an invisible 0.45 m sphere and the ring is just the read.
The panel shows live sail AREA, which SPRINT2 doesn't ask for but
decision 2 implies: the 70-192 m² problem is invisible to a player who
can't see what they're about to build. Picking the obvious quad
(h1/h3/p1/p2) now says "191 m2" before you commit to it.
dev_rigging.html follows the house pattern (C's weather_demo, D's
dev_player): the picking UI can't be asserted headless — it is clicks,
raycasts and materials — and can't run in index.html until A wires step
8. It boots the real world.js, real anchors and real cloth, so what's
verified is what ships. Retire it once index.html hosts prep.
Verified by hand in it: rig h2 by click, cycle carabiner -> shackle,
budget $80 -> $65, weak link flagged, dashed quad preview, commit ->
sail in scene (100 verts, 162 tris, casting a real shadow over the
garden bed) -> storm with corner loads reading 1.0-1.2 kN.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rebased onto M0 and reconciled against the real spine. checkContract
('sailRig') now conforms and js/tests/b.test.js runs 28 asserts green.
Contract fixes:
- anchor.sway(t) is the ABSOLUTE position, not an offset (thanks A —
I had it adding sway to pos, which would have flung every
tree-anchored corner to double its coordinates).
- events is an Emitter emitting {type, corner}, not a drained array.
- coverageOver() rects are centre+size, matching world.gardenBed. It
consumes world.sunDir directly: a hit along sunDir means shaded.
- START_BUDGET/SPARE_COST/HARDWARE/FIXED_DT now come from contracts.js
rather than being redeclared here.
Bug: a corner that blew was marked broken but never had its mass
returned, so invMass stayed 0 and the "blown" corner sat welded in
mid-air — no flogging, and the sail silently went dead. PLAN3D §5-B
wants flogging emergent from the freed node, so _checkFailure now frees
it. The cascade test missed this because it called _repin() by hand;
the new test drives a real overload failure instead and asserts the
corner tears 2 m off its anchor and keeps moving.
Tension dial remapped from the prototype's rest/tension to a real
pre-strain. rest/tension asks for 17% strain at dial 1.2 and 29% at 1.4
— stretching an 18 m sail by three metres — and put 68 kN on a corner of
the yard's biggest quad with no wind blowing. At 0.10 strain-per-dial it
swings a 5x5 rig's peak load 2.1x loose-to-tight and redlines a 192 m2
quad at 8.3 kN drum-tight, which is punishing and correct.
HARDWARE ratings retuned in contracts.js to real newtons per the
standing note there that Lane B owns these numbers. Costs and tier shape
untouched; $80 still buys rated hardware on at most 2 of 4 corners.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ports the prototype's economy verbatim: $80 budget, $5/$15/$30 hardware
tiers, $15 spare, tension 0.6-1.4. Adds unrig-with-refund, which the
prototype lacked — a misclick there was unrecoverable, and a full refund
costs the economy nothing.
RiggingSession holds all the rules and is three-free and DOM-free, so it
tests headless. The picking UI is left as an explicit seam: it needs Lane
A's camera and anchor markers to raycast against, which do not exist yet.
One assert encodes a design invariant rather than a code fact: $80 must
not buy rated shackles on all four corners. DESIGN.md's economic tension
is that you always field one dodgy corner and choose which one; if that
test ever passes, the budget has become decoration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>