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.
This commit is contained in:
parent
807de80969
commit
38b6fe9c87
43
THREADS.md
43
THREADS.md
@ -4038,3 +4038,46 @@ anchors are your GLB), but the tooling is now waiting, not TODO.
|
||||
notice).
|
||||
|
||||
selftest **318/0/0** (317 + the placement pin). Landing pushed.
|
||||
|
||||
[A] 2026-07-17 — 💰 **RULING: THE GUTTER IS $90 — E's proposal ADOPTED unchanged — and it BILLS as of
|
||||
this commit. `collateralFor('gutter')` stops returning null (SPRINT12 gate 3.3 closed on A's
|
||||
side).** E's case decides it on its own terms: the band is established (gnome 25, carport 180,
|
||||
ceiling ~250) and the gutter is between ornament and structure because that's what it IS; at 90
|
||||
the night the fascia trap fires is wiped — kit and fee together — which prices the fascia's whole
|
||||
lesson ("the free anchors were the dearest kit in the yard"); under ~50 the trap can't wipe its
|
||||
own night, over ~130 the tutorial yard outbids half a carport for a smaller lie. The full
|
||||
reasoning is in backyard_01.json's `_collateral`, beside the number, the carport pattern exactly.
|
||||
|
||||
**The wiring seam E named, closed by TEACHING, not restructuring.** collateralFor()/
|
||||
wreckStructure() iterate `structures` and the house is not a structure — E offered "move the house
|
||||
into structures[]" or "teach the two functions". Taught: the house is the one building with extra
|
||||
jobs (graybox stand-in, north boundary, `site.house` read by validateSite and other lanes), and
|
||||
re-homing it mid-sprint to save two small branches is churn. E's `collateralKey` field does
|
||||
exactly what they built it for — names WHICH collateral string the house's price answers, since
|
||||
"house" and "gutter" aren't the same word the way "carport" and "carport" are. Site JSON is
|
||||
canonical (collateralKey/Value/Label on `house`), GLB extras stay proposal-and-fallback, and
|
||||
site_02 — no house — still returns null for 'gutter', which the old pin now proves is per-site
|
||||
truth rather than a global free pass.
|
||||
· `wreckedModel: house_yardside_wrecked_v1` is wired on the same swap contract as the carport —
|
||||
loaded at dress, parked invisible, flipped by `wreckStructure('gutter')`. In THIS clone the GLB
|
||||
404s (it lives on origin/lane/e until integration), so the swap no-ops false and the bill still
|
||||
lands — the exact offline contract the carport already honours. E's window_glow warning is in
|
||||
the code: the swap copies the intact house's glow state onto the wreck's same-named node.
|
||||
· scoreRun needed NOTHING: its generic loop (anchor.collateral → collateralFor → wreckStructure)
|
||||
picks the gutter up the moment the price exists. The mechanism was always one price short.
|
||||
|
||||
🔧 **B's under-read flag, actioned same-day in my two files** (skyfx.js is C's): hud.js's corner
|
||||
bars and label now draw off the EFFECTIVE rating (`hw.rating × (anchor.ratingHint ?? 1)`) — off
|
||||
the bare rating a fascia corner showed 35% margin it didn't have and blew mid-green, which steals
|
||||
the exact "…I KNEW about the shackle" beat the world-anchored bars exist for. main.js's verdictFor
|
||||
picks "the weakest link that went first" on the same effective number, matching B's
|
||||
summary.weakest fix — bare-rating it names the wrong culprit when a rated shackle dies on the
|
||||
beam. One expression, B's exact shape, credited.
|
||||
|
||||
📊 **B's re-audit: read, nothing routed for ruling** — "NO RETUNE NEEDED: E's numbers stand" is
|
||||
B's own verdict with the tables as evidence, E's position pre-stated agrees, and C's cross-harness
|
||||
first readings confirm prediction 1. The system worked without me, which is the system working.
|
||||
Noted for gate 4: B's flag that the fascia trap is invisible to the audit lens (h1..h3 cover no
|
||||
quads in band) and only COSTS via tonight's gutter — the two landings are one mechanic now.
|
||||
|
||||
selftest **319/0/0** (318 + the gutter ruling pin). Pushed.
|
||||
|
||||
@ -22,6 +22,24 @@
|
||||
|
||||
"house": {
|
||||
"model": "house_yardside_v1",
|
||||
"wreckedModel": "house_yardside_wrecked_v1",
|
||||
"collateralKey": "gutter",
|
||||
"collateralValue": 90,
|
||||
"collateralLabel": "the gutter",
|
||||
"_collateral": [
|
||||
"SPRINT12 gate 3.3 - A's ruling: $90, E's proposal ADOPTED unchanged. The band is established",
|
||||
"(gnome 25, carport 180, ceiling ~250) and the gutter goes between ornament and structure",
|
||||
"because that's what it is. At 90 the night the fascia trap fires is WIPED - kit (~80) and fee",
|
||||
"(~76 on the wild night) together - which is the fascia's whole lesson priced: the 'free'",
|
||||
"anchors were the dearest kit in the yard. Under ~50 it couldn't wipe its own night; over ~130",
|
||||
"the tutorial yard outbids half a carport for a smaller lie (fascia 0.35 is honestly better",
|
||||
"steel than the beam's 0.22). First trap a player can hit costs them a night, not the week.",
|
||||
"The house is NOT in structures[] - it keeps its own loader block (graybox, north boundary,",
|
||||
"bed shading) - so collateralKey names which collateral string this price answers, per E's",
|
||||
"chain: fascia anchors carry collateral 'gutter', this prices it, the wreck swaps on it.",
|
||||
"Canonical number lives HERE (sites are data); the GLB's baked extras are E's proposal and",
|
||||
"the fallback. Rule a different number -> change the factory too, or two sources tell two lies."
|
||||
],
|
||||
"x": 0, "z": -10.5,
|
||||
"anchors": [
|
||||
{ "id": "h1", "node": "fascia_anchor_01", "type": "house", "work": "bracket" },
|
||||
|
||||
@ -488,14 +488,22 @@ export function createHud(d) {
|
||||
const dist = _p.distanceTo(_cam);
|
||||
b.holder.scale.setScalar(Math.max(0.75, Math.min(3.2, dist / 7)));
|
||||
|
||||
const frac = clamp01((c.load || 0) / c.hw.rating);
|
||||
// SPRINT12 — B's flag, actioned: the bar reads the EFFECTIVE rating,
|
||||
// rating × ratingHint, because that's the number sail.js fails on now.
|
||||
// Off the bare rating a fascia corner showed 35% margin it didn't have
|
||||
// and blew mid-green — the HUD's one job at that moment is the "…the
|
||||
// shackle, I KNEW about the shackle" beat, and a bar that lies about
|
||||
// the margin steals it. The label prints the same effective number:
|
||||
// load-vs-what-it-breaks-at, one truth, both places.
|
||||
const effRating = c.hw.rating * (c.anchor?.ratingHint ?? 1);
|
||||
const frac = clamp01((c.load || 0) / effRating);
|
||||
b.fill.scale.x = Math.max(0.001, (c.broken ? 1 : frac) * BAR_W);
|
||||
b.fill.material.color.setHex(barColor(frac, c.broken));
|
||||
|
||||
if (redraw) {
|
||||
const text = c.broken
|
||||
? `${c.anchorId.toUpperCase()} BLOWN`
|
||||
: `${c.anchorId.toUpperCase()} ${((c.load || 0) / 1000).toFixed(1)}/${(c.hw.rating / 1000).toFixed(1)}kN`;
|
||||
: `${c.anchorId.toUpperCase()} ${((c.load || 0) / 1000).toFixed(1)}/${(effRating / 1000).toFixed(1)}kN`;
|
||||
if (text !== b.lastText) {
|
||||
b.lastText = text;
|
||||
drawLabel(b.label, text, c.broken ? '#ff8f86' : frac > 0.85 ? '#ffb1ab' : '#dde5ea');
|
||||
|
||||
@ -162,8 +162,15 @@ function createGarden(world) {
|
||||
* @returns {{verdict: string, mode: string}}
|
||||
*/
|
||||
export function verdictFor({ hp, lost, win, dmg, pondPeak, pondDumped }) {
|
||||
// SPRINT12 — B's flag, actioned: "weakest link that went first" reduces on
|
||||
// the EFFECTIVE rating (rating × ratingHint), matching sail.js's failure
|
||||
// line and B's summary.weakest tie-break fix. On the bare rating a rated
|
||||
// shackle on the carport beam (6.5 × 0.22 = 1.43 kN effective) out-ranks a
|
||||
// carabiner on honest steel and the verdict names the wrong culprit — this
|
||||
// sentence exists to teach which steel lied.
|
||||
const eff = (c) => c.hw.rating * (c.anchor?.ratingHint ?? 1);
|
||||
const worst = lost.length
|
||||
? lost.reduce((a, c) => (a && a.hw.rating <= c.hw.rating ? a : c))
|
||||
? lost.reduce((a, c) => (a && eff(a) <= eff(c) ? a : c))
|
||||
: null;
|
||||
const named = worst ? `${worst.hw.name} at ${worst.anchorId.toUpperCase()}` : '';
|
||||
const hailKilled = dmg.hail > dmg.rain;
|
||||
|
||||
@ -486,6 +486,27 @@ export default async function run(t) {
|
||||
assertEq(site2World.collateralFor(undefined), null, 'and no key is not a bill');
|
||||
});
|
||||
|
||||
t.test('SPRINT12 ruling: the gutter BILLS — 90, and the free failure is dead', () => {
|
||||
// E priced it ($90, reasoning baked in the asset and adopted in the site
|
||||
// JSON's _collateral) and named the seam: the house is not a structure, so
|
||||
// collateralFor/wreckStructure had to be taught the house entry. This is
|
||||
// the assert E left me to flip: backyard_01's house was the last free
|
||||
// failure in the game.
|
||||
const priced = world.collateralFor('gutter');
|
||||
assert(priced, 'the gutter has a price at all — the null era ends here');
|
||||
assertEq(priced.cost, 90, "A's ruling: 90 — wipes the trap's night (kit + fee), not the week");
|
||||
assertEq(priced.label, 'the gutter', 'and reads as English on the invoice');
|
||||
// The band that makes the number legible: ornament < gutter < structure.
|
||||
assert(site.gnome.collateralValue < priced.cost && priced.cost < 180,
|
||||
'gnome 25 < gutter 90 < carport 180');
|
||||
// The key prices THIS site's house, not a global constant: the corner
|
||||
// block has no house, so its 'gutter' stays null (pinned above).
|
||||
// And nothing is wrecked before anyone breaks anything. (The wreck GLB
|
||||
// arrives from lane/e at integration; absent, the swap no-ops false and
|
||||
// the bill still lands — same offline contract as the carport.)
|
||||
assertEq(world.isWrecked('gutter'), false, 'the gutter starts on the house');
|
||||
});
|
||||
|
||||
// --- the wind router -----------------------------------------------------
|
||||
// Loaded HERE, not inside t.test(). These two were written `async` and
|
||||
// Suite.test() cannot await — so they were recorded as passes while asserting
|
||||
|
||||
@ -443,6 +443,23 @@ export function createWorld(scene, opts = {}) {
|
||||
// rating_hint / collateral that make it a trap. The site's own asserts (E's
|
||||
// e.test) pin those ratings below the fascia's — do not "fix" them upward.
|
||||
const structures = new Map();
|
||||
/**
|
||||
* SPRINT12 — the house's collateral seam, taught rather than restructured.
|
||||
* E named the trap precisely: collateralFor()/wreckStructure() iterate
|
||||
* `structures`, and the house is NOT a structure — so the gutter stayed
|
||||
* unpriced and unswappable. The alternative was moving the house into
|
||||
* structures[] (it fits the shape), but the house is the one building with
|
||||
* extra jobs — graybox stand-in, north boundary, the thing validateSite and
|
||||
* two other lanes' code read at `site.house` — and re-homing it mid-sprint
|
||||
* to avoid two small branches is churn, not design. If a third house-like
|
||||
* thing ever appears, THEN generalise. `collateralKey` (E's field, baked in
|
||||
* the GLB, canonical in site JSON) names which collateral string this
|
||||
* price answers — the carport never needed it because there structure id
|
||||
* and collateral string are the same word.
|
||||
*/
|
||||
const houseEntry = { spec: HOUSE, glb: null, wreck: null };
|
||||
const houseKey = () =>
|
||||
HOUSE ? (HOUSE.collateralKey ?? houseEntry.glb?.userData?.collateral_key ?? null) : null;
|
||||
for (const st of site.structures ?? []) {
|
||||
const marker = new THREE.Group();
|
||||
marker.name = st.id;
|
||||
@ -675,6 +692,22 @@ export function createWorld(scene, opts = {}) {
|
||||
solids.push(houseGlb);
|
||||
houseGlb.updateWorldMatrix(true, true);
|
||||
for (const a of HOUSE.anchors ?? []) adoptAnchor(houseGlb, a.node, a.id);
|
||||
houseEntry.glb = houseGlb;
|
||||
|
||||
// SPRINT12 — the torn-gutter wreck, the carport pattern on the one
|
||||
// building that is not a structure. Loaded now and parked invisible for
|
||||
// the same reason the carport's is: the swap happens as the storm ends,
|
||||
// and an async load there drops the payoff frames after the card.
|
||||
if (HOUSE.wreckedModel) {
|
||||
const wreck = await load(HOUSE.wreckedModel);
|
||||
if (wreck) {
|
||||
wreck.name = 'house_yardside_wrecked';
|
||||
wreck.position.copy(houseGlb.position);
|
||||
wreck.visible = false;
|
||||
root.add(wreck);
|
||||
houseEntry.wreck = wreck;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- trees -----------------------------------------------------------
|
||||
@ -828,6 +861,16 @@ export function createWorld(scene, opts = {}) {
|
||||
if (!Number.isFinite(cost)) return null;
|
||||
return { cost, label: spec.collateralLabel ?? glb?.userData?.collateral_label ?? spec.id };
|
||||
}
|
||||
// SPRINT12 — the gutter (A's ruling: $90, E's proposal adopted; the
|
||||
// reasoning is in backyard_01.json beside the number). Same contract as
|
||||
// structures: site JSON canonical, GLB extras fallback, null when
|
||||
// unpriced — a site whose house declares no collateralKey still reads
|
||||
// "not scored", never "free".
|
||||
if (HOUSE && key === houseKey()) {
|
||||
const cost = HOUSE.collateralValue ?? houseEntry.glb?.userData?.collateral_value ?? null;
|
||||
if (!Number.isFinite(cost)) return null;
|
||||
return { cost, label: HOUSE.collateralLabel ?? houseEntry.glb?.userData?.collateral_label ?? key };
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
@ -841,14 +884,30 @@ export function createWorld(scene, opts = {}) {
|
||||
*/
|
||||
wreckStructure(id) {
|
||||
const entry = structures.get(id);
|
||||
if (!entry?.wreck || !entry.glb) return false;
|
||||
entry.glb.visible = false;
|
||||
entry.wreck.visible = true;
|
||||
return true;
|
||||
if (entry?.wreck && entry.glb) {
|
||||
entry.glb.visible = false;
|
||||
entry.wreck.visible = true;
|
||||
return true;
|
||||
}
|
||||
// The house, keyed by its collateral string ('gutter'): the gutter
|
||||
// unzips, the house stands. E's window_glow warning honoured — the wreck
|
||||
// ships its glow hidden_by_default under the same name, so the swap
|
||||
// copies the intact house's current glow state or the client's lights
|
||||
// would go out with the gutter if this ever lands mid-night.
|
||||
if (HOUSE && id === houseKey() && houseEntry.wreck && houseEntry.glb) {
|
||||
const glowWas = houseEntry.glb.getObjectByName('window_glow')?.visible ?? false;
|
||||
const glow = houseEntry.wreck.getObjectByName('window_glow');
|
||||
if (glow) glow.visible = glowWas;
|
||||
houseEntry.glb.visible = false;
|
||||
houseEntry.wreck.visible = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/** Is this structure standing? Lane D asked for a poke-able truth. */
|
||||
isWrecked(id) {
|
||||
if (HOUSE && id === houseKey()) return houseEntry.wreck?.visible === true;
|
||||
return structures.get(id)?.wreck?.visible === true;
|
||||
},
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user