Lane F R7 F1: roster flip — streamed roster is now the DEFAULT (prime-law amendment)
The first deliberate flags-off baseline change. On D's GO (43edabf: E1 ped sub-mesh merge447188avalidated → ~1 draw/near-rig → full density 241 worst-street draws <=300, leak-free). index.html: chunk-streamed roster default-on (chunkStream:{radius:2}); ?roster=v1 restores the old fixed roster (escape hatch); ?roster=stream still accepted (now a no-op). Hours-aware setNightLivelyChunks keeps the open-late block lively at night. Flags-off regression baseline RE-PINNED (same commit, prime-law amendment): OLD (roster-off, v1.1): 163 draws, 19000 tris NEW (roster default-on, R7): 164 draws, ~66000 tris Draws barely move — E1's merge makes each near-rig ~1 draw (exactly what gated the flip). The tris jump is the streamed crowd (D worst-view 65,899); it is frame-variable, so the drift WARN is now DRAWS-ONLY, tris guarded by the STREET_TRIS_MAX hard cap. PLAN GOLDENS UNCHANGED (synthetic 0x3fa36874, osm 0x34cfdec0). Also fixed the harness roster probe (read a nonexistent prop c.streaming → was blind to streamMode) + flipped the assertion to REQUIRE streamMode on default boot + added a ?roster=v1 escape-hatch check. flags_check GREEN; qa.sh --strict green; 0 console errors both modes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
3724a65505
commit
0b84560457
@ -4,7 +4,20 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Round 6 (finish v2 wiring + pay review debts → v2.0-alpha) — IN PROGRESS
|
## Round 7 (the flip round → v2.0-beta) — IN PROGRESS
|
||||||
|
|
||||||
|
Critical path resolved: E1 ped-merge (`447188a`), E2 stock pack (`0c12dac`), **D's GO** (`43edabf`),
|
||||||
|
C1 `?stock=real` (`3724a65`) all landed. F executes flip → strict harness → tag.
|
||||||
|
|
||||||
|
| task | status |
|
||||||
|
|---|---|
|
||||||
|
| **F1 — roster flip (prime-law amendment)** | ✅ **DONE + verified.** `index.html`: streamed roster is now the **default** (`chunkStream:{radius:2}` per D's spec); `?roster=v1` = escape hatch (fixed roster); `?roster=stream` still accepted (no-op). Hours-aware `setNightLivelyChunks` on the open-late block. **Same commit** re-pins the flags-off baseline: **old (163 draws/19k tris, roster-off) → new (164 draws/~66k tris, roster-on)** — draws barely move (E1's merge = ~1 draw/rig, the whole point), tris = the crowd (D worst-view 65,899; drift-WARN now draws-only, tris under the hard cap). **Plan goldens UNCHANGED (`0x3fa36874`).** Also fixed the harness's roster probe (was reading a nonexistent prop → blind) + flipped the assertion to require streamMode ON + added an escape-hatch check. Verified: default boot streamMode:true (146–150 active), `?roster=v1` streamMode:false (96), both render, 0 errors, ≤300 draws, harness GREEN. |
|
||||||
|
| **F2 — harness to strict** | ⏳ next: flags failures fail qa.sh; add `stock=real` + `weather=1` smokes (new flags at warn). |
|
||||||
|
| **F3 — tag v2.0-beta** | ⏳ after F2 + B weather / A osm-parity land (decision #6). |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Round 6 (finish v2 wiring + pay review debts → v2.0-alpha) — DONE ✅ (v2.0-alpha tagged)
|
||||||
|
|
||||||
Sequencing (Fable): **F1 enforcement harness lands FIRST** (protects the round), then F re-smokes as
|
Sequencing (Fable): **F1 enforcement harness lands FIRST** (protects the round), then F re-smokes as
|
||||||
lanes land, adds C's ≤350-draw interior assertion when C1 lands, extends the determinism gate if A's
|
lanes land, adds C's ≤350-draw interior assertion when C1 lands, extends the determinism gate if A's
|
||||||
|
|||||||
BIN
docs/shots/laneF/flip_default_busy.png
Normal file
BIN
docs/shots/laneF/flip_default_busy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 796 KiB |
BIN
docs/shots/laneF/flip_v1_escape.png
Normal file
BIN
docs/shots/laneF/flip_v1_escape.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 815 KiB |
@ -27,8 +27,15 @@ ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|||||||
PORT = 8130
|
PORT = 8130
|
||||||
HOST = f'http://127.0.0.1:{PORT}'
|
HOST = f'http://127.0.0.1:{PORT}'
|
||||||
SEED = 20261990
|
SEED = 20261990
|
||||||
GOLDEN_HASH = 0x3fa36874 # frozen determinism anchor (Lane A selfcheck)
|
GOLDEN_HASH = 0x3fa36874 # frozen determinism anchor (Lane A selfcheck) — UNCHANGED by the R7 flip
|
||||||
BASE_DRAWS, BASE_TRIS = 163, 19000 # flags-off DBG.shot('street_noon') snapshot (v1.1, measured); ±40% warn tolerance
|
# 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
|
||||||
|
# Draws barely move — E1's ped sub-mesh merge makes each near-rig ~1 draw, which is exactly what let
|
||||||
|
# the roster flip to full density. Tris jump is the streamed crowd (each near-rig ~2.4k tris); it is
|
||||||
|
# inherently variable frame-to-frame (how many rigs are near at sample time), so the drift WARN below
|
||||||
|
# is DRAWS-ONLY — tris is guarded strictly by the STREET_TRIS_MAX hard cap, not a soft snapshot.
|
||||||
|
BASE_DRAWS, BASE_TRIS = 164, 66000
|
||||||
STREET_DRAWS_MAX, STREET_TRIS_MAX = 300, 200_000
|
STREET_DRAWS_MAX, STREET_TRIS_MAX = 300, 200_000
|
||||||
KNOWN_FLAGS = ['winmap=1', 'dig=1', 'roster=stream'] # plansrc=osm auto-appended if Lane A landed it
|
KNOWN_FLAGS = ['winmap=1', 'dig=1', 'roster=stream'] # plansrc=osm auto-appended if Lane A landed it
|
||||||
|
|
||||||
@ -98,7 +105,7 @@ def flags_off_regression(p):
|
|||||||
return { draws:i.drawCalls, tris:i.tris, mode:i.mode,
|
return { draws:i.drawCalls, tris:i.tris, mode:i.mode,
|
||||||
winmap: !!(P.winmap && P.winmap.active),
|
winmap: !!(P.winmap && P.winmap.active),
|
||||||
digActive: !!(P.interiorMode && P.interiorMode.digActive),
|
digActive: !!(P.interiorMode && P.interiorMode.digActive),
|
||||||
streaming: !!(c.streaming || c.mode==='stream') };
|
streamMode: !!c.streamMode }; // R7: real roster-mode flag (was reading a nonexistent prop)
|
||||||
}""")
|
}""")
|
||||||
# determinism anchor — run Lane A's selfcheck in-process (golden fingerprint)
|
# determinism anchor — run Lane A's selfcheck in-process (golden fingerprint)
|
||||||
r = subprocess.run(['node', 'web/js/citygen/selfcheck.js'], cwd=str(ROOT),
|
r = subprocess.run(['node', 'web/js/citygen/selfcheck.js'], cwd=str(ROOT),
|
||||||
@ -108,23 +115,40 @@ def flags_off_regression(p):
|
|||||||
if hash_ok and r.returncode == 0: OK(f'plan fingerprint == golden {hex(GOLDEN_HASH)} (selfcheck green)')
|
if hash_ok and r.returncode == 0: OK(f'plan fingerprint == golden {hex(GOLDEN_HASH)} (selfcheck green)')
|
||||||
else: FAIL(f'plan fingerprint != golden {hex(GOLDEN_HASH)} — determinism/prime-law broken')
|
else: FAIL(f'plan fingerprint != golden {hex(GOLDEN_HASH)} — determinism/prime-law broken')
|
||||||
|
|
||||||
if state['winmap'] or state['digActive'] or state['streaming']:
|
# winmap + dig stay OPT-IN (off by default). The streamed roster is now the shipping DEFAULT
|
||||||
FAIL(f"a v2 subsystem is ACTIVE on default boot: {state}")
|
# (R7 flip / prime-law amendment) — so it must be ON, not off, on a no-flag boot.
|
||||||
else: OK('all v2 subsystems inactive on default boot (winmap/dig/roster off)')
|
if state['winmap'] or state['digActive']:
|
||||||
|
FAIL(f"an opt-in v2 flag is active on default boot (winmap/dig): {state}")
|
||||||
|
else: OK('opt-in flags off on default boot (winmap/dig)')
|
||||||
|
if state['streamMode']:
|
||||||
|
OK('R7 roster flip in effect — streamed roster ACTIVE on default boot')
|
||||||
|
else: FAIL('roster flip REGRESSED — default boot is not streaming (expected streamMode:true)')
|
||||||
|
|
||||||
if state['draws'] <= STREET_DRAWS_MAX and state['tris'] <= STREET_TRIS_MAX:
|
if state['draws'] <= STREET_DRAWS_MAX and state['tris'] <= STREET_TRIS_MAX:
|
||||||
OK(f"flags-off street view within v1 budget (draws {state['draws']}≤{STREET_DRAWS_MAX}, tris {state['tris']}≤{STREET_TRIS_MAX})")
|
OK(f"flags-off street view within budget (draws {state['draws']}≤{STREET_DRAWS_MAX}, tris {state['tris']}≤{STREET_TRIS_MAX})")
|
||||||
else: FAIL(f"flags-off street view OVER v1 budget: draws {state['draws']}, tris {state['tris']}")
|
else: FAIL(f"flags-off street view OVER budget: draws {state['draws']}, tris {state['tris']}")
|
||||||
|
|
||||||
|
# Drift WARN is DRAWS-ONLY — tris is crowd-variable post-flip (guarded by the hard cap above).
|
||||||
dd = abs(state['draws'] - BASE_DRAWS) / BASE_DRAWS
|
dd = abs(state['draws'] - BASE_DRAWS) / BASE_DRAWS
|
||||||
dt = abs(state['tris'] - BASE_TRIS) / BASE_TRIS
|
if dd <= 0.40:
|
||||||
if dd <= 0.40 and dt <= 0.40:
|
OK(f"draws within 40% of re-pinned baseline ({state['draws']} vs {BASE_DRAWS}); tris {state['tris']} (crowd-variable, ≤cap)")
|
||||||
OK(f"draws/tris within 40% of v1.1 snapshot ({state['draws']} vs {BASE_DRAWS}, {state['tris']} vs {BASE_TRIS})")
|
|
||||||
else:
|
else:
|
||||||
WARN(f"draws/tris drifted >40% from v1.1 snapshot ({state['draws']} vs {BASE_DRAWS}, {state['tris']} vs {BASE_TRIS}) — investigate")
|
WARN(f"draws drifted >40% from baseline ({state['draws']} vs {BASE_DRAWS}) — investigate")
|
||||||
|
|
||||||
if errs: FAIL(f"{len(errs)} console error(s) on default boot; first: {errs[0][:140]}")
|
if errs: FAIL(f"{len(errs)} console error(s) on default boot; first: {errs[0][:140]}")
|
||||||
else: OK('0 console errors on default boot')
|
else: OK('0 console errors on default boot')
|
||||||
|
|
||||||
|
# R7 escape hatch: ?roster=v1 must restore the fixed roster (streamMode:false), render, no errors.
|
||||||
|
b2, pg2, errs2 = new_page(p)
|
||||||
|
try:
|
||||||
|
boot(pg2, 'roster=v1')
|
||||||
|
esc = pg2.evaluate("() => ({ streamMode: !!(window.PROCITY.citizens||{}).streamMode, mode: window.DBG.info().mode })")
|
||||||
|
if not esc['streamMode'] and esc['mode'] == 'street':
|
||||||
|
OK('escape hatch ?roster=v1 → fixed roster (streamMode:false), renders')
|
||||||
|
else: FAIL(f"escape hatch ?roster=v1 broken: {esc}")
|
||||||
|
if errs2: FAIL(f"?roster=v1: {len(errs2)} console error(s); first: {errs2[0][:140]}")
|
||||||
|
finally:
|
||||||
|
b2.close()
|
||||||
finally:
|
finally:
|
||||||
b.close()
|
b.close()
|
||||||
|
|
||||||
|
|||||||
@ -147,10 +147,26 @@ const interiorMode = createInteriorMode({ THREE, renderer, camera, plan, fleet,
|
|||||||
// open and re-lock (on click) after. digActive gates the unlock→leaveShop guard below so opening a bin
|
// open and re-lock (on click) after. digActive gates the unlock→leaveShop guard below so opening a bin
|
||||||
// doesn't read as "walked out of the shop".
|
// doesn't read as "walked out of the shop".
|
||||||
window.addEventListener('procity:digOpen', () => player.unlock());
|
window.addEventListener('procity:digOpen', () => player.unlock());
|
||||||
// [Lane F integration] street pedestrians (Lane D). graph = plan.streets directly; with the rig fleet the
|
// [Lane F §F1 — ROUND 7 ROSTER FLIP · prime-law amendment] The chunk-streamed roster is now the
|
||||||
// near tier shares GLB meshes (cheap). `?pop=` tunable. setTimeOfDay/update in the street loop.
|
// DEFAULT: the town is permanently busier. `?roster=v1` restores the old fixed roster (escape hatch);
|
||||||
const citizens = new CitizenSim({ renderer, scene, camera, citySeed: plan.citySeed, graph: plan.streets, fleet });
|
// `?roster=stream` is still accepted (now a no-op — stream is default). Gated on Lane D's GO: E1's
|
||||||
citizens.setPopulation((parseInt(params.get('pop'), 10) || 140) >>> 0); // midday ≈96 active
|
// ped sub-mesh merge makes each near-rig ~1 draw, so full density (perChunk 16) = 241 worst-street
|
||||||
|
// draws ≤300. Plan goldens do NOT move (roster doesn't touch the plan). See LANE_D_NOTES → Lane F.
|
||||||
|
const rosterV1 = params.get('roster') === 'v1';
|
||||||
|
const citizens = new CitizenSim({ renderer, scene, camera, citySeed: plan.citySeed, graph: plan.streets,
|
||||||
|
fleet, chunkStream: rosterV1 ? null : { radius: 2 } }); // default-on
|
||||||
|
citizens.setPopulation((parseInt(params.get('pop'), 10) || 140) >>> 0); // fixed-roster (?roster=v1) size; stream = per-chunk density
|
||||||
|
// [Lane F] hours-aware: keep the open-late block lively at night (Lane D3 setNightLivelyChunks). Keys via
|
||||||
|
// sim.chunkKeyAt so they match sim's own chunk-key format; openLate shop = hours close ≥22 (Lane A contract).
|
||||||
|
if (!rosterV1) {
|
||||||
|
const late = plan.shops.find((s) => s.hours && s.hours[1] >= 22);
|
||||||
|
const lot = late && plan.lots.find((l) => l.id === late.lot);
|
||||||
|
if (lot) {
|
||||||
|
const keys = [];
|
||||||
|
for (let dx = -1; dx <= 1; dx++) for (let dz = -1; dz <= 1; dz++) keys.push(citizens.chunkKeyAt(lot.x + dx * 64, lot.z + dz * 64));
|
||||||
|
citizens.setNightLivelyChunks(keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
document.addEventListener('visibilitychange', () => citizens.setPaused(document.hidden));
|
document.addEventListener('visibilitychange', () => citizens.setPaused(document.hidden));
|
||||||
|
|
||||||
// spawn on the footpath near the west end of the strip, looking east down main street
|
// spawn on the footpath near the west end of the strip, looking east down main street
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user