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>
hide_render does not survive glTF export — verified in three r175, every node
arrives visible:true. So garden_bed has been drawing plants_full, tattered AND
dead superimposed since Sprint 1, and my THREADS note told Lane A the opposite.
The asserts missed it by checking the nodes existed rather than that only one
was on. The flag now rides in extras, which does export, and consumers apply it
in one line; e.test.js pins the flag instead of the node.
Night dressing: house_yardside gains window_glow, an unlit emissive pane marked
hidden_by_default, plus window_light_anchor. A warm window means someone is
inside, which is why the player is out in the dark keeping the sail on. Plus a
moon sprite whose halo, not its disc, is the part that survives cloud.
End cards are rendered from the game's own props as a diptych: identical camera
and yard, only the night's outcome differs. The left third is left empty for
Lane A's text.
Cards are opt-in behind --cards because EEVEE's shadow pass is not
byte-reproducible across processes in 5.1 — proven with a minimal repro, not
PYTHONHASHSEED, and present even with a hard sun. The dawn shadow is the art, so
the cards become committed art rather than build output. Everything the game
loads stays byte-identical: 38/38 verified.
Selftest 241/0/0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verifying the GLBs in three.js (not just Blender) showed four assets reporting
inflated bounds: tramp_01 came back 3.29 x 1.27 m against a true 2.96 x 0.78.
Box3.setFromObject expands each mesh's LOCAL box by the world matrix, so a node
carrying a rotation over-reports — the same trap Blender's obj.bound_box sets,
and what three uses for frustum culling. Joined nodes inherited parts[0]'s
rotation, which for an arc is half a segment step off-axis.
Applying rotation at join time makes every local box axis-aligned, so the
default Box3 path is now correct for consumers and culling is tight. World
geometry is unchanged; the exported dims are identical.
Adds tools/assetcheck/, the three.js harness that caught this. It's the only
check that can: a Blender round-trip exports Z-up->Y-up and imports Y-up->Z-up,
so a broken export_yup flips back and passes green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
16 generated assets plus the grass billboard atlas and the four debris models
copied verbatim from the 3D-STORE library (copies rule, §0). All meter-scale,
Y-up, and far under the 15k tri budget — garden_bed is heaviest at 2,580.
Regeneration is byte-deterministic: two consecutive runs produce identical
files, so re-running the factory causes no churn.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>