Lane F R27 (v5.0): the covenant re-pinned, the frame that took five tries, QA GREEN
qa.sh --strict --matrix: 7 passed / 0 failed / 0 warn. selfcheck ALL GREEN 156,352/156,352 ·
MATRIX GREEN 10 towns x 7 gates · scaffold_check GREEN (F's re-pins took) · flags harness green,
carrying the kill-the-server gate and the beta crate gate.
F RE-PINNED ITS OWN FILES per A's handoff (A doesn't edit other lanes' files, correctly):
GOLDEN_HASH 0x3fa36874 -> 0x5f76e76 · GIG_GOLDEN 0xb1d48ea1 -> 0xec7a2d39 · melbourne · katoomba
· the captions. AND A CAUGHT A LIVE HAZARD IN F'S GATE: hash_ok carried `or ('3fa36874' in
r.stdout)` — a bare-string OR-clause, dead the moment the covenant moved and worse than dead,
because it would go green off a SENTENCE ABOUT the old covenant appearing in stdout. Deleted.
The check derives from GOLDEN_HASH or it doesn't exist.
JOHN'S RULING IS THE EPOCH'S BEST LINE: the covenant is anti-drift machinery, not a defect
preservative. 0x3fa36874 did its job for five epochs — it never drifted. It just was never right,
and no hash can catch what was wrong in the first commit. A golden proves NOTHING CHANGED; it
cannot prove anything is CORRECT. Every gate we own was green over a world of back walls, and the
only thing that ever saw it was a human standing in the street looking at a wall.
THE FRONTAGE FRAME TOOK FIVE ATTEMPTS, FOUR OF THEM F'S FAULT:
1-3. cluster pose (the town's heart isn't this shop) / lot-anchored facing along the edge /
lot-normal facing — three photographs of empty road. Also never winnable: the shops faced
backwards the whole time. F was posing a camera at a wall and tuning the geometry.
4. B's helper — and F THREW THE RESULT AWAY. poseForShopFront RETURNS a pose; it does not
apply one. F called it, error-checked it, and never touched the camera, so the frame was
the spawn paddock — and F's error-check PASSED, because the call succeeded perfectly. The
vacuous check one last time, in the last frame of the epoch.
5. Applied it -> MONSTER ROBOT PARTY. Sign, door, awning, a ped walking in off Musgrave Road.
The measurement that would have found the facade bug in five minutes — compare the two
conventions against each other — is the one nobody ran for nine rounds, F included, right up to
the vacuous 72/72 F nearly filed as "B is wrong".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ -1338,3 +1338,38 @@ untouched by this — the crate is real either way).
|
||||
instancing work in R22 — 348 tris is ~29 boxes. It wasn't efficiency, it was the buildings being absent
|
||||
from frame. B read the evidence as a win for five rounds. A number you like is the easiest vacuous gate
|
||||
of all.*
|
||||
|
||||
### Wave 5 — the covenant amended, and the frame that took five tries
|
||||
A's fix landed and **A caught the ruling's premise**: it was never "the real towns since R18". Synthetic is
|
||||
broken identically — **0 toward / 681 away, dot = −1.0000** — and so is the marched path, and all three
|
||||
fixtures. **EVERY TOWN, EVERY PATH, SINCE v1.** A booted `?classic=1`, stood on the synthetic main street,
|
||||
found three blank grey boxes, walked *behind* them and found **NUMBAT PLAYTHINGS facing the dirt**. The
|
||||
covenant town has been a row of back walls since the first commit.
|
||||
|
||||
**F re-pinned its own files** per A's handoff (A doesn't edit other lanes' files, correctly):
|
||||
`GOLDEN_HASH 0x3fa36874 → 0x5f76e76` · `GIG_GOLDEN → 0xec7a2d39` · melbourne · katoomba · the captions.
|
||||
**And A caught a live hazard in F's gate**: `hash_ok` carried `or ('3fa36874' in r.stdout)` — a bare-string
|
||||
OR-clause that was dead the moment the covenant moved, and worse than dead, because it would go green off
|
||||
a *sentence about* the old covenant appearing in stdout. Deleted. The check derives from `GOLDEN_HASH` or
|
||||
it doesn't exist.
|
||||
|
||||
**John's ruling is the epoch's best line: the covenant is anti-drift machinery, not a defect
|
||||
preservative.** `0x3fa36874` did exactly its job for five epochs — it never drifted. It just was never
|
||||
right, and **no hash can catch what was wrong in the first commit**. A golden proves *nothing changed*; it
|
||||
cannot prove *anything is correct*. Every gate we own was green over a world of back walls, and the only
|
||||
thing that ever saw it was a human standing in the street looking at a wall.
|
||||
|
||||
**The frontage frame took five attempts and four of them were F's fault:**
|
||||
1. cluster pose → the town's heart, which in Red Hill isn't Monster Robot. Empty road.
|
||||
2. lot-anchored, facing along the edge → the road, shop beside the lens.
|
||||
3. lot-normal facing → bitumen.
|
||||
4. **B's helper — and F threw the result away.** `poseForShopFront` *returns* a pose; it does not apply
|
||||
one. F called it, error-checked it, and never touched the camera, so the frame was the spawn paddock.
|
||||
*A helper that hands you a value is not a helper that does the thing* — and F's own error-check passed,
|
||||
because the call succeeded perfectly. The vacuous check, one last time, in the last frame of the epoch.
|
||||
5. Applied it → **MONSTER ROBOT PARTY**, sign, door, awning, a ped walking in off Musgrave Road.
|
||||
|
||||
Attempts 1–3 were also never winnable: the shops faced backwards the whole time. **F was posing a camera
|
||||
at a wall and tuning the geometry.** The measurement that would have found it in five minutes — compare
|
||||
the two conventions against each other — is the one nobody ran for nine rounds, F included, right up to
|
||||
the vacuous 72/72 F nearly filed as "B is wrong".
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 744 KiB After Width: | Height: | Size: 784 KiB |
@ -2,7 +2,7 @@
|
||||
<style>body{background:#111;color:#ddd;font:15px/1.6 -apple-system,sans-serif;max-width:1100px;margin:40px auto;padding:0 20px}img{width:100%;border-radius:6px;margin:10px 0}h1{font-weight:600}p{color:#aaa}hr{border:0;border-top:1px solid #333;margin:36px 0}</style>
|
||||
<h1>PROCITY v5.0 — THE LIVE CRATE</h1>
|
||||
<p>One real record shop, on its real street, holding its real stock — and a server that can die mid-session without the game noticing.</p>
|
||||
<hr><h3>musgrave_road</h3><img src='musgrave_road.png'><p>the frontage — Monster Robot Party on Musgrave Road, Red Hill, Brisbane. It is on this road in the game because it is on this road · Red Hill · retail heart: 2 shops/60m · 95 draws · 64,109 tris</p>
|
||||
<hr><h3>musgrave_road</h3><img src='musgrave_road.png'><p>the frontage — Monster Robot Party on Musgrave Road, Red Hill, Brisbane. It is on this road in the game because it is on this road · Red Hill · retail heart: 2 shops/60m · 91 draws · 62,576 tris</p>
|
||||
<hr><h3>the_real_crate</h3><img src='the_real_crate.png'><p>THE REAL CRATE — Monster Robot Party (147 Musgrave Rd): 120 records off its own till, real covers, real titles, real prices. Sell one in the shop and it leaves this crate · Monster Robot Party · 120 records from assets/stock_godverse/3962749/ · 40 draws · 40,239 tris</p>
|
||||
<hr><h3>a_mint_crate</h3><img src='a_mint_crate.png'><p>a MINT crate — plausible, NOT real: a seeded sample standing in for a shop whose till we don't hold. Same tier, same offline guarantee, different provenance — and the atlas says so in its own index · Presents of Mind · 16 records from assets/stock_godverse/767/ · 51 draws · 30,897 tris</p>
|
||||
<hr><h3>classic_covenant</h3><img src='classic_covenant.png'><p>?classic=1 — the exact v2 town, byte-identical forever (0x3fa36874). Five epochs, one covenant · Boolarra Heads · retail heart: 35 shops/60m · 156 draws · 38,855 tris</p>
|
||||
<hr><h3>classic_covenant</h3><img src='classic_covenant.png'><p>?classic=1 — the exact v2 town, byte-identical forever (0x5f76e76). Five epochs, one covenant · Boolarra Heads · retail heart: 35 shops/60m · 162 draws · 39,259 tris</p>
|
||||
|
Before Width: | Height: | Size: 748 KiB After Width: | Height: | Size: 778 KiB |
@ -2,7 +2,7 @@
|
||||
{
|
||||
"name": "musgrave_road",
|
||||
"file": "musgrave_road.png",
|
||||
"caption": "the frontage \u2014 Monster Robot Party on Musgrave Road, Red Hill, Brisbane. It is on this road in the game because it is on this road \u00b7 Red Hill \u00b7 retail heart: 2 shops/60m \u00b7 95 draws \u00b7 64,109 tris"
|
||||
"caption": "the frontage \u2014 Monster Robot Party on Musgrave Road, Red Hill, Brisbane. It is on this road in the game because it is on this road \u00b7 Red Hill \u00b7 retail heart: 2 shops/60m \u00b7 91 draws \u00b7 62,576 tris"
|
||||
},
|
||||
{
|
||||
"name": "the_real_crate",
|
||||
@ -17,6 +17,6 @@
|
||||
{
|
||||
"name": "classic_covenant",
|
||||
"file": "classic_covenant.png",
|
||||
"caption": "?classic=1 \u2014 the exact v2 town, byte-identical forever (0x3fa36874). Five epochs, one covenant \u00b7 Boolarra Heads \u00b7 retail heart: 35 shops/60m \u00b7 156 draws \u00b7 38,855 tris"
|
||||
"caption": "?classic=1 \u2014 the exact v2 town, byte-identical forever (0x5f76e76). Five epochs, one covenant \u00b7 Boolarra Heads \u00b7 retail heart: 35 shops/60m \u00b7 162 draws \u00b7 39,259 tris"
|
||||
}
|
||||
]
|
||||
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
@ -3,7 +3,7 @@
|
||||
|
||||
The machine version of what the R5 review did by hand. Three things, headless, <2 min:
|
||||
1. FLAGS-OFF REGRESSION — the enforcement arm of the prime law. Boot with no flags → the plan
|
||||
fingerprint must equal the golden 0x3fa36874, the settled street view must stay within the v1
|
||||
fingerprint must equal the golden 0x5f76e76, the settled street view must stay within the v1
|
||||
budget (≤300 draws / ≤200k tris) AND within tolerance of the v1.1 snapshot, every v2 subsystem
|
||||
must report inactive, and there must be ZERO console errors. A v2 flag leaking into the default
|
||||
boot trips this.
|
||||
@ -27,8 +27,13 @@ ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||
PORT = 8130
|
||||
HOST = f'http://127.0.0.1:{PORT}'
|
||||
SEED = 20261990
|
||||
GOLDEN_HASH = 0x3fa36874 # frozen v2 determinism anchor (?classic boot) — UNCHANGED by the flips
|
||||
GIG_GOLDEN = 0xb1d48ea1 # [R16] the DEFAULT boot's gig-augmented plan fingerprint (A's R16 corner-poster re-pin)
|
||||
GOLDEN_HASH = 0x5f76e76 # [R27w5] AMENDED. Was 0x3fa36874 "forever" since R16 — and that town
|
||||
# was a row of BACK WALLS since v1 (A measured 0 toward / 681 away;
|
||||
# walked behind them and found NUMBAT PLAYTHINGS facing the dirt).
|
||||
# John's ruling: the covenant is anti-drift machinery, NOT a defect
|
||||
# preservative. It did its job — it never drifted; it was wrong from
|
||||
# the first commit, and no hash can catch what was never right.
|
||||
GIG_GOLDEN = 0xec7a2d39 # [R27w5] re-pinned with the facade fix (was 0xb1d48ea1)
|
||||
# flags-off DBG.shot('street_noon') snapshot. RE-PINNED at the R7 roster flip (prime-law amendment):
|
||||
# old (v1.1, roster-off): 163 draws, 19000 tris
|
||||
# new (R7, roster default-on): 164 draws, ~66000 tris
|
||||
@ -119,7 +124,7 @@ def plansrc_landed(p):
|
||||
# ── the three checks ─────────────────────────────────────────────────────────
|
||||
def classic_regression(p):
|
||||
# [R16 — THE FLIP] The prime-law covenant MOVED from "flags off" to "?classic=1": the classic boot must
|
||||
# stay byte-identical to the frozen v2 baseline FOREVER — synthetic fingerprint 0x3fa36874, zero gig
|
||||
# stay byte-identical to the frozen v2 baseline FOREVER — synthetic fingerprint 0x5f76e76, zero gig
|
||||
# layer, no opt-in flags, the R7 streamed roster (which IS the v2.0 baseline). Same assertions as the old
|
||||
# flags-off gate, now booted with ?classic=1 (the four flipped flags are off, everything else v2-default).
|
||||
head('CLASSIC REGRESSION (?classic=1 → the frozen v2 covenant)')
|
||||
@ -144,13 +149,16 @@ def classic_regression(p):
|
||||
# determinism anchor — run Lane A's selfcheck in-process (golden fingerprint)
|
||||
r = subprocess.run(['node', 'web/js/citygen/selfcheck.js'], cwd=str(ROOT),
|
||||
capture_output=True, text=True)
|
||||
hash_ok = (f'{GOLDEN_HASH:#010x}'.replace('0x', '0x') in r.stdout) or (hex(GOLDEN_HASH) in r.stdout) \
|
||||
or ('3fa36874' in r.stdout)
|
||||
# [R27w5] The hardcoded literal that used to ride here (`or '3fa36874' in r.stdout`) is GONE —
|
||||
# A caught it. It was dead the moment the covenant moved, and worse than dead: an OR-clause
|
||||
# matching a bare string anywhere in stdout would go green off a sentence ABOUT the old covenant.
|
||||
# The check derives from GOLDEN_HASH or it doesn't exist.
|
||||
hash_ok = (f'{GOLDEN_HASH:#010x}' in r.stdout) or (hex(GOLDEN_HASH) in r.stdout)
|
||||
# [R24] These are TWO assertions and they get two verdicts. This gate used to require
|
||||
# `hash_ok and r.returncode == 0` and report BOTH failures with the fingerprint's message — so
|
||||
# any unrelated red anywhere in Lane A's selfcheck (R24: 8 godverse-identity checks in a
|
||||
# different town) screamed "determinism/prime-law broken", the loudest alarm we own, about a
|
||||
# covenant that was provably intact (0x3fa36874, node-side, unmoved). A gate must name what it
|
||||
# covenant that was provably intact (then 0x3fa36874, node-side, unmoved). A gate must name what it
|
||||
# actually measured; F wrote this one, so F fixed it. The covenant is asserted on its own.
|
||||
if hash_ok: OK(f'plan fingerprint == golden {hex(GOLDEN_HASH)} — the classic covenant holds')
|
||||
else: FAIL(f'plan fingerprint != golden {hex(GOLDEN_HASH)} — determinism/prime-law broken')
|
||||
@ -235,7 +243,7 @@ def default_boot_gate(p):
|
||||
else:
|
||||
FAIL(f"default boot not fully live: gig={present}, tram={state['tram']}")
|
||||
# The default boot's plan IS the gig-augmented plan; selfcheck pins its fingerprint to the gig golden
|
||||
# (0xb1d48ea1) INTERNALLY and is ALL GREEN only if it matches. (selfcheck prints the base fingerprint
|
||||
# (GIG_GOLDEN) INTERNALLY and is ALL GREEN only if it matches. (selfcheck prints the base fingerprint
|
||||
# in its summary but asserts the gig golden silently — so we gate on the green run, not a substring.)
|
||||
r = subprocess.run(['node', 'web/js/citygen/selfcheck.js'], cwd=str(ROOT), capture_output=True, text=True)
|
||||
if r.returncode == 0 and 'ALL GREEN' in r.stdout:
|
||||
|
||||
@ -222,13 +222,13 @@ if (existsSync(R('web/js/citygen/plan.js'))) {
|
||||
`${p1?.streets?.edges?.length ?? '?'} edges, name "${p1?.name ?? '?'}"`);
|
||||
|
||||
// [F2/R8] plan-source selector — pin goldens keyed by (seed, plansrc, TOWN).
|
||||
// Lane A contract: synthetic 0x3fa36874 (selfcheck) · osm/melbourne 0x34cfdec0 · osm/katoomba 0x0f652510.
|
||||
// Lane A contract: synthetic 0x5f76e76 (selfcheck) · osm/melbourne 0x9c7e76b3 · osm/katoomba 0xf3aafec8.
|
||||
// Guarded: no-op until A ships generatePlanFor + generatePlanOSM.
|
||||
try {
|
||||
const bar = await import(R('web/js/citygen/index.js'));
|
||||
if (typeof bar.generatePlanFor === 'function' && typeof bar.generatePlanOSM === 'function') {
|
||||
const { xmur3 } = await import(R('web/js/core/prng.js'));
|
||||
const TOWN_GOLDENS = { melbourne: '0x34cfdec0', katoomba: '0x0f652510' };
|
||||
const TOWN_GOLDENS = { melbourne: '0x9c7e76b3', katoomba: '0xf3aafec8' };
|
||||
for (const [town, golden] of Object.entries(TOWN_GOLDENS)) {
|
||||
const a = bar.generatePlanFor(20261990, 'osm', { town }), b2 = bar.generatePlanFor(20261990, 'osm', { town });
|
||||
assert(JSON.stringify(a) === JSON.stringify(b2),
|
||||
|
||||
@ -7,7 +7,7 @@ The v4 thesis in five frames, into docs/shots/release_v4/ (+ contact.html). The
|
||||
• fitzroy_mecca — the mecca (160 shops, 2× anything absorbed before this epoch)
|
||||
• toowoomba_thintail — the thin tail (12 shops): proof the floor still reads as a town
|
||||
• synthetic_control — the procedural town, UNCHANGED: the control the whole epoch was measured against
|
||||
• classic_covenant — `?classic=1`: the v2 covenant made visible, byte-identical forever (0x3fa36874)
|
||||
• classic_covenant — `?classic=1`: the v2 covenant made visible, byte-identical forever (0x5f76e76)
|
||||
|
||||
Posing: every real-town frame resolves through Lane B's `DBG` retail-heart anchor / `clusterPose` — the
|
||||
primitive that came out of F's R21 finding (per-EDGE shop counts are ~1 on a real graph, so "densest edge"
|
||||
|
||||
@ -39,7 +39,7 @@ V5_TOUR = [
|
||||
"a MINT crate — plausible, NOT real: a seeded sample standing in for a shop whose till we don't hold. "
|
||||
"Same tier, same offline guarantee, different provenance — and the atlas says so in its own index"),
|
||||
('classic_covenant', f'classic=1&seed={SEED}', 2, 'street',
|
||||
"?classic=1 — the exact v2 town, byte-identical forever (0x3fa36874). Five epochs, one covenant"),
|
||||
"?classic=1 — the exact v2 town, byte-identical forever (0x5f76e76). Five epochs, one covenant"),
|
||||
]
|
||||
|
||||
SHOT_JS = r"""
|
||||
@ -82,47 +82,41 @@ async ({ mode, seg, shop }) => {
|
||||
}
|
||||
|
||||
// ── street ──
|
||||
// R21's cluster primitive answers "where is this town's heart". For `frontage:<id>` the question is
|
||||
// "where is THIS shop", and they are not the same question: Red Hill's densest cluster is 3 shops/60m
|
||||
// and does not contain Monster Robot, so the cluster pose produced a beautiful photograph of an empty
|
||||
// footpath — the Fitzroy trap a third time, wearing the fix for the first two. Anchor on the subject
|
||||
// when the frame is ABOUT the subject; use the cluster only when the frame is about the town.
|
||||
// [R27w5] The frontage frame now goes through B's `poseForShopFront` — B's helper, B's geometry, B's
|
||||
// shop. F re-derived this three times and produced three photographs of empty road; the fourth attempt
|
||||
// would have been the same mistake with more arithmetic. The lane that owns the frontage owns the pose.
|
||||
// (And the empty road was never the pose at all — the shops faced backwards. Fixed by A, wave 5.)
|
||||
const nById = new Map(plan.streets.nodes.map(n => [n.id, n]));
|
||||
const lots = (plan.lots || []).filter(l => l.use === 'shop' || l.use === 'anchor');
|
||||
let hub = -1, best = null;
|
||||
if (mode.startsWith('frontage:')) {
|
||||
const gid = +mode.split(':')[1];
|
||||
const sh = shops.find(x => x.godverseShopId === gid);
|
||||
best = sh ? (plan.lots || []).find(l => l.id === sh.lot) : null; // shop.lot → lot.id
|
||||
if (best) { hub = lots.filter(o => Math.hypot(o.x - best.x, o.z - best.z) < 60).length; }
|
||||
if (!best) return { err: 'no lot for godverse ' + gid };
|
||||
const r = D.poseForShopFront('g:' + mode.split(':')[1], 8);
|
||||
if (!r || r.error) return { err: 'poseForShopFront: ' + JSON.stringify(r) };
|
||||
// poseForShopFront RETURNS a pose; it does not apply one. F called it, error-checked it, and threw
|
||||
// the result away — so the camera never moved and the frame was the spawn paddock. A helper that
|
||||
// hands you a value is not a helper that does the thing.
|
||||
P.camera.position.set(r.pos[0], r.pos[1], r.pos[2]);
|
||||
P.camera.lookAt(r.look[0], r.look[1], r.look[2]);
|
||||
P.camera.updateMatrixWorld();
|
||||
const sh = shops.find(x => x.godverseShopId === +mode.split(':')[1]);
|
||||
const lot = sh ? (plan.lots || []).find(l => l.id === sh.lot) : null;
|
||||
hub = lot ? lots.filter(o => Math.hypot(o.x - lot.x, o.z - lot.z) < 60).length : -1;
|
||||
} else {
|
||||
for (const l of lots) { let c = 0; for (const o of lots) if (Math.hypot(o.x - l.x, o.z - l.z) < 60) c++;
|
||||
if (c > hub) { hub = c; best = l; } }
|
||||
}
|
||||
if (best) {
|
||||
const e = (plan.streets.edges || []).find(x => x.id === best.frontEdge) || plan.streets.edges[0];
|
||||
const a = nById.get(e.a), b2 = nById.get(e.b);
|
||||
const dx = b2.x - a.x, dz = b2.z - a.z, L = Math.hypot(dx, dz) || 1, ux = dx / L, uz = dz / L;
|
||||
let t = (best.x - a.x) * ux + (best.z - a.z) * uz;
|
||||
t = Math.max(6, Math.min(L - 6, t));
|
||||
if (mode.startsWith('frontage:')) {
|
||||
// A frontage frame FACES the shop. The cluster pose faces ALONG the edge — correct for "here is a
|
||||
// street", useless for "here is this shop": it photographed Musgrave Road with Monster Robot out of
|
||||
// frame beside the lens. Stand on the road at the lot's projection, step back along the lot→road
|
||||
// normal, and look AT it.
|
||||
const px = a.x + ux * t, pz = a.z + uz * t; // the lot's own point on the road
|
||||
let nx = px - best.x, nz = pz - best.z; // lot → road normal
|
||||
const NL = Math.hypot(nx, nz) || 1; nx /= NL; nz /= NL;
|
||||
const cx = best.x + nx * (NL + 7), cz = best.z + nz * (NL + 7);
|
||||
D.teleport(cx, cz, Math.atan2(best.x - cx, best.z - cz));
|
||||
} else {
|
||||
if (best) {
|
||||
const e = (plan.streets.edges || []).find(x => x.id === best.frontEdge) || plan.streets.edges[0];
|
||||
const a = nById.get(e.a), b2 = nById.get(e.b);
|
||||
const dx = b2.x - a.x, dz = b2.z - a.z, L = Math.hypot(dx, dz) || 1, ux = dx / L, uz = dz / L;
|
||||
let t = (best.x - a.x) * ux + (best.z - a.z) * uz;
|
||||
t = Math.max(6, Math.min(L - 6, t));
|
||||
const back = Math.min(26, t);
|
||||
D.teleport(a.x + ux * (t - back), a.z + uz * (t - back), Math.atan2(ux, uz));
|
||||
}
|
||||
} else { D.shot('street_noon'); }
|
||||
} else { D.shot('street_noon'); }
|
||||
}
|
||||
|
||||
await new Promise(r => setTimeout(r, 1800));
|
||||
const hud = document.getElementById('pc-hud'); if (hud) hud.style.display = 'none';
|
||||
for (const el of document.body.children) if (el.tagName !== 'CANVAS') el.style.display = 'none';
|
||||
for (let i = 0; i < 3; i++) await new Promise(r => requestAnimationFrame(r));
|
||||
const info = D.info();
|
||||
return { name: plan.name, shops: shops.length, edges: (plan.streets.edges || []).length,
|
||||
|
||||