hex_to_rgba put sRGB fractions straight into Blender's LINEAR Base Color
socket, so baseColorFactor shipped linear-encoded-as-sRGB and three's
linear->sRGB display step made every surface one gamma step too light
and desaturated, on every asset, since Sprint 1. Measured: leaf_gum
#7C8F5E displayed #B9C5A3, bark_gum #BFB8A8 displayed #E0DDD4 (the
near-white gum trunk THREADS blamed on Blender's view transform).
Fixed at source with the exact IEC curve (alpha untouched — coverage,
not colour) and the whole factory rebuilt: ALL ASSETS PASS. e.test now
pins mat.color.getHexString(SRGBColorSpace) against the authored PAL hex
— factory intent vs what the runtime draws, the one comparison neither
end can fake. Mutation-checked red-then-green against the pre-fix GLB.
523/0/0, verified by eye in the running game before and after.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.