3dee2a7c83
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
106a33cb31 |
Lane D: calibrate the douse to B's real ponds; fix the sagging-belly refusal
Rebuilt the broom against B's FROZEN contract, which is not the shape I asked
for and is better: pondCentroid() (one pond, not a list — B's belly pools into a
single heaviest place) and drainPondAt(node, dt, radius) draining PER FRAME and
returning the kg shed that call. Added interact `onHold` for actions that do work
over the hold rather than at the end of it; the total is only knowable by
accumulating. Falls out nicely: an interrupted poke sheds only what it got
through, which is the honest rule.
Calibration, measured on B's live sim rather than modelled (flat rig, storm_02,
pond mass at poke → kg shed → outcome):
8s · 85 kg → 76 shed → wet (caught it before the ticker)
12s · 152 kg → 104 shed → stagger (you answered the warning)
20s · 281 kg → 182 shed → knocked (you ignored it)
Bands sit under those so all three are REACHABLE — my Sprint-5 guesses (60/120)
would have flattened the player on every poke, and my first pass at real numbers
(150/350) put the knockdown above anything this rig can shed, i.e. dead code.
That is the StumbleBack lesson exactly, so d.test.js guards reachability with the
measured numbers and will fail loudly when gate 1 moves pond masses.
Bug the live sim found that no stub could: a loaded belly SAGS toward you — real
ponds ride from y=1.2 down through y=-1.0 — and my reach gate rejected `up < 0`,
so the broom answered "nothing pooling here" with 281 kg hanging over the garden.
Sagging toward you is the mechanic working. Gate is now a physical band between
sagFloor and reach, with the floor set between two measured cases: a live 281 kg
belly at y=-0.8 (pokeable) and post-tear wreckage at y=-4.5 (nothing to poke).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
d67126eacf |
Lane D: the broom, greyed prompts, and the label pass
The broom (SPRINT5 §Lane D-1, DESIGN.md's funniest correct mechanic): a third
carry type that queues behind the same hands, walk under the sail belly, hold-E
poke (Crank per E's anim_hint) → B's drainPondAt() → the water lands ON YOU,
sized to the joke: splash under 15 kg, stagger over 60, flat on your back over
120. Everything defers to E's baked metadata (carry_type, poke_tip.use on the
bristle end, anim_hint) — read, not invented. Duck-typed against the ponds[] /
drainPondAt seam I posted to B early, so it carries/walks/refuses-thin-air today
and lights up fully the moment B lands ponding.
Self-wires from createPlayer like the ladder; needs the live sail rig, which it
reads off interact.sailRig (published by wireYardActions, which main.js re-calls
through rigSail whenever attach() swaps the rig).
Greyed prompts (§Lane D-2, my offer, A's HUD hook): interact.visible() shows the
nearest UNAVAILABLE action with its reason when nothing's usable, instead of the
prompt vanishing — the confusion I logged last sprint. nearest() (what hold-E
acts on) is unchanged, so display and action stay separate. step() now reports
`usable` so the HUD can grey the radial. Fixed the ladder-place label to read as
a reason too ("the fascia needs the ladder — it's by the shed").
broom.js keeps no top-level THREE import (the vendored addons need index.html's
importmap) so d.test.js stays headless; the view loads via dynamic import that
only fires in a browser. 217/0/0 (was 207), 13 new asserts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
0ceff91d5a |
Lane D: the ladder sub-system (decision 12)
New ladder.js. The whole mechanic is 200 mm: the fascia bracket sits at 2.48 m, a 1.72 m person's hands reach 2.20, and E's ladder tops out at 2.90. The asset and the yard were already built for each other; this is the verb between them. Carry-ladder is a second carry type, so the ladder and the spare compete for the same pair of hands and a fascia repair costs two trips while a post repair costs one — DESIGN.md's "limited hands" rule doing real work, and the reason the house is the expensive anchor to depend on (which E's ratingHint 0.35 / collateral "gutter" was already saying in the data). Climb height is code-driven with ClimbLadder playing on top — the knockdown precedent, since _rotOnly strips the root and a clip can no more lift the body than Falling could lay it down. You can't brace up there (both hands on the rungs), the wind's bar drops to 0.6x, and being blown off is a fall that feeds straight into the existing get-up chain. needsLadder is scoped to the fascia on purpose: a height test would have roped in the 3.95 m posts and 5.05 m limbs, made every repair a two-trip job, and silently invalidated the recorded §7 run — and it isn't true to rigging either. Landed with no change to main.js: createLadder self-wires from createPlayer, which Lane A already hands the scene, world and interact. Two bugs found by building on my own API, both now asserted: a canUse that reads player.state cancels its own hold (starting a hold sets busy) — it ate the climb AND the reach gate before I keyed both on physical height instead; and onLadder had to become height-based for the same reason. Documented on register(). Selftest 194/0/0 (was 184). Full loop driven by hand in the real game. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
264c2e25d2 |
Lane D: solids collision, the M3 verbs, shelter and stumble
At gate 1 the ped walked straight through the house. Collision now stops it 0.30 m off the wall face (expected -9.70, measured -9.70), off trunks, posts and the fence, and slides along a wall hit at an angle. Injected as opts.collide the way groundAt already was, so player.sim.js stays zero-import and node-runnable. Two things the real yard taught, neither guessable from the plan: - `fence` is a GROUP of 37 child meshes whose combined box is the entire 30x20 m yard, so one box per solids entry is useless. Flattened to 43 leaf boxes. - the house ROOF spans y 2.99-3.21 and reaches 0.4 m FURTHER into the yard than the wall under it (eaves overhang). A flat footprint test would stop a 1.7 m person dead at an invisible eave, so every box is filtered by vertical overlap with the body and the roof drops out on its own. Boxes are built once (solids are static) and distance-pruned — no per-frame raycast, which is the thing Lane A measured as catastrophic on the terrain. The M3 pack is wired: carrying swaps locomotion to Carry/CarryIdle; an interaction names its own verb through a new `clip` field on the interact spec (Crank at a turnbuckle, PickUp at the shed table); StumbleBack fires on a gust that breaks your stride but can't floor you — below knockWind on purpose, so a storm reads as shoved → stumbling → floored rather than fine-fine-fine-flat. TakeCover (hold C) became a real mechanic rather than a pose: brace and knockWind x2.0, shove x0.25. A 38 m/s gale floors you standing and doesn't while braced; let go in the same gale and you're down in half a second. It raises the bar, it does not remove it — a big enough gust still wins, braced or not. So the storm's answer to "the gusts are too strong to cross the yard" is now wait one out and move in the lull, which is the repair-window language DESIGN.md already uses. wireYardActions now reads sailRig.corners[i] live by index instead of capturing the corner object — per Lane A's warning that attach() replaces the array, a captured corner is one the sim no longer steps and would gate forever on a `broken` flag that can never change again. 35 Lane D asserts, 0 fail (was 20). Carry/shelter/knockdown verified against the real 17-clip pack in the assembled game, not only in the harness. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
6382e604b8 |
Lane D: the small person — player, interactions, selftests
player.sim.js Deterministic core, zero imports: camera-relative movement, the
state-machine table, wind slow/gust-shove/knockdown. step(dt,t)
is the whole clock — no Date.now, no Math.random, no rAF — so a
storm fast-forwards identically in selftest and in the game.
player.js The view: rig load per the 90sDJsim DEVMANUAL rig rules
(SkeletonUtils.clone, height-normalise off the MEASURED head
bone, canonicalised bone namespace), clip retarget, and
createPlayer() satisfying the Player contract.
interact.js Hold-E with radial progress + wireYardActions (re-rig 2.5 s,
turnbuckle trim 1.2 s, carry-one-item), duck-typed so it no-ops
cleanly until Lane B lands sailRig.repair/trim.
d.test.js 20 asserts in Lane A's harness: 38 pass / 3 skip overall.
Ported from the 2D prototype's shape (game.js:250-252), retuned to m/s: the
slow curve, and shove gated to gusts only and scaling with speed² so gusts have
teeth. Knockdown needs 0.5 s of SUSTAINED overload — deliberately the same rule
as a sail corner letting go, so cloth and people speak one language.
Two decisions worth the review:
- The knockdown pitches the root in code rather than playing the Falling clip's
root. Shared clips must drop Hips.quaternion (a different-orientation source
lays the target flat), so a clip physically cannot lie the body down. Doing it
in code also lets the fall go DOWNWIND of the gust that caused it, which a
canned clip could never do, and keeps it deterministic.
- Gust magnitude is recovered from a slow EMA of local wind rather than widening
Lane C's contract: wind.sample() gives the total and gustTelegraph() only fires
BEFORE a gust, so nothing reports gust size during the hold. The EMA
self-calibrates to whatever storm JSON Lane C authors.
Verified in a real scene, not only in asserts: head bone 1.715 m at fig scale
0.983, all six clips bound, walk/run at the tuned speeds, the body lies down and
gets back up, hold-E fires exactly once per press.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|