diff --git a/README.md b/README.md index 707aca5..2cbaa4c 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Click **START** to lock the mouse. Then: | **W A S D** + mouse | walk / look (first person) | | walk into a shop door | enter the interior (open shops only — check the hours tooltip) | | **E** (inside a shop) | open the crate at a record bin — then it's cursor-driven: click sleeves to riffle, click BUY | +| **E** (at the counter) · **C** (street) · **SLEEP** (top bar) | sell a find to the keeper (always under what you paid) · open THE CRATE — your collection · sleep = save, the day advances, stock rotates | | click a book spine / toy box | pull-and-buy card (real price stickers in godverse shops) | | walk out the door / **Esc** | leave the shop (Esc also frees the mouse anywhere) | | **M** | 2D town map (street only) | diff --git a/tools/flags_check.py b/tools/flags_check.py index 99794c8..5f5bea3 100644 --- a/tools/flags_check.py +++ b/tools/flags_check.py @@ -24,7 +24,10 @@ except ImportError: "tools/.venv/bin/pip install playwright && tools/.venv/bin/python -m playwright install chromium") ROOT = pathlib.Path(__file__).resolve().parent.parent -PORT = 8130 +# [R31] PROCITY_QA_PORT: run the suite on an isolated port. The default (:8130) is also the house preview +# port — ensure_server() would silently REUSE a dev server already parked there, and a shared server is +# somebody else's cache semantics + somebody else's lifecycle. Gates want their own server or a named one. +PORT = int(os.environ.get('PROCITY_QA_PORT', '8130')) HOST = f'http://127.0.0.1:{PORT}' SEED = 20261990 GOLDEN_HASH = 0x5f76e76 # [R27w5] AMENDED. Was 0x3fa36874 "forever" since R16 — and that town @@ -173,6 +176,30 @@ def classic_regression(p): if state['winmap'] or state['digActive']: FAIL(f"a flipped/opt-in flag leaked into ?classic (winmap/dig): {state}") else: OK('flipped + opt-in flags off under ?classic (winmap/dig)') + # [R31 — THE DIG FLIP, classic side] dig is default-ON now, so classic's dig-lessness must be + # MEASURED at a bin, not inferred from an idle digActive (which is vacuously false on the street): + # enter a record shop, E aimed at a bin — the riffle must NOT open and ZERO dig DOM may exist + # (createDig is never constructed off-flag; the E falls through trySell(null game) + shelf no-op). + dug = pg.evaluate("""() => { + const P = window.PROCITY, D = window.DBG; D.setSegment(2); + const s = (P.plan.shops || []).find(x => x.type === 'record' && P.isOpen(x)); + if (!s) return { err: 'no open record shop' }; + D.enterShop(s.id); + const bins = []; P.interiorMode.current.group.traverse(o => { if (o.userData && o.userData.kind === 'bin') bins.push(o); }); + if (!bins.length) { D.exitShop(); return { err: 'no bins' }; } + const bp = new P.THREE.Vector3(); bins[0].getWorldPosition(bp); + P.camera.position.set(bp.x, 1.6, bp.z + 1.4); P.camera.lookAt(bp.x, bp.y + 0.4, bp.z); P.camera.updateMatrixWorld(); + window.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyE' })); + const opened = !!P.interiorMode.digActive; + const dom = document.querySelectorAll('[class^="pcdg"]').length; + D.exitShop(); + return { opened, dom }; + }""") + if dug.get('err'): WARN(f"?classic dig-inert leg unmeasured — {dug['err']}") + elif not dug['opened'] and dug['dom'] == 0: + OK('?classic dig INERT at the bin — E opened nothing, zero dig DOM (measured, not assumed)') + else: + FAIL(f"?classic dig LEAKED: E at a bin → opened={dug['opened']}, dig DOM nodes={dug['dom']}") if state['streamMode']: OK('R7 roster flip in effect — streamed roster ACTIVE under ?classic (the v2 baseline)') else: FAIL('roster flip REGRESSED — ?classic is not streaming (expected streamMode:true)') @@ -255,6 +282,37 @@ def default_boot_gate(p): OK(f"default-boot street budget @ venue block: {state['draws']} draws ≤ {STREET_DRAWS_MAX}, {state['tris']:,} tris ≤ {STREET_TRIS_MAX:,}") else: FAIL(f"default-boot street OVER budget @ venue block: {state['draws']} draws / {state['tris']:,} tris (Fable scope call — not silent-trim)") + # [R31 — THE DIG FLIP GATE] Fable's wave-0 playtest found the alpha-blocker this whole suite + # missed: every R30 game-loop gate booted `dig=1` EXPLICITLY (belt-and-braces that became a + # blindfold), so the suite was green while the NAKED boot's E did nothing at a bin — index.html + # still carried the v2-era opt-in. The gates tested the game; nobody tested that the default boot + # IS the game. So this gate now proves dig REACHABILITY with zero dig params: enter a record shop, + # E at an aimed bin, the riffle must open. Un-flip the default again and this line goes red. + f2 = state['flags'] or {} + if not f2.get('dig'): + FAIL(f"dig intent OFF on the naked boot (PROCITY.flags.dig falsy) — the R31 flip regressed: {f2}") + dug = pg.evaluate("""() => { + const P = window.PROCITY, D = window.DBG; D.setSegment(2); + const s = (P.plan.shops || []).find(x => x.type === 'record' && P.isOpen(x)); + if (!s) return { why: 'no open record shop' }; + D.enterShop(s.id); + const bins = []; P.interiorMode.current.group.traverse(o => { if (o.userData && o.userData.kind === 'bin') bins.push(o); }); + if (!bins.length) { D.exitShop(); return { why: 'no bins' }; } + const bp = new P.THREE.Vector3(); bins[0].getWorldPosition(bp); + P.camera.position.set(bp.x, 1.6, bp.z + 1.4); P.camera.lookAt(bp.x, bp.y + 0.4, bp.z); P.camera.updateMatrixWorld(); + window.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyE' })); + const opened = !!P.interiorMode.digActive; + const out = document.querySelector('.pcdg-out'); if (out) out.click(); + const closed = !P.interiorMode.digActive; + D.exitShop(); + return { opened, closed }; + }""") + if dug.get('opened') and dug.get('closed'): + OK('dig REACHABLE on the naked boot — E at a bin opened the riffle (and closed clean); the default boot IS the game') + elif dug.get('opened'): + WARN(f"naked-boot dig opened but close unconfirmed — {dug}") + else: + FAIL(f"THE DIG IS OFF ON A PLAIN BOOT — the R31 alpha-blocker is back: {dug}") if errs: FAIL(f"{len(errs)} console error(s) on the default boot; first: {errs[0][:140]}") else: OK('0 console errors on the default boot') finally: diff --git a/tools/qa/interior_scale_check.py b/tools/qa/interior_scale_check.py index 4d45a6c..ee3f754 100644 --- a/tools/qa/interior_scale_check.py +++ b/tools/qa/interior_scale_check.py @@ -24,10 +24,10 @@ Run: tools/.venv/bin/python tools/qa/interior_scale_check.py [--seed N] Setup: the same Playwright venv as tools/soak.py / tools/flags_check.py. Needs Lane B's window.DBG hook (?dbg=1) + window.PROCITY.interiorMode (Lane C/F). """ -import sys, time, socket, subprocess, pathlib +import sys, os, time, socket, subprocess, pathlib ROOT = pathlib.Path(__file__).resolve().parent.parent.parent -PORT = 8130 +PORT = int(os.environ.get('PROCITY_QA_PORT', '8130')) # [R31] port-isolated suite runs (see flags_check.py) HOST = f'http://127.0.0.1:{PORT}' SEED = 20261990 if '--seed' in sys.argv: diff --git a/tools/qa/town_matrix.py b/tools/qa/town_matrix.py index 6d3805b..f228240 100644 --- a/tools/qa/town_matrix.py +++ b/tools/qa/town_matrix.py @@ -16,10 +16,10 @@ Gates per town (all on the DEFAULT boot — gigs/weather/winmap/tram on, post-R1 Run: tools/.venv/bin/python tools/qa/town_matrix.py Needs the Playwright venv + Lane B's ?dbg=1 hook + Lane F's real-cache shell wiring (R17). """ -import sys, time, socket, subprocess, pathlib +import sys, os, time, socket, subprocess, pathlib ROOT = pathlib.Path(__file__).resolve().parent.parent.parent -PORT = 8130 +PORT = int(os.environ.get('PROCITY_QA_PORT', '8130')) # [R31] port-isolated suite runs (see flags_check.py) HOST = f'http://127.0.0.1:{PORT}' STREET_DRAWS_MAX, STREET_TRIS_MAX = 300, 200_000 # [R21 ledger #6c] B's R20 tram ruling: on a REAL-ROADS town the tram only runs if its best main chain diff --git a/web/index.html b/web/index.html index 8e8f6cc..c19bbb5 100644 --- a/web/index.html +++ b/web/index.html @@ -31,10 +31,10 @@

PROCITY

A procedurally generated, walkable 90s-Australian shopping town. - Every door opens (soon). Walk the strip, watch the day turn.
+ Every door opens. Dig the crates, sell the flips, sleep — the town remembers.
-
WASD move · shift run · mouse look · click a door · [ ] time of day · M map · P screenshot
+
WASD move · shift run · mouse look · click a door · E riffle a bin / sell at the counter · C your crate · SLEEP to save · [ ] time · M map