Lane F R16 (v3.1 THE FLIP) — close: default-boot tour + week soak + docs
The closing half of F's bookend. All gates green on the flipped tree (qa.sh --strict --soak: classic
regression + default-boot gate + smokes + no-giants incl. the seated drummer + the full-week soak).
- tour_shots.py -> docs/shots/release_v3_1/: the money shots on the DEFAULT boot (no per-shot flag juggling
now the flip covers it) + a ?classic=1 before/after ("the town before v3"). 7 shots, settled, 0 page errors.
- week_soak.py: re-pointed to the default boot; the leak check now treats an at/below-baseline OR non-climbing
trail as leak-free (a decreasing tex trail is GC/atlas-settle, not a leak — it was false-failing at warn-level).
- docs: README v3.1 flags table (defaults on + ?classic=1), CITY_SPEC v3.1 amendment (the rewritten prime
law + classic covenant, A's deferred edit), LANE_F_NOTES §16, F-progress R16.
Numbers (seed 20261990): default @ venue block 124 draws / 57k tris (<=300/200k); ?classic 161 draws / 24k
tris (the v2 baseline); seated drummer 1.32 m stature; week soak 42 cycles leak-free (net +0/-6).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ -4,6 +4,27 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Round 16 (v3.1 THE FLIP) — F bookends; the town shows its best by default
|
||||||
|
|
||||||
|
The gig layer soaked through three v3 stages, so v3.1 flips it (and the mature v2 flags) ON by default.
|
||||||
|
F owns the flip; A–E landed their polish against it (seated drummer, town-wide pools, backline, corner-poster).
|
||||||
|
|
||||||
|
**Opening commit `a4946b7` (the round's foundation):**
|
||||||
|
- **The flip** — `index.html` `CLASSIC` + `flagOn()`: gigs/weather/tram default-on, `loadPedFleet({sit:!CLASSIC})`,
|
||||||
|
`window.PROCITY.flags`; `buildings.js` winmap default-on + classic-aware. **`?classic=1`** forces the exact v2 boot.
|
||||||
|
- **The covenant moved** — `flags_off_regression → classic_regression` (byte-identical `0x3fa36874`); a new
|
||||||
|
`default_boot_gate` (all four flags on, gig layer present, gig golden `0xb1d48ea1`, budget); every gig smoke
|
||||||
|
re-pointed to the DEFAULT boot; qa.sh gate renamed.
|
||||||
|
- **Seated-stature gate** (ledger #3) — `interior_scale_check.py` + `flags_check.py` accept `procitySeated` figs
|
||||||
|
at [0.9,1.5] m; the drummer passes at **1.32 m**.
|
||||||
|
|
||||||
|
Verified: `?classic` byte-identical (0x3fa36874, gig absent); default = full town (3 venues / 3 genres, gig golden,
|
||||||
|
112 draws @ venue block); classic/default/district/continuity/cover all green. **Close: re-baseline, re-shot tour
|
||||||
|
→ `release_v3_1/` + a `?classic=1` before/after, docs (CITY_SPEC v3.1 flip+classic law, README flags table,
|
||||||
|
LANE_F_NOTES §16), tag `v3.1`.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Round 15 (v3.0 RELEASE CLOSE, `?gigs=1`) — F ran last; the release is closed
|
## Round 15 (v3.0 RELEASE CLOSE, `?gigs=1`) — F ran last; the release is closed
|
||||||
|
|
||||||
R14 landed A–E's hardening but **F never ran**, so v3.0 stayed untagged. R15 = the close. All deps were in
|
R14 landed A–E's hardening but **F never ran**, so v3.0 stayed untagged. R15 = the close. All deps were in
|
||||||
|
|||||||
51
README.md
@ -48,30 +48,35 @@ Seeded everything, `Math.random` banned in gen code · the game must run with ze
|
|||||||
instancing from day one · canvas text, no font files · characters = shared base rigs + one
|
instancing from day one · canvas text, no font files · characters = shared base rigs + one
|
||||||
clip bank, canonical sources live outside this repo · 🟢 web-ok licensing only.
|
clip bank, canonical sources live outside this repo · 🟢 web-ok licensing only.
|
||||||
|
|
||||||
## v3.0 — venues & gigs, THE DISTRICT (`?gigs=1`)
|
## v3.1 — THE FLIP: the town shows its best by default
|
||||||
|
|
||||||
The Friday-night layer, shipped behind a flag so the default boot stays byte-identical to v2 (the
|
The Friday-night layer soaked through alpha, beta, and the v3.0 release — so **v3.1 flips it on by
|
||||||
synthetic golden `0x3fa36874` never moves). Turn it on and each town seeds **2–4 venues** — a pub, a
|
default**, and the mature v2 flags flip with it. Boot the plain URL and each town seeds **2–4 venues**
|
||||||
band room, an RSL — with a **full seeded week of gigs** (`plan.gigs`), town-wide **posters**, and a
|
(pub, band room, RSL) with a **full seeded week of gigs**, town-wide **posters**, and a Friday night that
|
||||||
Friday night that reads: a **4-piece band** (front-line trio + drummer) with real instruments on the
|
reads: a **4-piece band** (trio + a **seated drummer**) with real instruments, a mixed **crowd**, an
|
||||||
stage, a mixed standing/dancing **crowd**, an outdoor **queue** at the door, a **cover charge** per
|
outdoor **queue**, a per-venue **cover charge**, muffled **gig spill** + **walla and applause**,
|
||||||
venue (paid & free nights), muffled **gig spill** through the wall + **walla and applause** inside,
|
town-wide **streetlamp pools**, and **identity continuity** — the ped you followed in is in the crowd.
|
||||||
**streetlamp pools** and lit frontages for the night walk, and **identity continuity** — the ped you
|
Plus **weather**, the **tram**, and the parallax interior-**window shader**, all live by default.
|
||||||
followed in off the street turns up in the crowd. Deterministic from the seed; asset-free
|
|
||||||
(`?noassets=1`) it all still happens with placeholders and zero fetches.
|
|
||||||
|
|
||||||
**Flags** (all default-off unless noted; compose freely):
|
**The covenant moved, it didn't disappear.** `?classic=1` boots the **exact v2 town** — byte-identical
|
||||||
|
forever (synthetic golden `0x3fa36874`, zero gig layer, zero fetch delta). That is the new frozen
|
||||||
|
regression target; the default boot is deterministic to the gig golden `0xb1d48ea1`. Asset-free
|
||||||
|
(`?noassets=1`) composes with the default boot and stays silent-and-fine.
|
||||||
|
|
||||||
| flag | what |
|
**Flags** — the four headliners are **ON by default**, each with a `=0` opt-out; `?classic=1` is the
|
||||||
|---|---|
|
master off:
|
||||||
| `?seed=N` | town seed (default `20261990`) |
|
|
||||||
| `?gigs=1` | the v3.0 gig layer — venues, week schedule, band/crowd/queue, cover, posters, night pass |
|
|
||||||
| `?weather=rain` · `?tram=1` · `?winmap=1` | wet strip · tram loop · parallax interior-window shader |
|
|
||||||
| `?roster=stream` | chunk-streamed citizen density (**on by default**) · `?pop=N` tunes it |
|
|
||||||
| `?dig=1` · `?stock=real` | crate-riffle on record bins · real GODVERSE sleeves in the stock |
|
|
||||||
| `?noassets=1` | asset-free — zero network, 100% placeholders, still legible |
|
|
||||||
| `?plansrc=osm` · `?mute=1` · `?dbg=1` | OSM plan source · silence · the `window.DBG` capture/soak harness |
|
|
||||||
|
|
||||||
**Release tour:** [`docs/shots/release_v3/contact.html`](docs/shots/release_v3/) — the six money shots
|
| flag | default | what |
|
||||||
(`queue_night` is the hero) with settled measurements. **QA:** `tools/qa.sh --strict` (6 gates) +
|
|---|---|---|
|
||||||
`tools/qa/week_soak.py` (the full-week soak) + the continuity/gig smokes in `tools/flags_check.py`.
|
| `?classic=1` | off | **the exact v2 boot** — all four below off, byte-identical to the frozen baseline |
|
||||||
|
| `?gigs=0` | **on** | the gig district — venues, week schedule, band/crowd/queue, cover, posters, night pass |
|
||||||
|
| `?weather=0` · `?tram=0` · `?winmap=0` | **on** | seeded weather · the tram loop · the parallax interior-window shader |
|
||||||
|
| `?roster=v1` · `?pop=N` | stream | chunk-streamed citizen density is the baseline; `?roster=v1` restores the fixed roster |
|
||||||
|
| `?dig=0` · `?stock=real` | dig on | crate-riffle on record bins · real GODVERSE sleeves in the stock |
|
||||||
|
| `?noassets=1` · `?plansrc=osm` · `?mute=1` · `?dbg=1` | off | asset-free (zero network) · OSM plan source · silence · the `window.DBG` capture/soak harness |
|
||||||
|
| `?seed=N` | `20261990` | town seed |
|
||||||
|
|
||||||
|
**Release tour:** [`docs/shots/release_v3_1/contact.html`](docs/shots/release_v3_1/) — the money shots on
|
||||||
|
the default boot, plus a `?classic=1` before/after. **QA:** `tools/qa.sh --strict` (classic regression +
|
||||||
|
default-boot gate + smokes + no-giants) `--soak` (the full-week soak); the seated drummer passes the
|
||||||
|
no-giants gate at the seated stature band `[0.9,1.5] m`.
|
||||||
|
|||||||
@ -226,11 +226,26 @@ night in v1; interior-mapping shader is a stretch goal).
|
|||||||
> golden re-pinned `0x4f4a549d → 0xb1d48ea1`; synthetic `0x3fa36874` still frozen. The v3.1 default flip
|
> golden re-pinned `0x4f4a549d → 0xb1d48ea1`; synthetic `0x3fa36874` still frozen. The v3.1 default flip
|
||||||
> + classic law is F's amendment to this section — see the flags table.)*
|
> + classic law is F's amendment to this section — see the flags table.)*
|
||||||
|
|
||||||
> **Prime flag law.** The gig layer is a **post-hoc augmentation** applied by the selector only when
|
> **Prime flag law.** The gig layer is a **post-hoc augmentation** applied by the selector when the gig
|
||||||
> `?gigs=1`. With the flag OFF, `generatePlanFor(seed, src)` returns the byte-identical v2 base plan —
|
> flag is on. With it off, `generatePlanFor(seed, src)` returns the byte-identical v2 base plan — **all v2
|
||||||
> **all v2 goldens stay frozen**. So this section adds fields that exist *only on gig-on plans*; it does
|
> goldens stay frozen**. So this section adds fields that exist *only on gig-on plans*; it does not touch
|
||||||
> not touch the v2 contract above. Deterministic from `(citySeed, customBands)`; works on synthetic AND
|
> the v2 contract above. Deterministic from `(citySeed, customBands)`; works on synthetic AND osm towns.
|
||||||
> osm towns. Node-pure (no THREE, no runtime fetch — the shell loads the band drop-in and passes it in).
|
> Node-pure (no THREE, no runtime fetch — the shell loads the band drop-in and passes it in).
|
||||||
|
>
|
||||||
|
> **Prime flag law — REWRITTEN at `v3.1` (ROUND16, THE FLIP; Lane F amendment).** The covenant MOVED, it
|
||||||
|
> did not disappear. `gigs`, `weather`, `winmap`, `tram` now default **ON**; the default boot is the full
|
||||||
|
> experience, deterministic to the **gig golden `0xb1d48ea1`**. Each flag keeps a `=0` opt-out, and
|
||||||
|
> **`?classic=1`** forces all four off at once:
|
||||||
|
> - **Classic law (the new frozen regression target):** the `?classic=1` boot is byte-identical to the v2
|
||||||
|
> baseline **forever** — synthetic fingerprint `0x3fa36874`, zero gig layer, **zero fetch delta** (no
|
||||||
|
> `custom_bands.json`, no `sit.glb`). The old "flags-off regression" gate is now the **classic-regression** gate.
|
||||||
|
> - **Default law:** the no-flag boot pins to the gig golden, and every gig / district / continuity / cover
|
||||||
|
> smoke **and the budget law** (interior ≤350 draws · street ≤300 / 200k tris, at the busiest venue block)
|
||||||
|
> now run against the **default boot with all four flags live**. `?noassets=1` composes with it and stays
|
||||||
|
> silent-and-fine.
|
||||||
|
> - The four flags read through one rule in the shell — `flagOn(name) = !classic && param !== '0'` (winmap
|
||||||
|
> reads the same rule in `buildings.js`); the shell publishes the intent on `window.PROCITY.flags`, and the
|
||||||
|
> seated drummer's `sit.glb` rides `!classic` so classic keeps its zero-fetch-delta.
|
||||||
>
|
>
|
||||||
> *(v3.0-alpha was one pub, one genre, one night's posters. **ROUND13 → v3.0-beta, the district:** 2–4
|
> *(v3.0-alpha was one pub, one genre, one night's posters. **ROUND13 → v3.0-beta, the district:** 2–4
|
||||||
> venues of three kinds, a full week's schedule across town, town-wide posters. Same flag, same laws.)*
|
> venues of three kinds, a full week's schedule across town, town-wide posters. Same flag, same laws.)*
|
||||||
|
|||||||
@ -594,3 +594,47 @@ pathspec:
|
|||||||
- **Drive live-spawned things explicitly**: automation throttles rAF, so the queue's seeded auto-drain may
|
- **Drive live-spawned things explicitly**: automation throttles rAF, so the queue's seeded auto-drain may
|
||||||
not fire on schedule — `smoke_continuity`/`week_soak`/`tour_shots` all call `admitOne()` / step the loop
|
not fire on schedule — `smoke_continuity`/`week_soak`/`tour_shots` all call `admitOne()` / step the loop
|
||||||
explicitly rather than trust the timer.
|
explicitly rather than trust the timer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. Round 16 — v3.1 THE FLIP (`?classic=1` is the new covenant). F bookends; opening below, close after.
|
||||||
|
|
||||||
|
The town shows its best by default. After three v3 stages the gig layer flips ON, and the mature v2 flags
|
||||||
|
(`weather`, `winmap`, `tram`) flip with it. F owns the flip; A–E landed their v3.1 polish against it (D's
|
||||||
|
seated drummer, B's town-wide pools + default re-baseline, C's backline unification, A's corner-poster +
|
||||||
|
no-spine fixture, E's `sit.glb`).
|
||||||
|
|
||||||
|
### The flip (ledger #1) — the covenant MOVED from "flags off" to `?classic=1`
|
||||||
|
- **Shell** (`index.html`): `const CLASSIC = has('classic') && get('classic')!=='0'`, `const flagOn = name
|
||||||
|
=> !CLASSIC && get(name)!=='0'`. `gigs`/`weather`/`tram` construct via `flagOn`; `loadPedFleet({sit:!CLASSIC})`
|
||||||
|
gives the drummer his clip while keeping classic zero-fetch; `window.PROCITY.flags` publishes the intent.
|
||||||
|
- **`buildings.js`**: `winmap` reads the SAME rule (default-on, classic-off) — the one cross-lane flip line,
|
||||||
|
because winmap's flag lives in B's file. Marked `[Lane F R16 THE FLIP]`.
|
||||||
|
- **Two goldens, two gates:** classic → `0x3fa36874` (byte-identical v2 forever, the frozen regression
|
||||||
|
target); default → the gig golden `0xb1d48ea1`. `flags_off_regression` became **`classic_regression`**
|
||||||
|
(boot `?classic=1`); a new **`default_boot_gate`** proves the no-flag boot is the full experience (all four
|
||||||
|
flags on, gig layer present, gig golden green, budget). Every gig smoke re-points to the DEFAULT boot.
|
||||||
|
|
||||||
|
### Seated-stature gate (ledger #3) — published in the brief, no mid-round handshake
|
||||||
|
A seated figure fails the standing no-giants floor by design. Contract (in the round brief so D built to it):
|
||||||
|
D tags seated figs `fig.userData.procitySeated = true`; F's `interior_scale_check.py` + `flags_check.py`
|
||||||
|
accept them at **stature ∈ [0.9, 1.5] m** (still < ceiling, feet planted); standing stays [1.4, 2.0]. Landed
|
||||||
|
in the opening commit. Verified live: the seated drummer passes at **1.32 m stature**.
|
||||||
|
|
||||||
|
### Numbers (measured, seed 20261990 → "Boolarra Heads")
|
||||||
|
| thing | number |
|
||||||
|
|---|---|
|
||||||
|
| `?classic=1` | byte-identical v2: fingerprint 0x3fa36874, gig layer absent, streamed roster on, 160 draws / 21k tris |
|
||||||
|
| default boot | all four flags on · gig golden 0xb1d48ea1 · 14 gigs, 15 posters · **112 draws @ the venue block** (≤300) |
|
||||||
|
| district (default) | 3 venues, **3 playing** (corner-poster re-pin shifted the schedule) · 3 genres live |
|
||||||
|
| seated drummer | **1.32 m** stature — passes the seated band [0.9,1.5]; the standing crew 1.68–1.81 m |
|
||||||
|
|
||||||
|
### The gotchas closed this round
|
||||||
|
- **A flag whose parse lives in another lane's file** (winmap in `buildings.js`) still flips as part of F's
|
||||||
|
structural change — one marked line, same `!classic && !=='0'` rule as the shell.
|
||||||
|
- **selfcheck prints the base fingerprint in its summary but asserts the GIG golden silently**, so the
|
||||||
|
default-boot gate gates on the ALL-GREEN run (which includes the gig-golden assert), not a stdout substring.
|
||||||
|
- **`P.winmap` isn't exposed** — the default gate verifies winmap *intent* via `window.PROCITY.flags`; the
|
||||||
|
actual shader is B's own `winmap` smoke.
|
||||||
|
- **`?classic` must zero the fetch delta**, so `sit.glb` is gated on `!CLASSIC` (not just on the fleet) — a
|
||||||
|
raw sit fetch under classic would break the covenant even though the drummer never spawns there.
|
||||||
|
|||||||
1
docs/shots/release_v3_1/contact.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!doctype html><meta charset="utf-8"><title>PROCITY v3.1 — release tour</title><style>body{background:#14110c;color:#f4efe6;font:14px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;margin:24px}h1{font-weight:700}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(420px,1fr));gap:18px}figure{margin:0;background:#1d1810;border-radius:10px;overflow:hidden}img{width:100%;display:block}figcaption{padding:8px 12px;color:#cdc4b2}b{color:#ffd9a0}</style><h1>PROCITY v3.1 — release money-shot tour (THE FLIP) <small>· seed 20261990 · settled measurements</small></h1><div class="grid"><figure><img src="venue_night.png" alt="venue_night" loading="lazy"><figcaption><b>venue_night</b><br>124 draws · 57,669 tris · queue 2 · 15 posters</figcaption></figure><figure><img src="district_posters.png" alt="district_posters" loading="lazy"><figcaption><b>district_posters</b><br>189 draws · 139,838 tris · queue 2 · 15 posters</figcaption></figure><figure><img src="queue_night.png" alt="queue_night" loading="lazy"><figcaption><b>queue_night</b><br>122 draws · 62,763 tris · queue 2 · 15 posters</figcaption></figure><figure><img src="tram_stop.png" alt="tram_stop" loading="lazy"><figcaption><b>tram_stop</b><br>111 draws · 178,122 tris · queue 0</figcaption></figure><figure><img src="gig_interior.png" alt="gig_interior" loading="lazy"><figcaption><b>gig_interior</b><br>The Exchange Hotel · Screaming Utes · 4-piece · crowd 8 (2 from roster) · 54 draws · 35,216→182,633 tris (pre-load→settled)</figcaption></figure><figure><img src="rain_verandah.png" alt="rain_verandah" loading="lazy"><figcaption><b>rain_verandah</b><br>137 draws · 77,338 tris · queue 2</figcaption></figure><figure><img src="district_posters_classic.png" alt="district_posters_classic" loading="lazy"><figcaption><b>district_posters_classic</b><br>?classic=1 — the town before v3 (no gig layer, no pools)</figcaption></figure></div>
|
||||||
BIN
docs/shots/release_v3_1/district_posters.png
Normal file
|
After Width: | Height: | Size: 219 KiB |
BIN
docs/shots/release_v3_1/district_posters_classic.png
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
docs/shots/release_v3_1/gig_interior.png
Normal file
|
After Width: | Height: | Size: 944 KiB |
BIN
docs/shots/release_v3_1/queue_night.png
Normal file
|
After Width: | Height: | Size: 361 KiB |
BIN
docs/shots/release_v3_1/rain_verandah.png
Normal file
|
After Width: | Height: | Size: 203 KiB |
BIN
docs/shots/release_v3_1/tram_stop.png
Normal file
|
After Width: | Height: | Size: 843 KiB |
BIN
docs/shots/release_v3_1/venue_night.png
Normal file
|
After Width: | Height: | Size: 334 KiB |
@ -24,14 +24,18 @@ ROOT = pathlib.Path(__file__).resolve().parent.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
|
||||||
OUTDIR = ROOT / 'docs' / 'shots' / 'release_v3'
|
OUTDIR = ROOT / 'docs' / 'shots' / 'release_v3_1'
|
||||||
if '--seed' in sys.argv:
|
if '--seed' in sys.argv:
|
||||||
SEED = int(sys.argv[sys.argv.index('--seed') + 1])
|
SEED = int(sys.argv[sys.argv.index('--seed') + 1])
|
||||||
|
|
||||||
# shot groups — each is one clean page load with the flags that shot needs (deterministic, same seed).
|
# [R16 THE FLIP] shot groups — the DEFAULT boot now has all four flags on, so most shots come from ONE clean
|
||||||
|
# load (no per-shot flag juggling). Rain is still forced (the default seed rolls clear); ?classic=1 gives the
|
||||||
|
# before/after comparison. Deterministic, same seed.
|
||||||
GROUPS = [
|
GROUPS = [
|
||||||
{'flags': 'gigs=1', 'street': ['venue_night', 'district_posters', 'queue_night'], 'interior': True},
|
{'flags': '', 'street': ['venue_night', 'district_posters', 'queue_night', 'tram_stop'], 'interior': True},
|
||||||
{'flags': 'weather=rain&tram=1&winmap=1', 'street': ['rain_verandah', 'tram_stop'], 'interior': False},
|
{'flags': 'weather=rain', 'street': ['rain_verandah'], 'interior': False},
|
||||||
|
# the town before v3 — the exact v2 boot; same main-street pose as district_posters for a clean A/B
|
||||||
|
{'flags': 'classic=1', 'street': ['district_posters'], 'interior': False, 'suffix': '_classic'},
|
||||||
]
|
]
|
||||||
|
|
||||||
# settle + re-pose a street bookmark once its live/async content (queue, instrument GLBs, poster textures,
|
# settle + re-pose a street bookmark once its live/async content (queue, instrument GLBs, poster textures,
|
||||||
@ -112,12 +116,12 @@ def contact_html(shots):
|
|||||||
f'<figure><img src="{s["file"]}" alt="{s["name"]}" loading="lazy">'
|
f'<figure><img src="{s["file"]}" alt="{s["name"]}" loading="lazy">'
|
||||||
f'<figcaption><b>{s["name"]}</b><br>{cap}</figcaption></figure>')
|
f'<figcaption><b>{s["name"]}</b><br>{cap}</figcaption></figure>')
|
||||||
return (
|
return (
|
||||||
'<!doctype html><meta charset="utf-8"><title>PROCITY v3.0 — release tour</title>'
|
'<!doctype html><meta charset="utf-8"><title>PROCITY v3.1 — release tour</title>'
|
||||||
'<style>body{background:#14110c;color:#f4efe6;font:14px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;margin:24px}'
|
'<style>body{background:#14110c;color:#f4efe6;font:14px/1.4 -apple-system,Segoe UI,Roboto,sans-serif;margin:24px}'
|
||||||
'h1{font-weight:700}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(420px,1fr));gap:18px}'
|
'h1{font-weight:700}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(420px,1fr));gap:18px}'
|
||||||
'figure{margin:0;background:#1d1810;border-radius:10px;overflow:hidden}img{width:100%;display:block}'
|
'figure{margin:0;background:#1d1810;border-radius:10px;overflow:hidden}img{width:100%;display:block}'
|
||||||
'figcaption{padding:8px 12px;color:#cdc4b2}b{color:#ffd9a0}</style>'
|
'figcaption{padding:8px 12px;color:#cdc4b2}b{color:#ffd9a0}</style>'
|
||||||
f'<h1>PROCITY v3.0 — release money-shot tour <small>· seed {SEED} · settled measurements</small></h1>'
|
f'<h1>PROCITY v3.1 — release money-shot tour (THE FLIP) <small>· seed {SEED} · settled measurements</small></h1>'
|
||||||
f'<div class="grid">{"".join(cards)}</div>')
|
f'<div class="grid">{"".join(cards)}</div>')
|
||||||
|
|
||||||
|
|
||||||
@ -127,7 +131,7 @@ def main():
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
sys.exit('playwright not installed — tools/.venv/bin/python -m playwright install chromium')
|
sys.exit('playwright not installed — tools/.venv/bin/python -m playwright install chromium')
|
||||||
|
|
||||||
print(f"\033[1mv3.0 RELEASE MONEY-SHOT TOUR\033[0m (seed {SEED} → docs/shots/release_v3/)")
|
print(f"\033[1mv3.1 RELEASE MONEY-SHOT TOUR\033[0m (seed {SEED} → docs/shots/release_v3_1/)")
|
||||||
OUTDIR.mkdir(parents=True, exist_ok=True)
|
OUTDIR.mkdir(parents=True, exist_ok=True)
|
||||||
srv = ensure_server()
|
srv = ensure_server()
|
||||||
shots, errs_total = [], 0
|
shots, errs_total = [], 0
|
||||||
@ -139,15 +143,17 @@ def main():
|
|||||||
errs = []
|
errs = []
|
||||||
pg.on('pageerror', lambda e: errs.append(str(e)))
|
pg.on('pageerror', lambda e: errs.append(str(e)))
|
||||||
boot(pg, grp['flags'])
|
boot(pg, grp['flags'])
|
||||||
|
sfx = grp.get('suffix', '')
|
||||||
for name in grp['street']:
|
for name in grp['street']:
|
||||||
stat = pg.evaluate(STREET_JS, name)
|
stat = pg.evaluate(STREET_JS, name)
|
||||||
out = OUTDIR / f'{name}.png'
|
fname = f'{name}{sfx}.png'
|
||||||
pg.screenshot(path=str(out))
|
pg.screenshot(path=str(OUTDIR / fname))
|
||||||
cap = f"{stat['draws']} draws · {stat['tris']:,} tris"
|
cap = (f"?classic=1 — the town before v3 (no gig layer, no pools)" if sfx
|
||||||
if stat.get('queue') is not None: cap += f" · queue {stat['queue']}"
|
else f"{stat['draws']} draws · {stat['tris']:,} tris")
|
||||||
if name in ('district_posters', 'venue_night', 'queue_night'): cap += f" · {stat['posters']} posters"
|
if not sfx and stat.get('queue') is not None: cap += f" · queue {stat['queue']}"
|
||||||
shots.append({'name': name, 'file': f'{name}.png', 'caption': cap})
|
if not sfx and name in ('district_posters', 'venue_night', 'queue_night'): cap += f" · {stat['posters']} posters"
|
||||||
print(f" \033[32m●\033[0m {name}: {cap}")
|
shots.append({'name': name + sfx, 'file': fname, 'caption': cap})
|
||||||
|
print(f" \033[32m●\033[0m {name}{sfx}: {cap}")
|
||||||
if grp['interior']:
|
if grp['interior']:
|
||||||
info = pg.evaluate(INTERIOR_JS)
|
info = pg.evaluate(INTERIOR_JS)
|
||||||
if info.get('ok'):
|
if info.get('ok'):
|
||||||
@ -168,7 +174,7 @@ def main():
|
|||||||
if srv: srv.terminate()
|
if srv: srv.terminate()
|
||||||
|
|
||||||
(OUTDIR / 'contact.html').write_text(contact_html(shots))
|
(OUTDIR / 'contact.html').write_text(contact_html(shots))
|
||||||
print(f"\n\033[1m{len(shots)} shots\033[0m → docs/shots/release_v3/ (+ contact.html) · {errs_total} page errors")
|
print(f"\n\033[1m{len(shots)} shots\033[0m → docs/shots/release_v3_1/ (+ contact.html) · {errs_total} page errors")
|
||||||
sys.exit(1 if errs_total or len(shots) < 6 else 0)
|
sys.exit(1 if errs_total or len(shots) < 6 else 0)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""PROCITY Lane F — the full-week gig soak (round 15, v3.0 release · ?gigs=1). NON-STRICT qa extra.
|
"""PROCITY Lane F — the full-week gig soak (round 15→16, on the DEFAULT boot (v3.1 flip)). NON-STRICT qa extra.
|
||||||
|
|
||||||
Rolls the clock through all 7 nights in ONE browser context, entering every playing venue each night,
|
Rolls the clock through all 7 nights in ONE browser context, entering every playing venue each night,
|
||||||
and proves the shell's per-night gig machinery holds over the week with no leaks:
|
and proves the shell's per-night gig machinery holds over the week with no leaks:
|
||||||
@ -135,7 +135,7 @@ def main():
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
sys.exit('playwright not installed — tools/.venv/bin/python -m playwright install chromium')
|
sys.exit('playwright not installed — tools/.venv/bin/python -m playwright install chromium')
|
||||||
|
|
||||||
print(f"\033[1mFULL-WEEK GIG SOAK\033[0m (seed {SEED} · 7 nights × all venues · leak/cover/roster · ?gigs=1)")
|
print(f"\033[1mFULL-WEEK GIG SOAK\033[0m (seed {SEED} · 7 nights × all venues · leak/cover/roster · default boot)")
|
||||||
srv = ensure_server()
|
srv = ensure_server()
|
||||||
try:
|
try:
|
||||||
with sync_playwright() as p:
|
with sync_playwright() as p:
|
||||||
@ -144,7 +144,7 @@ def main():
|
|||||||
errs = []
|
errs = []
|
||||||
pg.on('console', lambda m: errs.append(m.text) if m.type == 'error' else None)
|
pg.on('console', lambda m: errs.append(m.text) if m.type == 'error' else None)
|
||||||
pg.on('pageerror', lambda e: errs.append(str(e)))
|
pg.on('pageerror', lambda e: errs.append(str(e)))
|
||||||
pg.goto(f'{HOST}/index.html?seed={SEED}&gigs=1&dbg=1')
|
pg.goto(f'{HOST}/index.html?seed={SEED}&dbg=1') # [R16] the DEFAULT boot now IS the gig boot
|
||||||
pg.wait_for_function('window.DBG && window.DBG.ready === true', timeout=25000)
|
pg.wait_for_function('window.DBG && window.DBG.ready === true', timeout=25000)
|
||||||
pg.evaluate("() => { const o=document.getElementById('pc-start'); if(o) o.style.display='none'; }")
|
pg.evaluate("() => { const o=document.getElementById('pc-start'); if(o) o.style.display='none'; }")
|
||||||
try:
|
try:
|
||||||
@ -190,15 +190,17 @@ def main():
|
|||||||
lk = res['leakAfter']
|
lk = res['leakAfter']
|
||||||
trail = res['memTrail']
|
trail = res['memTrail']
|
||||||
tail = trail[-3:] if len(trail) >= 3 else trail
|
tail = trail[-3:] if len(trail) >= 3 else trail
|
||||||
flat = all(m['geo'] == tail[0]['geo'] and m['tex'] == tail[0]['tex'] for m in tail)
|
# a real leak CLIMBS every cycle; a flat OR DECREASING back half (GC / atlas settle) is not a leak, and
|
||||||
|
# ending AT OR BELOW the warmed baseline can't be one either. Only monotone growth into the tail fails.
|
||||||
|
climbing = (tail[-1]['geo'] > tail[0]['geo']) or (tail[-1]['tex'] > tail[0]['tex'])
|
||||||
traj = " → ".join(f"{m['geo']}/{m['tex']}" for m in trail)
|
traj = " → ".join(f"{m['geo']}/{m['tex']}" for m in trail)
|
||||||
if lk['geo'] == 0 and lk['tex'] == 0:
|
if lk['geo'] <= 0 and lk['tex'] <= 0:
|
||||||
OK(f"leak-free: geo/tex back to baseline across {res['cycles']} cycles (night trail {traj})")
|
OK(f"leak-free: geo/tex ended at/below the warmed baseline (net {lk['geo']:+d}/{lk['tex']:+d}) across {res['cycles']} cycles (trail {traj})")
|
||||||
elif flat:
|
elif not climbing:
|
||||||
OK(f"leak-free: warm-up residue geo {lk['geo']:+d}/tex {lk['tex']:+d}, then FLAT across the back half "
|
OK(f"leak-free: warm-up residue geo {lk['geo']:+d}/tex {lk['tex']:+d}, then flat/settling across the back half "
|
||||||
f"— no per-cycle growth (night trail {traj}, {res['cycles']} cycles)")
|
f"— no per-cycle growth (trail {traj}, {res['cycles']} cycles)")
|
||||||
else:
|
else:
|
||||||
FAIL(f"LEAK: geo {lk['geo']:+d}/tex {lk['tex']:+d} still climbing (night trail {traj}) across {res['cycles']} cycles")
|
FAIL(f"LEAK: geo {lk['geo']:+d}/tex {lk['tex']:+d} still climbing (trail {traj}) across {res['cycles']} cycles")
|
||||||
|
|
||||||
if res['cycles'] < 20:
|
if res['cycles'] < 20:
|
||||||
FAIL(f"cycles: only {res['cycles']} enter/exit (want ≥20)")
|
FAIL(f"cycles: only {res['cycles']} enter/exit (want ≥20)")
|
||||||
|
|||||||