S18 integration: apply E's gate-5 hunks (E specified, A blessed) — the site names its own collateral target, the bake stops clobbering it, and validateSite refuses the clash
This commit is contained in:
parent
ecd88f0183
commit
43724c7428
@ -114,6 +114,27 @@ export function validateSite(site, name = site?.id ?? '?') {
|
||||
if (!ids.length) bad.push('no anchors at all — nothing to rig to');
|
||||
const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);
|
||||
if (dupes.length) bad.push(`duplicate anchor ids: ${[...new Set(dupes)].join(', ')}`);
|
||||
// SPRINT18 gate 5 (E specified, A blessed): two author-time refusals for the
|
||||
// second-rust-post landmine. A duplicate STRUCTURE id makes structures.set()
|
||||
// silently replace the first, which then never renders, bills, or wrecks;
|
||||
// two structures resolving to ONE collateral key make collateralFor bill and
|
||||
// wreck whichever matched first. Both are quiet until a yard has two of the
|
||||
// same thing, which is why they are refused here rather than measured later.
|
||||
const structIds = (site.structures ?? []).map((s) => s.id);
|
||||
const dupStructs = structIds.filter((id, i) => structIds.indexOf(id) !== i);
|
||||
if (dupStructs.length) {
|
||||
bad.push(`duplicate structure ids: ${[...new Set(dupStructs)].join(', ')}`);
|
||||
}
|
||||
const structKeys = (site.structures ?? [])
|
||||
.filter((s) => s.collateral != null)
|
||||
.map((s) => s.collateralKey ?? s.id);
|
||||
const dupKeys = structKeys.filter((k, i) => structKeys.indexOf(k) !== i);
|
||||
if (dupKeys.length) {
|
||||
bad.push(
|
||||
`two priced structures resolve to one collateral key: ${[...new Set(dupKeys)].join(', ')}`
|
||||
+ ' — give each a distinct collateralKey and point its anchors at it',
|
||||
);
|
||||
}
|
||||
// Lane D's field. `work` is the MECHANISM, not the anchor's type: a post is
|
||||
// tensioned from a cleat at its base and a tree strop is thrown, but a
|
||||
// bracket bolted up a bare wall is the one you cannot fake — so that, and
|
||||
@ -542,6 +563,11 @@ export function createWorld(scene, opts = {}) {
|
||||
makeStaticAnchor(a.id, a.type ?? 'post', new THREE.Vector3(st.x, heightAt(st.x, st.z) + 2.4, st.z)),
|
||||
{ work: a.work },
|
||||
a.node ? null : factoryExtras(a.type ?? 'post'),
|
||||
// SPRINT18 gate 5 (E specified, A blessed): the site names its own
|
||||
// collateral target, applied AFTER factoryExtras so the override wins.
|
||||
// Without it, two same-type structures share one baked key and
|
||||
// collateralFor bills or wrecks whichever matched first.
|
||||
a.collateral ? { collateral: a.collateral } : null,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -687,7 +713,10 @@ export function createWorld(scene, opts = {}) {
|
||||
if (!node || !anchor) return false;
|
||||
anchor.pos.setFromMatrixPosition(node.matrixWorld);
|
||||
anchor.ratingHint = node.userData?.rating_hint ?? 1;
|
||||
anchor.collateral = node.userData?.collateral ?? null;
|
||||
// SPRINT18 gate 5: the bake is the DEFAULT, never a clobber — a site that
|
||||
// named its own target above keeps it. With no override the chain is
|
||||
// identical, so every shipped site is byte-unchanged.
|
||||
anchor.collateral = anchor.collateral ?? node.userData?.collateral ?? null;
|
||||
// SPRINT14, E's gap 1: some baked anchor nodes are NOT tie-offs — a door
|
||||
// step, a bench top, a broom grip, a lighting hint. They carry no
|
||||
// `rating_hint`, and `?? 1` therefore rated them PERFECT: name one in a
|
||||
|
||||
Loading…
Reference in New Issue
Block a user