Lane F R38 (v8 WAVE 1, CLOSE): the budget did NOT move — 292/300 stands, and four gates ship with four controls

THE RECONCILIATION. The brief's naive sum (292 + washing 1 + magpie 1 = 294) is wrong, in our
favour. The pinned worst-DRAW frame is a NIGHT frame and both of wave 1's draw-spending layers are
structurally zero at night: the washing comes in (count 0), the magpie roosts (count 0). Wave 1's
one draw is spent in daylight, where the walked peak sits ten draws lower.

Two pinned trees, assets symlinked to the live tree on BOTH sides, 802 stations at 2 m down the
main spine at yaw 0 (the worst bearing), citizen sim and tram LIVE, 1280x720:

  NIGHT worst draws   292 -> 292   (+0)     tris 120,145 -> 122,879
  NOON  worst draws   282 -> 283   (+1)     tris  99,872 -> 103,118
  ?classic=1          269 / 90,580 unmoved
  fitzroy_real        172 / 130,727 unmoved      bendigo_real 204 / 131,492 unmoved
                                                 (even with ?magpie=1 forced)

The control reproduced R37's pin TO THE BYTE, twice, on a fresh checkout. Every revert is exact:
?yards=0 at night returns all three control numbers; ?yards=0 at NOON returns 282/99,872, which
isolates the noon +1 to the washing and nothing else. MARGIN IS STILL EIGHT DRAWS.

LANE B's "control 346", answered rather than dismissed. As a delta it is the wall-clock ped-spawn
artifact R37 named, and B was right to hide the sim. But 346 > 300 is a claim about the world, and
neither stepwalk (walks the chunks in, photographs a half-dressed street) nor dwell (fills the
street, arrives clean at 27 chunks) can adjudicate it. budget_walk.py gains --method stepsettle:
the audit's fixed-yaw stations, each DRAINED then held N real rAF frames — R+1 dispose residue AND
a fully-acquired crowd in the same frame. Like for like on the same 101 stations, the settle is
worth +3 draws at noon and -9 at night, and the SIGN FLIP is the finding: at night the settled
street retires near rigs to impostors (tris 120,145 -> 100,939) — the crowd goes home, and a
sampler that never waits does not see it happen. The highest number anywhere in the reconciliation
(4 methods x 2 trees x 2 segments x 5 towns) is 292, the pin. NOTHING READ OVER 300.

A CORRECTION TO MY OWN R37 PIN: the corpus TRI worst is 130,727 on fitzroy_real, not 124,793 — and
it reads 130,727 on the CONTROL tree, so it is my R37 station spacing (12-bearing 8 m sweep) versus
this round's 2 m stations, not a wave-1 cost. Station spacing, not settle policy, is the biggest
lever on a walked worst frame. A pin is only as good as its spacing, and mine had not said so.

FOUR GATES, FOUR CONTROLS DEMONSTRATED (smoke_ambient / smoke_character / smoke_magpie /
smoke_yards, wired into flags_check.main()). A gate that cannot fail is not a gate — this round
earned the sentence twice, R37 killing two vacuous gates and B catching its own season mechanism
producing a constant that would have gated green under ?magpie=1.

  1.1 audio  — 6 sources anchored 63/8/81/32/39/57 (B's numbers exactly); dog AUDIBLE at 5.9 m with
    1 play scheduled (for a one-shot, `audible` is only published once plays>0, so one word covers
    fetch/decode/gain/schedule/counter); HOURS enforced (sprinkler audible 06:30 -> idle 12:30, same
    pose); DAY-OF-WEEK enforced (mower idle Mon -> audible Sat, same pose); PAN is a bearing (yaw
    0/90/180/270 -> +0.00 +0.85 -0.00 -0.85); ZERO DRAWS byte-identical to ?ambient=0. THE CONTROL:
    a second server whose root is web/ symlink-for-symlink except one patched manifest with
    sfx['dog-bark-fence'] renamed -> no-entry, 0 plays, never audible, other five unchanged, 0
    console errors. Printed on every run: audible OUTPUT is unverifiable headlessly. Nobody in this
    loop has ears.
  1.2 character — classic is the frozen v1 literals, all tints null, 5 ground meshes; all 23 shipped
    keys matched PER TOWN including the two carrying state:''; the control fires (unknown key ->
    matched:false); 21 distinct palettes across 23 towns (one shared palette FAILS by name); and the
    two orphans are ON THE GPU — ground-gravel.jpg bound on Castlemaine, ground-reddust.jpg on
    Darwin, read off the live material.
  1.3 magpie — the latch arm verbatim with its positive control FIRST; day-roll clears it;
    localStorage gained NO new key (baseline taken after a game.save() — the first cut blamed the
    magpie for v7's own write); the season is 23 distinct positions across 24 towns run UNFORCED,
    against the first cut's 24-of-24-at-214, newtown_real in season on day 1, 129/1000 seeds against
    a 11.5% design ratio; territory 6 -> 157 synthetic -> bendigo; +1 draw / +182 tris at ONE pose
    reused across both boots.
  1.4 yards — 181 lots, 181 hoists -> 394 items, 256 active, 0 at night; cost decomposition at one
    pose with the sim quiet: box props +0 draws (in fact -2, the 36 `yard` lots used to fall through
    to buildShopfront) with +1,936 tris, washing exactly +1 draw / +512 tris; and on real towns it
    SKIPS LOUDLY — a ⊘ SCOPE line printing 0 house/yard lots / 0 hoists / mesh never created, beside
    the synthetic 181 that makes the zero attributable.

Caught in my own work: the pan arm's first draft used two bearings and read +0.00/-0.00 because
that anchor sat on the forward axis — it would have called a working panner inconclusive. Four
bearings now. The gate's first draft failed the standard the gate exists to enforce.

SUITE: tools/qa.sh --strict --matrix GREEN on the true final tree (74d6257) — 7 passed, 0 failed,
0 warn, 0 skipped; flags_check GREEN 0/0 with all four new gates inside it; MATRIX GREEN 10 towns
x 7 gates, 0 fetches under ?noassets on all ten.

Held: nothing. Recorded not fixed: magpie.js says 154 triangles at :9/:123 and 182 at :42 (measured
182); E's three published GLBs are manifest-and-depot but not under web/assets/models (staged in
pipeline/_normalized, nothing loads them, the validator's contract covers it — not a defect).

NO TAG — wave 1 is content; the epoch tags when it is whole.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-08-03 19:48:45 +10:00
parent 1c4552a947
commit 763eabde5e
4 changed files with 1252 additions and 1 deletions

View File

@ -947,3 +947,81 @@ F drives/inspects via `window.PROCITY` + JS for programmatic checks.
msg `Lane F: integration — interiors + keepers + street peds + QA harness + barrel + skins fix`.
- A local `python3 -m http.server 8130` is running for the smoke test (harmless; kill when done).
- Lane prompts hard-code `/Users/jing/Documents/PROCITY`; real path is `/Users/m3ultra/Documents/procity`.
---
## Round 38 (v8 WAVE 1 — INHABITATION) — the budget reconciliation · four new gates, four controls · the round close
**THE BUDGET OF RECORD DOES NOT MOVE: 292 / 300 draws, margin still EIGHT.** The brief's naive sum
(292 + washing 1 + magpie 1 = 294) is wrong in our favour. **The pinned worst-draw frame is a NIGHT
frame and both draw-spending layers are structurally zero at night** — the washing comes in
(`count = 0`), the magpie roosts (`count = 0`). Wave 1's one draw is spent in daylight, where the
walked peak sits ten draws lower. Two pinned trees (`6bc7926` control, byte-identical under `web/` to
R37's `7bd96e5`; `4514cb9` treatment, byte-identical under `web/js` to the final `74d6257`), assets
symlinked to the live tree on both sides, 802 stations @ 2 m at **yaw 0**, sim + tram **LIVE**:
**night 292 → 292 (+0)** / 120,145 → 122,879 tris; **noon 282 → 283 (+1)** / 99,872 → 103,118;
`?classic=1` **269 / 90,580 → unchanged**; `fitzroy_real` **172 / 130,727 → unchanged**;
`bendigo_real` **204 / 131,492 → unchanged even with `?magpie=1` forced**. The control reproduced
R37's pin **to the byte, twice**. Every revert is exact: `?yards=0` at night gives the control's three
numbers; `?yards=0` at noon gives 282 / 99,872, isolating the noon +1 to the washing alone.
**Lane B's "control 346" — answered rather than dismissed.** As a *delta* it is the wall-clock ped
spawn artifact R37 named (114 draws of swing between two browser instances measures the sim, not the
build), and B was right to hide the sim. But 346 > 300 is a claim about the world, and neither
`stepwalk` (walks the chunks in, photographs a half-dressed street) nor `dwell` (fills the street,
arrives clean at 27 chunks) can adjudicate it. **`tools/qa/budget_walk.py` gains `--method
stepsettle`**: the audit's fixed-yaw stations, each **drained then held N real rAF frames** — R+1
dispose residue AND a fully-acquired crowd in the same frame. **Like for like — the same 101 stations at 8 m, yaw 0 — the settle is worth +3 draws at noon (277→280 control, 278→281 R38) and 9 at night (287→278 both trees), and the sign flip is the finding: at night the settled street RETIRES near rigs to impostors (tris 120,145→100,939), so the crowd goes home and a sampler that never waits does not see it. **The highest draw count anywhere in the reconciliation — 4 methods × 2 trees × 2 segments × 5 towns — is 292, the pin. Nothing read over 300.** The mechanism that could legitimately have breached it (full crowd inside a full R+1 dispose window) now reads 280/281. B's 346 was the artifact, and the refusal is now backed by the measurement that could have falsified it.
**A correction to my own R37 pin:** the corpus TRI worst is **130,727** on `fitzroy_real`, not
124,793 — and it reads 130,727 on the **control** tree, so it is my R37 sampling density (12-bearing
8 m sweep) versus this round's 2 m fixed-yaw stations, not a wave-1 cost. Re-pinned. *A pin is only
as good as its station spacing, and mine had not said so.*
**FOUR NEW GATES, FOUR CONTROLS DEMONSTRATED** (`smoke_ambient` · `smoke_character` · `smoke_magpie`
· `smoke_yards`, all wired into `flags_check.main()`):
- **§1.1 audio** — six sources anchored (63/8/81/32/39/57, B's numbers exactly); dog **audible at
5.9 m with 1 play scheduled** (for a one-shot `audible` is only published once `plays > 0`, so one
word covers fetch→decode→gain→schedule→counter); **hours enforced** (sprinkler audible 06:30 → idle
12:30, same pose); **day-of-week enforced** (mower idle Mon → audible Sat, same pose); **pan is a
bearing** (yaw 0/90/180/270 → +0.00 +0.85 0.00 0.85); **zero draws byte-identical to
`?ambient=0`**. **THE CONTROL:** a second server, root symlinked file-for-file except one patched
`manifest.json` with `sfx['dog-bark-fence']` renamed → that source reports **`no-entry`, 0 plays,
never audible**, other five unchanged, **0 console errors**. Stated on every run: audible OUTPUT is
unverifiable headlessly — **nobody in this loop has ears**.
- **§1.2 character** — classic is the frozen v1 literals with all tints null and 5 ground meshes;
**all 23 shipped keys matched per-town including the two carrying `state: ''`**; **the control
fires** (an unknown key returns `matched:false`); **21 distinct palettes across 23 towns** (a single
shared palette FAILS by name); and the two orphans are **on the GPU**`ground-gravel.jpg` bound on
Castlemaine, `ground-reddust.jpg` on Darwin, read off the live material.
- **§1.3 magpie** — the latch arm verbatim with its **positive control first** (TRUE before the roll);
day-roll clears it; **localStorage gained NO new key** (baseline taken *after* a `game.save()`
the first cut blamed the magpie for v7's own write); **the season is 23 distinct positions across
24 towns, run UNFORCED**, against B's first cut's 24-of-24-at-214, with `newtown_real` in season on
day 1 and 129/1000 seeds hitting the 11.5% design ratio; **territory 6 → 157** synthetic → bendigo;
**+1 draw / +182 tris** at one pose reused across both boots.
- **§1.4 yards** — 145 house + 36 yard = 181 lots, 181 hoists → 394 items, 256 active, **0 at night**;
the cost decomposition at one pose with the sim quiet: **box props +0 draws** (and **2**, because
the 36 `yard` lots used to fall through to `buildShopfront()`) with **+1,936 tris**, **washing
exactly +1 draw / +512 tris**; and on real towns it **SKIPS LOUDLY** — a `⊘ SCOPE` line printing
0 house/yard lots / 0 hoists / mesh never created, next to the synthetic 181 that makes the zero
attributable.
**Also caught, in my own gates:** the pan arm's first draft used two bearings and read `+0.00/0.00`
because that anchor sat on the forward axis — it would have called a working panner inconclusive.
Four bearings. *The gate's first draft failed the standard the gate exists to enforce.*
**SUITE: `tools/qa.sh --strict --matrix` GREEN on the true final tree** (`74d6257` — E's session-2
publish landed mid-round, `web/` diff vs B's `4514cb9` is manifest + one thumb, **no code**, and both
were already in the tree both budget servers read): **7 passed · 0 failed · 0 warn · 0 skipped**;
**flags_check GREEN 0/0** with all four new gates inside it; **MATRIX GREEN 10 towns × 7 gates**,
0 fetches under `?noassets` on all ten, tram verdicts unchanged. Wave 1 moved nothing in the matrix.
**Held: nothing.** Recorded, not fixed (other people's files): `magpie.js` says **154 triangles** at
`:9`/`:123` and **182** at `:42` — measured 182, so two stale comments in B's file; E's three
published GLBs are manifest-and-depot but not under `web/assets/models/` (staged in
`pipeline/_normalized/`, nothing loads them, validator's contract covers it — **not a defect**).
Carried debt unchanged: A's D1D4 · the matrix's budget column is bookmark numbers · `?r=3` breaches
at 307 · the `?classic=1` town-selector question.
**No tag** — wave 1 is content; the epoch tags when it is whole.

View File

@ -2438,3 +2438,273 @@ measures it. Making the matrix walk-and-turn every town is a real improvement an
**filed to the v8 debt ledger, not smuggled into a floor round.**
**No tag.** Wave 0 levels the floor; it does not ship a version.
---
## §38 — R38: v8 WAVE 1, INHABITATION. F: the budget reconciliation · the four gates · the round close
**Verdict: WAVE 1 SHIPS. The budget of record does NOT move — still 292 / 300 draws, and the margin
is still EIGHT, not six.** The brief's naive sum (292 + washing 1 + magpie 1 = 294) is wrong, and it
is wrong in our favour. **The pinned worst-draw frame is a NIGHT frame, and both of wave 1's
draw-spending layers are structurally zero at night**: the washing comes in (`mesh.count = 0`) and
the magpie roosts (`mesh.count = 0`). Wave 1's one draw is spent in daylight, where the walked peak
sits **ten draws lower**. Measured on two pinned trees, one box, one afternoon — not reasoned.
### §38.1 THE BUDGET RECONCILIATION — **292 / 300 stands. +0 draws at the pinned worst frame.**
**Method: R37 §37.1's, unchanged, so the two rounds are comparable.** Two **port-isolated no-store
servers** over `git archive`'d trees. `f38_ctrl` = **`6bc7926`** (pre-B, pre-E) — byte-identical under
`web/` to R37's pinned `7bd96e5` (`git diff --stat 7bd96e5 6bc7926 -- web/` is empty, so the control
IS the tree the pin was taken on). `f38_treat` = **`4514cb9`** (B on top of E) — byte-identical under
`web/js` + `web/index.html` to the round's true final `74d6257` (E's session-2 publish is assets only).
**`web/assets` is a SYMLINK to the live tree on BOTH sides**, so Lane E's asset wave and its mid-round
follow-up are the same bytes in both browsers and cannot be the difference. Default boot, seed
20261990, synthetic golden town, **1280×720**, `--method stepwalk --stepm 2 --fixed-yaw 0 --laps 2` =
**802 stations** down the main spine at **yaw 0, the worst bearing** (R37: 110 draws of spread from
the camera direction alone, peak at the *default argument* of `DBG.teleport`). **The citizen sim and
the tram are LIVE**, because the default boot is what the pin measures.
**The control reproduces the R37 pin to the byte, twice, on a fresh checkout: 292 draws / 120,145
tris, 31 live chunks, at x 4.1 z 319.2.** The method is repeatable; that is what makes the rest of
this table an argument rather than an anecdote.
| synthetic, default boot, 802 stations @ 2 m, yaw 0, **sim LIVE** | control `6bc7926` | R38 `4514cb9` | delta |
|---|---|---|---|
| **NIGHT (seg 5) — worst draws** | **292** | **292** | **+0** |
| NIGHT — worst tris | 120,145 | 122,879 | +2,734 |
| **NOON (seg 2) — worst draws** | **282** | **283** | **+1** |
| NOON — worst tris | 99,872 | 103,118 | +3,246 |
| night p50 draws | 177 | 176 | 1 |
| `?classic=1`, night | **269 / 90,580** | **269 / 90,580** | **0 / 0** |
| `fitzroy_real`, night (corpus tri worst) | **172 / 130,727** | **172 / 130,727** | **0 / 0** |
| `bendigo_real`, noon | **204 / 131,492** | **204 / 131,492** | **0 / 0** |
| `bendigo_real`, noon, **`?magpie=1` forced** | — | **204 / 131,492** | **0 / 0** |
**The reverts are EXACT, not approximate.** On the treatment tree: `?yards=0` at night →
**292 / 120,145**, all three of the control's numbers; all four flags off
(`yards=0&washing=0&magpie=0&ambient=0`) → **292 / 120,145**; and `?yards=0` **at noon**
**282 / 99,872**, which isolates the noon **+1 to the washing and to nothing else**. `?magpie=1`
forced at noon still reads **283** — the bird is +1 *where he is*, and where he is is never the
worst frame.
**Settled at the pinned worst point** (`dwell`, 700 frames at x 4.1 z 318.3 yaw 0, sim live, crowd
fully acquired): night control **270 / 60,476** vs R38 **270 / 61,276****+0 draws**; noon control
**265 / 55,734** vs R38 **266 / 57,046****+1**, the washing again, in daylight again.
**Lane B's isolated costs reproduce independently in my gates** (`smoke_yards`, `smoke_magpie`, at a
fixed pose with the citizen sim quiet on both sides via `?roster=v1&pop=0`): washing **+1 draw /
+512 tris**, magpie **+1 draw / +182 tris**, every yard box prop **+0 draws**. B's numbers are B's
numbers and they are also mine.
**One finding B did not claim and I will:** the yard build is **2 draws CHEAPER** than what it
replaced at a residential pose (`?washing=0` 191 draws vs `?yards=0` 193). The 36 `yard` lots that
fell through to `buildShopfront()` since v1 carried facade, signage and awning materials; they are now
`boxMats`-only fenced blocks. That is why B's 102-frame sum went **down** 13 draws while the worst
frame went up 1.
### §38.2 LANE B's "control 346": **the artifact is real AND the mechanism is real. Both, separately.**
B reported that with the sim live the same step read **control 346 vs treatment 232**, and correctly
refused to use it. As a *delta* it is the artifact R37 §37.1 named: `CitizenSim` acquires near-tier
rigs on a per-frame budget (`sim.js NEAR_MAX 24`) and peds spawn on **wall-clock** time, so how many
rigs are dressed at sample time is a function of how long that browser has been sitting there. A
114-draw swing between two browser instances measures the sim, not the build. B was right to hide it.
**But 346 is also above 300, and "it is an artifact" is not an answer to that.** R37 named *two*
under-reporting mechanisms and each of my existing methods defeats exactly one:
| method | R+1 dispose residue | crowd acquisition | reads at the pinned point |
|---|---|---|---|
| `stepwalk` (the pin) | **yes** — 31 live chunks | **no** — 1 frame/station | **292** |
| `dwell` | **no** — clean arrival, 27 chunks | **yes** — 700 frames | **270** |
| **`stepsettle` (new this round)** | **yes** | **yes** | *below* |
`tools/qa/budget_walk.py` gains **`--method stepsettle`**: the audit's fixed-yaw stations, each one
**drained (`chunks.pending → 0`) and then held `--holdframes` real rAF frames** so the shell's own
`frame()` runs the sim to steady state before the sample is taken. It is Lane B's method — drain,
then read through the composer — with R37's bearing discipline on top. It is the only one of the four
that can adjudicate a >300 claim, and it exists because B's 346 deserved an answer rather than a
dismissal.
**THE ANSWER: a >300 transient is NOT reproducible, on either tree, by any of the four methods.**
Like for like — the **same 101 stations at 8 m, yaw 0, one lap**, so the ONLY difference between the
two rows is the settle:
| 101 stations @ 8 m, yaw 0, sim live | `stepwalk` (1 frame/station) | `stepsettle` (drained + 25 frames) | what the settle is worth |
|---|---|---|---|
| **NIGHT** control | **287** / 120,145 | **278** / 100,939 | **9 draws** |
| **NIGHT** R38 | **287** / 122,879 | **278** / 104,178 | 9 |
| **NOON** control | **277** / 99,872 | **280** / 111,308 | **+3 draws** |
| **NOON** R38 | **278** / 103,118 | **281** / 115,169 | +3 |
**Settling is worth +3 draws at noon and 9 at night, and the sign flip is the finding.** At noon
the crowd fills in — R37's mechanism, in the direction R37 predicted, and smaller than R37's
standing-still +9. **At night it goes the other way**: the single-frame teleport photographs rigs
still dressed from the previous station and the settled street *retires them to impostors*
(tris fall 120,145 → 100,939, and a near rig is ~2.4k tris). The night crowd goes home; a sampler
that never waits does not see it happen.
**So the highest draw count anywhere in this reconciliation — four methods × two trees × two
segments × five towns — is 292, and it is the pin.** Nothing read over 300. The mechanism that
*could* legitimately have breached it (a fully-acquired crowd standing inside a full R+1 dispose
window) is now measured and it reads **280 / 281**: nineteen draws under the cap, eleven under the
pin. **Lane B's 346 was the artifact, and B was right to refuse it.** What I will add is that the
refusal is now backed by a measurement rather than by a citation — the method that could have
falsified it exists, was run, and did not.
*(One caution the same table teaches: `stepsettle`'s 278 is below `stepwalk`'s 292 mostly because it
walked 101 stations and the pin walks 802. **Station spacing, not settle policy, is the single
biggest lever on a walked worst-frame number** — see §38.3, where it moved a corpus pin by 6k tris.
Any future budget claim must state its station spacing as well as its settle policy and its bearing.)*
### §38.3 A CORRECTION TO MY OWN R37 PIN: the corpus TRI worst is **130,727**, not 124,793.
R37 pinned `fitzroy_real` at **124,793 tris** off a 12-bearing × 8 m *sweep*. Walked at **2 m stations
at yaw 0**, the same tree reads **130,727** — and it reads 130,727 on the **control**, so this is a
sampling-density correction to my own number, not a wave-1 cost. A finer station grid finds a worse
frame; that is what finer grids are for. **Re-pin: corpus tri worst 130,727 / 200,000** (35% headroom).
The draw pin is untouched. *A pin is only as good as its station spacing, and mine had not said so.*
### §38.4 THE FOUR GATES — each with its falsifiability control DEMONSTRATED, in the same run
`tools/flags_check.py` gains **`smoke_ambient` · `smoke_character` · `smoke_magpie` · `smoke_yards`**,
wired into `main()` before `smoke_tier2`. The house rule they all obey, in one line: **a gate that
cannot fail is not a gate.** This round earned that sentence twice over — R37 killed two vacuous
gates, and R38's own §1.3 season mechanism produced a constant (24 of 24 towns at 214 days) that
*would have gated green* because `?magpie=1` forces the state. Lane B caught its own bug. Every arm
below either flips a subject between two boots that differ in exactly one thing, or asserts a
negative that a broken build would satisfy — never "the object exists".
#### (a) `smoke_ambient` — §1.1, the audio inhabitation layer. **GREEN, 0 warns.**
| arm | result |
|---|---|
| six sources published, table == `character.js`'s `AMBIENCE_SOURCES` | ✓ |
| all six resolve in the **shipped** manifest; none reads `no-entry` unpatched | ✓ (so the control below means something) |
| all six anchored on the default boot | **63 dog · 8 mower · 81 radio · 32 sprinkler · 39 roller-door · 57 magpie-carol** — B's numbers exactly |
| **positive control** — one source reaches `audible` | dog-bark-fence **audible at 5.9 m, gain 0.25, 1 play scheduled**. For a one-shot, `audible` is only published once `plays > 0`, so this single word covers fetch → decode → layer gain → scheduling → counter |
| **HOURS is enforced** | sprinkler ([6,10)) **audible at 06:30 → idle at 12:30, same pose** |
| **DAY-OF-WEEK is enforced** | mower (weekend-only) **idle on day 1 (Mon) → audible on day 6 (Sat), same pose, same segment** |
| **PAN is a bearing, not a constant** | yaw 0/90/180/270° → **+0.00 +0.85 0.00 0.85** |
| **ZERO DRAWS is structural** | default **191 draws / 30,342 tris == `?ambient=0`, byte for byte** |
| `?classic=1` | layer OFF, no sources constructed |
| **THE FALSIFIABILITY CONTROL (kept verbatim)** | a second server on its own port whose root is `web/` **symlink-for-symlink** except one patched `manifest.json``audio.sfx['dog-bark-fence']` renamed. That source reports **`no-entry`, 0 plays, never `audible`**, anchors still 63; the other five are **unchanged**; **0 console errors** (a missing key is silence by house law, not an error) |
**The pan arm's first cut was itself nearly vacuous and I am recording it.** Two bearings (0°/180°)
read `+0.00 / 0.00`, because that anchor happened to sit on the forward axis — a working panner
would have been reported "inconclusive". Four bearings cannot be dodged by geometry. *The gate's own
first draft failed the standard the gate exists to enforce.*
**What this gate cannot do, said plainly:** it cannot confirm a human would HEAR anything. The
autoplay policy needs a trusted gesture and headless output goes nowhere. Everything downstream of
the unlock is measured; the acoustics are not. **Nobody in this loop has ears.** The gate prints that
sentence on every run rather than leaving it in a notes file nobody opens.
#### (b) `smoke_character` — §1.2, the per-town character vector. **GREEN, 0 warns.**
- **Classic BY CONSTRUCTION**: the default boot's character is the frozen v1 literals
(`grass`/`djsim-road`/`djsim-footpath`/`brickpave`), **all four tints null**, kerb `0x9a948c`, sky
null; all five ground meshes untinted `#ffffff` plus the flat kerb; **still exactly 5 town-wide
meshes**. No flag test is involved — a boot with no town key has no character, which is what
`?classic=1` and the synthetic default both get.
- **The per-town arm, against the SHIPPED index**: all **23** keys carry their own entry, including
the two that carry `state: ''``newtown_godverse` and `redhill_godverse`. A `state`-keyed
implementation would have dropped **exactly those two**, silently. Asserted against
`assets/towns/index.json`, not against a list in the gate.
- **THE CONTROL**: `townCharacter('__no_such_town_r38')` returns **`matched:false`**, label
`unmatched`, and so does `townCharacter(null)`. Without this arm, "matched is true for all 23" is a
claim a `matched: true` constant would satisfy.
- **Not vacuous as a table**: **21 distinct palettes across 23 towns** (the 2 repeats are the
deliberate godverse↔real sibling pairs). B's count, reproduced by a harness B did not write. A
single shared palette would FAIL this arm by name.
- **The two orphans are on the GPU, not just in a table**: booted live, `castlemaine_real` binds
**`ground-gravel.jpg`**, `darwin_real` binds **`ground-reddust.jpg`**, `hobart_real` binds
**`ground-grass-temperate.jpg`** — read off the live material's texture URL. Both orphans had been
selected by **nothing** since v1.
#### (c) `smoke_magpie` — §1.3, the magpie. **GREEN, 0 warns.**
- **THE LATCH, kept verbatim, with its positive control first**: after 1 swoop `gotYouToday` is
**TRUE** (without that, "false afterwards" is worthless); `game.sleep()` → day 1→2, `gotYouToday`
**false**, `swoopsToday` **0**, `dayRolls` 1, mode back to **`perched`**.
- **THE DELTA LAW**: **localStorage gained NO new key** across the swoop and the roll, and the save
blob mentions none of magpie/swoop/washing/yard (keys: cash, collection, day, savedAt, schema,
town, wants). *The baseline is taken **after** a `game.save()` on purpose:* the first run of this
arm reported `procity-save` as "new" and blamed the magpie for v7's own write. A delta law needs a
correctly-drawn baseline or it fabricates violations.
- **THE SEASON IS NOT A CONSTANT — the arm B's own bug demands, run UNFORCED** (`forced:false`
asserted first, or the arm is vacuous): **23 distinct positions in the year across 24 towns**,
min 0 / max 275 days out, against the first cut's **24 of 24 at 214**. **`newtown_real` is in
season on day 1** on the default seed, and **129/1000 `?seed=` values (12.9%)** put the synthetic
default in season against a design ratio of 42/364 = **11.5%** — the distribution, not one lucky
boot. Every one of B's season numbers reproduces.
- **TERRITORY SCALES WITH THE ROAD GRAPH**: synthetic **6** over 6.78 km → `bendigo_real` **157** over
188.6 km (1 per ~1.2 km both times). **A fixed-capacity magpie passes every other arm in this gate**
— which is why this one is here.
- **+1 DRAW, and the 0 is by absence**: same pose on both boots (taken once off the `?magpie=1` boot
and reused verbatim — `?magpie=0` has no `PROCITY.magpie` to ask, and two different poses is not an
A/B), sim quiet: **165 → 166 draws (+1), 22,378 → 22,560 tris (+182)** at instance count 1.
`?magpie=0` constructs nothing at all.
#### (d) `smoke_yards` — §1.4, backyard Australia. **GREEN, 0 warns, and it SKIPS LOUDLY.**
- **The subject is real**: **145 house + 36 yard = 181 lots**; **181 hoists → 394 items, cap 256, 256
active** (the cap BINDS here, so proximity-first spending is doing real work); at **night the count
is 0** — the washing comes in, and that zero is the vendored early-return, not a small number.
- **The cost decomposition, both halves falsifiable at one pose, sim quiet on all three boots**
(residential centroid `[9.04, 0.87]`): `?yards=0` **193 d / 30,582 t** · `?washing=0` **191 d /
32,518 t** · default **192 d / 33,030 t**. So: **the box props add 0 draws** (and in fact save 2 —
see §38.1) while moving **+1,936 tris**, and **the washing costs exactly +1 draw / +512 tris**. A
per-chunk yards module would have read +1 × up to 31 live chunks here. If `?yards=0` were
decoration the triangles would not move, and the gate FAILS on that explicitly.
- **THE LOUD SKIP**: on `newtown_real` (67 lots) and `bendigo_real` (35 lots) the gate prints a
`⊘ SCOPE` line — **0 house/yard lots, 0 hoists, 0 washing items, mesh never created, flag still
ON** — and says in the same breath that the synthetic arm found 181, *which is what makes the zero
a scope statement and not an empty run*. §1.4 is synthetic-only until Ruling 2's civic fetch lands
districts, and the suite now says so out loud every time instead of passing quietly.
### §38.5 THE SUITE — **`tools/qa.sh --strict --matrix` GREEN on the true final tree**
Run on the live working tree, which at the time of the run equalled **`74d6257`** (Lane E's session-2
publish landed mid-round; its `web/` diff against B's `4514cb9` is **manifest + one thumb, no code**,
and both were already present in the tree both budget servers read — so the reconciliation above
measured the final tree, not a stale one).
```
SUMMARY 7 passed · 0 failed · 0 warn · 0 skipped
● QA GREEN — every landed gate passed.
```
- **`flags_check` GREEN — 0 fails, 0 warns**, with all four new R38 gates inside it (they print their
own `GATE R38 §1.x` banners in the suite log, and `smoke_yards` prints its two `⊘ SCOPE` lines).
- **MATRIX GREEN — 10 towns × 7 gates** (boot · determinism · budget · district · roads · tram ·
noassets): 2 synthetic + 3 fixtures + 5 real caches, every town byte-equal on re-boot, **0 fetches
under `?noassets` across all ten**, tram verdicts unchanged (fenced on bendigo / fremantle /
katoomba_real, runs elsewhere). Wave 1 moved nothing in the matrix.
- scaffold_check · consistency · manifest validator (`--depot`) · interior figure-scale: all green.
29 interior figs across 6 shops still human-sized; the R36 DJ bodies still spawn as rigs.
- Total: **flags harness ~18 min**, whole suite ~19 min, on this box with two budget harnesses
running beside it.
### §38.6 HELD, AND HANDED ON
Nothing is held. Wave 1 gates green and the budget did not move. The following are **recorded, not
fixed**, because they are other people's files or other rounds' scope:
1. **`magpie.js`'s own header contradicts itself on the triangle count** — `:9` and `:123` say
**154 triangles**, `:42` says **182**, and the measured number (my gate, an independent harness) is
**182**. Two stale comments in Lane B's file; flagged, not edited. It is the kind of drift R38's own
§1.3 season bug was made of.
2. **E's three published GLBs are in the manifest but not under `web/assets/models/`** — they live in
`pipeline/_normalized/` and on the public depot, and `validate_manifest.py --depot` passes because
its contract is "local `_normalized` **OR** HEAD 200 on the depot". Nothing loads them (B declined
all three on measured budget). **Not a defect** — recorded so nobody reads the manifest as a
shipping list.
3. **The corpus TRI pin needs its station spacing quoted with it** (§38.3). Filed as a method note on
`budget_walk.py`, which now carries four methods with four different biases; the tool's docstring
says which is which.
4. Carried from R37, unchanged: **A's D1D4** · **the matrix's budget column is bookmark numbers**
(still a regression signal, not the budget of record) · **`?r=3` breaches at 307** and is still an
ungated diagnostic override · **should `?classic=1` show the town selector at all**.
**No tag.** Wave 1 is content; the epoch tags when it is whole.

View File

@ -3397,6 +3397,838 @@ def smoke_rotation(p):
b.close()
# ══════════════════════════════════════════════════════════════════════════════════════════════════
# [R38 — v8 WAVE 1, INHABITATION] THE FOUR GATES (Lane F)
#
# Four content systems landed in one round (Lane B `4514cb9`, Lane E `1568c9b`). Each gate below
# ships its FALSIFIABILITY CONTROL demonstrated in the same run, because this house has now twice
# shipped a mechanism that produced a constant and gated green on it:
# · R37 killed two vacuous gates (`smoke_tram`'s single assertion that was TRUE on the fenced path;
# the classic covenant's total absence of a network assertion);
# · R38's own §1.3 season mechanism was keyed on `citySeed` alone, made **24 of 24 towns a constant
# 214 days from swooping season**, and would have gated green because `?magpie=1` forces the
# state. Lane B caught it in its own sweep. Every arm here therefore either (a) flips a subject
# between two boots that differ in exactly one thing, or (b) asserts a NEGATIVE that a broken
# build would satisfy — never "the object exists".
#
# The house rule these four obey, in one line: **a gate that cannot fail is not a gate.**
# ══════════════════════════════════════════════════════════════════════════════════════════════════
R38_AMB_KEYS = ['dog-bark-fence', 'mower-distant', 'radio-through-window', 'sprinkler',
'roller-door', 'magpie-carol']
R38_V1_GROUND = {'road': 'djsim-road', 'foot': 'djsim-footpath', 'base': 'grass', 'plaza': 'brickpave'}
R38_V1_KERB = 0x9a948c
R38_ALT_PORT = PORT + 41 # the patched-manifest control server (its own port, its own root)
def _r38_patched_root(mutate):
"""A second web root that is `web/` symlink-for-symlink EXCEPT `assets/manifest.json`, which is a
patched copy. Everything else index.html, every module, every byte of every asset is the same
inode, so the only difference between the two servers is the one manifest key `mutate` changes.
That is what makes the control's verdict attributable."""
import tempfile
d = pathlib.Path(tempfile.mkdtemp(prefix='procity-r38-ctl-'))
src = ROOT / 'web'
for e in src.iterdir():
if e.name != 'assets':
(d / e.name).symlink_to(e)
a = d / 'assets'; a.mkdir()
for e in (src / 'assets').iterdir():
if e.name != 'manifest.json':
(a / e.name).symlink_to(e)
man = json.loads((src / 'assets' / 'manifest.json').read_text())
mutate(man)
(a / 'manifest.json').write_text(json.dumps(man, indent=1))
return d
def _r38_serve(root, port):
proc = subprocess.Popen(['python3', '-m', 'http.server', str(port)], cwd=str(root),
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
for _ in range(60):
if port_up(port): return proc
time.sleep(0.1)
proc.terminate()
return None
def _r38_boot_at(pg, host, query):
pg.goto(f'{host}/index.html?seed={SEED}&dbg=1' + (('&' + query) if query else ''))
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'; }")
try:
pg.wait_for_function("() => window.PROCITY && (!window.PROCITY.fleet || window.PROCITY.fleet.ready)", timeout=12000)
except Exception:
pass
# The five ground meshes, hashed over VERTEX + UV bytes (FNV-1a, the JS_VERTEX_FP shape) plus the
# material identity. §1.2's whole claim is "+0 draws / +0 tris and classic is byte-identical BY
# CONSTRUCTION" — a hash of the geometry and a read of the shared material is exactly that claim.
JS_R38_GROUND = """() => {
const P = window.PROCITY, g = P.scene.getObjectByName('ground');
const c = P.character || null;
const fnv = (arr) => { let h = 0x811c9dc5 >>> 0;
const u = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
for (let i = 0; i < u.length; i++) { h = (h ^ u[i]) >>> 0; h = Math.imul(h, 0x01000193) >>> 0; }
return h >>> 0; };
const meshes = [];
if (g) g.traverse((o) => {
if (!o.isMesh || !o.geometry) return;
const pos = o.geometry.getAttribute('position'), uv = o.geometry.getAttribute('uv');
let h = 0x811c9dc5 >>> 0;
if (pos) h = (h ^ fnv(pos.array)) >>> 0;
if (uv) h = (h ^ fnv(uv.array)) >>> 0;
const m = o.material;
const src = (m && m.map && m.map.image && (m.map.image.src || m.map.image.currentSrc)) || null;
meshes.push({ fp: h >>> 0, color: m && m.color ? '#' + m.color.getHexString() : null,
map: src ? String(src).split('/').pop() : null,
tris: pos ? (o.geometry.index ? o.geometry.index.count : pos.count) / 3 : 0 });
});
return {
townKey: P.townKey || null,
character: c ? { key: c.key, label: c.label, matched: !!c.matched,
ground: Object.assign({}, c.ground), tint: Object.assign({}, c.tint),
kerb: c.kerb, sky: c.sky } : null,
meshes, meshCount: meshes.length,
};
}"""
JS_R38_AMB = """() => {
const A = window.PROCITY.audio;
const a = A && A.state && A.state.ambient;
if (!a) return null;
const out = { panning: !!a.panning, off: !!a.off, ready: !!(A.state && A.state.ready), sources: {} };
for (const k of Object.keys(a.sources || {})) {
const s = a.sources[k];
out.sources[k] = { anchors: s.anchors, status: s.status, dist: s.dist, gain: s.gain,
pan: s.pan, plays: s.plays };
}
return out;
}"""
def _r38_amb(pg):
return pg.evaluate(JS_R38_AMB)
def _r38_home_lots(pg, n=34):
"""A deterministic spread of house/yard lot centres — the anchor pool §1.1 and §1.4 both derive
from. Returned in plan order, evenly sampled, so both the treatment and the control walk the
SAME list."""
return pg.evaluate("""(n) => {
const L = (window.PROCITY.plan.lots || []).filter((l) => l.use === 'house' || l.use === 'yard');
if (!L.length) return [];
const out = []; const step = Math.max(1, Math.floor(L.length / n));
for (let i = 0; i < L.length && out.length < n; i += step) out.push([L[i].x, L[i].z]);
return out;
}""", n)
def _r38_probe_poses(pg, poses):
"""Walk the pose list once, recording the closest approach to every §1.1 source. One pass (the
engine re-scans nearest-anchor every 0.33 s) that serves the whole gate, so the per-source
assertions below all stand at a position where the subject is genuinely in range."""
best = {k: (1e9, None) for k in R38_AMB_KEYS}
for (x, z) in poses:
pg.evaluate("([x, z]) => window.DBG.teleport(x, z, 0)", [x, z])
pg.wait_for_timeout(420)
st = _r38_amb(pg)
if not st: continue
for k, s in st['sources'].items():
d = s.get('dist')
if d is not None and d < best.get(k, (1e9, None))[0]:
best[k] = (d, (x, z))
return best
def _r38_wait_status(pg, key, want, timeout_s=22, poll=0.5):
"""Poll one source's status until it is in `want`. Returns the final reading."""
t0 = time.time()
last = None
while time.time() - t0 < timeout_s:
st = _r38_amb(pg)
last = st['sources'].get(key) if st else None
if last and last['status'] in want:
return last
pg.wait_for_timeout(int(poll * 1000))
return last
def smoke_ambient(p):
"""[R38 §1.1] THE AUDIO INHABITATION LAYER — six new mixer layers, zero draws.
Arms, each with its control:
1. six sources resolve against the SHIPPED manifest (not a table of hopes) and all six anchor
on the default boot;
2. THE FALSIFIABILITY CONTROL, kept verbatim from Lane B: a second server whose ONLY difference
is `audio.sfx['dog-bark-fence']` renamed that source must report `no-entry`, 0 plays, and
must NEVER reach `audible`, while the other five read exactly what they read unpatched;
3. HOURS is a real gate the sprinkler ([6,10)) flips audibleidle between seg 0 (06:30) and
seg 2 (12:30) at the SAME position;
4. DAY-OF-WEEK is a real gate the mower (weekend-only) flips idleaudible between day 1
(Monday) and day 6 (Saturday) at the same position and the same segment;
5. PANNING is a real bearing, not a constant the pan SIGN FLIPS when the camera turns 180°;
6. ZERO DRAWS is structural `?ambient=0` and the default boot read the same draw count at a
fixed pose with the citizen sim quiet.
WHAT THIS GATE CANNOT DO, said plainly: it cannot confirm that a human would HEAR anything.
The autoplay policy needs a trusted gesture and headless output goes nowhere; everything
downstream of the unlock (manifest resolve fetch decode layer gain pan one-shot
scheduling play counters) is measured, and the acoustics are not. Nobody in this loop has ears.
"""
head('GATE R38 §1.1: the audio inhabitation layer (6 point sources · hours · day · pan · the patched-manifest control)')
b, pg, errs = new_page(p)
ref = None; poses = None; best = None
try:
boot(pg, '')
# A real Input-domain KEY press: the autoplay unlock (audio.js listens for pointerdown OR
# keydown). Deliberately not a click — a click on the canvas also requests Pointer Lock, which
# the automation browser refuses, and the resulting console error is an artifact of the probe
# rather than of the build (R30/R37 recorded the same four).
pg.keyboard.press('Shift')
pg.wait_for_timeout(500)
pg.evaluate("() => window.DBG.setSegment(2)")
st = _r38_amb(pg)
if not st:
FAIL('§1.1: no PROCITY.audio.state.ambient on the default boot — the layer did not construct')
return
if st.get('off'):
FAIL('§1.1: the ambient layer reports off on the DEFAULT boot (it is default-on)')
return
got = sorted(st['sources'].keys())
if got == sorted(R38_AMB_KEYS):
OK(f"six point sources published: {' '.join(R38_AMB_KEYS)}")
else:
FAIL(f"§1.1: source table is {got}, want {sorted(R38_AMB_KEYS)}")
return
OK(f"StereoPannerNode available: panning={st['panning']} (mono fail-soft says so in the same field)")
# ── arm 1: every key resolves against the manifest that actually shipped ──
man = json.loads((ROOT / 'web' / 'assets' / 'manifest.json').read_text())
banks = man.get('audio', {})
missing = [k for k in R38_AMB_KEYS
if not any(k in banks.get(bk, {}) for bk in ('sfx', 'ambience', 'music'))]
if missing: FAIL(f"§1.1: manifest has no entry for {missing} — the table names sounds nobody shipped")
else: OK('all six keys resolve in the shipped manifest (Lane E `1568c9b`)')
noent = [k for k, s in st['sources'].items() if s['status'] == 'no-entry']
if noent: FAIL(f"§1.1: {noent} report no-entry on the UNPATCHED boot")
else: OK('no source reports no-entry unpatched — the control below therefore means something')
zero = [k for k, s in st['sources'].items() if not s['anchors']]
if zero: FAIL(f"§1.1: {zero} anchored NOWHERE on the default boot (the magpie-carol running-counter bug's shape)")
else: OK('all six anchored on the default boot: '
+ ' · '.join(f"{k} {st['sources'][k]['anchors']}" for k in R38_AMB_KEYS))
# ── position once, use everywhere ──
poses = _r38_home_lots(pg)
if len(poses) < 8:
FAIL(f"§1.1: only {len(poses)} house/yard lots on the default boot — the pose search is vacuous")
return
best = _r38_probe_poses(pg, poses)
OK('closest approach per source over %d residential poses: %s' % (
len(poses), ' · '.join(f"{k} {best[k][0]:.0f}m" for k in R38_AMB_KEYS)))
# ── arm 2 (positive control for the whole gate): ONE source reaches `audible` ──
# For a one-shot, `audible` is only published once `plays > 0` (audio.js), so this single
# assertion covers fetch, decode, gain, scheduling and the play counter in one.
dpos = best['dog-bark-fence'][1]
if not dpos:
FAIL('§1.1: no pose ever came within range of a dog anchor')
else:
pg.evaluate("([x, z]) => window.DBG.teleport(x, z, 0)", list(dpos))
r = _r38_wait_status(pg, 'dog-bark-fence', {'audible'}, timeout_s=26)
if r and r['status'] == 'audible' and r['plays'] > 0:
OK(f"positive control: dog-bark-fence AUDIBLE at {r['dist']} m — gain {r['gain']}, "
f"pan {r['pan']}, {r['plays']} play(s) scheduled")
ref = _r38_amb(pg)
else:
FAIL(f"§1.1: dog-bark-fence never reached audible in 26 s at {best['dog-bark-fence'][0]:.0f} m "
f"(status {r and r['status']}, plays {r and r['plays']}) — the whole layer is unproven")
# ── arm 5: the pan is a BEARING, not a constant ──
if dpos:
# FOUR bearings, not two. An anchor that happens to sit on the forward axis reads ~0 at
# yaw 0 AND at yaw 180 — a two-bearing arm would call a working panner inconclusive (it
# did, on the first run of this gate). Turning through all four quadrants means the
# anchor is hard right at one of them and hard left at another, whatever its bearing.
pans = []
for k in range(4):
pg.evaluate("([x, z, y]) => window.DBG.teleport(x, z, y)", [dpos[0], dpos[1], k * 1.5707963])
pg.wait_for_timeout(600)
pans.append(_r38_amb(pg)['sources']['dog-bark-fence']['pan'])
if max(pans) > 0.25 and min(pans) < -0.25:
OK('pan is a real bearing, not a constant: yaw 0/90/180/270° → '
+ ' '.join(f'{v:+.2f}' for v in pans)
+ ' (hard right at one bearing, hard left at another)')
else:
FAIL('pan does not track the camera: yaw 0/90/180/270° → '
+ ' '.join(f'{v:+.2f}' for v in pans)
+ ' — the StereoPanner is not driven by the bearing-to-anchor')
# ── arm 3: HOURS is a real gate (sprinkler [6,10)) ──
spos = best['sprinkler'][1]
if spos and best['sprinkler'][0] < 15:
pg.evaluate("([x, z]) => { window.DBG.setSegment(0); window.DBG.teleport(x, z, 0); }", list(spos))
on = _r38_wait_status(pg, 'sprinkler', {'audible', 'loading'}, timeout_s=10)
pg.evaluate("([x, z]) => { window.DBG.setSegment(2); window.DBG.teleport(x, z, 0); }", list(spos))
off = _r38_wait_status(pg, 'sprinkler', {'idle'}, timeout_s=10)
if on and on['status'] in ('audible', 'loading') and off and off['status'] == 'idle':
OK(f"HOURS arm: sprinkler {on['status']} at 06:30 → idle at 12:30, same pose ({on['dist']} m) "
'— the [6,10) window is enforced, not decorative')
else:
FAIL(f"§1.1 hours arm: sprinkler 06:30 {on and on['status']} / 12:30 {off and off['status']} "
'— the hours rule did not change the state')
else:
FAIL(f"§1.1 hours arm: no pose within the sprinkler's 15 m radius "
f"(closest {best['sprinkler'][0]:.0f} m) — arm SKIPPED, and a skipped arm is not a pass")
pg.evaluate("() => window.DBG.setSegment(2)")
# ── arm 4: DAY OF WEEK is a real gate (mower = weekend only; dow = (day-1)%7) ──
mpos = best['mower-distant'][1]
if mpos and best['mower-distant'][0] < 95:
pg.evaluate("([x, z]) => window.DBG.teleport(x, z, 0)", list(mpos))
wk = _r38_wait_status(pg, 'mower-distant', {'idle'}, timeout_s=8)
day = pg.evaluate("() => window.PROCITY.game && window.PROCITY.game.day")
for _ in range(8):
d = pg.evaluate("() => { window.PROCITY.game.sleep(); return window.PROCITY.game.day; }")
pg.wait_for_timeout(120)
if (d - 1) % 7 == 5: break # Saturday
d2 = pg.evaluate("() => window.PROCITY.game.day")
pg.evaluate("([x, z]) => { window.DBG.setSegment(2); window.DBG.teleport(x, z, 0); }", list(mpos))
we = _r38_wait_status(pg, 'mower-distant', {'audible', 'loading'}, timeout_s=14)
if wk and wk['status'] == 'idle' and we and we['status'] in ('audible', 'loading'):
OK(f"DAY arm: mower idle on day {day} (dow {(day-1)%7}, a weekday) → {we['status']} on day {d2} "
f"(dow {(d2-1)%7}, Saturday) at the same pose — the local dowOf() rule is enforced")
else:
FAIL(f"§1.1 day arm: mower day {day} {wk and wk['status']} → day {d2} {we and we['status']} "
'— weekend-only did not change the state')
else:
FAIL(f"§1.1 day arm: no pose within the mower's 95 m radius (closest {best['mower-distant'][0]:.0f} m) — arm SKIPPED")
real = [e for e in errs if 'PointerLock' not in e and 'Pointer Lock' not in e]
if real: FAIL(f"§1.1: {len(real)} console error(s) on the default boot; first: {real[0][:140]}")
else: OK('0 console errors on the default boot (Pointer Lock refusals excluded — the automation '
'browser, recorded R30/R37)')
finally:
b.close()
# ══ arm 6: ZERO DRAWS, and it is structural. Sim quiet on BOTH sides (?roster=v1&pop=0) so the
# comparison is of the audio layer, not of how many peds happened to spawn on wall-clock time —
# the trap R37 named and R38 reproduced.
draws = {}
for label, q in (('default', 'roster=v1&pop=0'), ('?ambient=0', 'ambient=0&roster=v1&pop=0')):
b, pg, _ = new_page(p)
try:
boot(pg, q)
r = pg.evaluate("() => { window.DBG.setSegment(2); return window.DBG.teleport(0, 0, 0); }")
draws[label] = (r['drawCalls'], r['tris'])
finally:
b.close()
if draws['default'][0] == draws['?ambient=0'][0] and draws['default'][1] == draws['?ambient=0'][1]:
OK(f"ZERO DRAWS confirmed: default {draws['default'][0]} draws / {draws['default'][1]:,} tris "
'== ?ambient=0, byte for byte (nothing in §1.1 reaches the scene graph)')
else:
FAIL(f"§1.1 claims zero draws but default {draws['default']} != ?ambient=0 {draws['?ambient=0']}")
# ══ arm 2b: THE FALSIFIABILITY CONTROL — kept verbatim ═══════════════════════════════════════
# One manifest key renamed. Nothing else on the disk differs (symlinked root). That source must
# report `no-entry`, must never reach `audible`, must have 0 plays; the other five must read
# exactly what they read unpatched. A missing key is silence by house law, so `status` is the
# ONLY thing that can tell a missing key from a quiet street — which is why the field exists.
if ref is None or not poses:
FAIL('§1.1: the unpatched reference reading was never taken — the control cannot be attributed')
return
def _rename_dog(m):
m['audio']['sfx']['dog-bark-fence-MISSING'] = m['audio']['sfx'].pop('dog-bark-fence')
root = _r38_patched_root(_rename_dog)
proc = _r38_serve(root, R38_ALT_PORT)
if not proc:
FAIL(f"§1.1 control: could not start the patched-manifest server on :{R38_ALT_PORT}")
return
b, pg, errs = new_page(p)
try:
_r38_boot_at(pg, f'http://127.0.0.1:{R38_ALT_PORT}', '')
pg.keyboard.press('Shift')
pg.wait_for_timeout(400)
pg.evaluate("() => window.DBG.setSegment(2)")
dpos = best['dog-bark-fence'][1]
pg.evaluate("([x, z]) => window.DBG.teleport(x, z, 0)", list(dpos))
r = _r38_wait_status(pg, 'dog-bark-fence', {'no-entry'}, timeout_s=12)
st2 = _r38_amb(pg)
d = st2['sources']['dog-bark-fence']
if d['status'] == 'no-entry' and d['plays'] == 0:
OK(f"CONTROL FIRES: one manifest key renamed → dog-bark-fence reports no-entry, {d['plays']} plays, "
f"anchors still {d['anchors']} (it is the SOUND that is missing, not the street)")
else:
FAIL(f"§1.1 CONTROL DID NOT FIRE: with the key renamed dog-bark-fence reads status "
f"{d['status']}, plays {d['plays']} — this gate cannot distinguish a shipped sound from a missing one")
# the other five must be untouched
drift = [k for k in R38_AMB_KEYS if k != 'dog-bark-fence'
and st2['sources'][k]['anchors'] != ref['sources'][k]['anchors']]
if drift: FAIL(f"§1.1 control: renaming one key moved the anchor counts of {drift} — the sources are coupled")
else: OK('the other five sources are unchanged by the patch (anchor counts identical) — the control is attributable')
if any(s['status'] == 'audible' for k, s in st2['sources'].items() if k == 'dog-bark-fence'):
FAIL('§1.1 control: the renamed source reached audible')
real = [e for e in errs if 'PointerLock' not in e and 'Pointer Lock' not in e]
if real: FAIL(f"§1.1 control: {len(real)} console error(s) — a missing key must be SILENT, not an "
f"error; first: {real[0][:140]}")
else: OK('a missing key is silence, not an error: 0 console errors on the patched boot (house audio law 1)')
finally:
b.close()
proc.terminate()
import shutil; shutil.rmtree(root, ignore_errors=True)
# ── scope + classic, stated rather than assumed ──
b, pg, _ = new_page(p)
try:
boot(pg, 'classic=1')
st = _r38_amb(pg)
if st is None or st.get('off'):
OK('?classic=1: the ambient layer is OFF (no sources constructed) — the covenant boot never hears it')
else:
FAIL(f"§1.1: ?classic=1 constructed the ambient layer ({st})")
finally:
b.close()
print(' \033[36m[record]\033[0m audible OUTPUT is NOT verified here and cannot be headlessly: the autoplay '
'policy needs a trusted gesture and nothing plays to a speaker. Everything downstream of the unlock '
'is measured above. Nobody in this loop has ears.')
def smoke_character(p):
"""[R38 §1.2] THE PER-TOWN CHARACTER VECTOR — +0 draws, +0 tris, classic byte-identical.
Arms:
1. CLASSIC BY CONSTRUCTION the default boot's character is the frozen v1 literals, every
ground material is untinted (#ffffff), the kerb is 0x9a948c, and the ground is still exactly
5 meshes. No `if (classic)` is needed because a boot with no town key HAS no character.
2. THE VACUOUS ARM IS PER-TOWN, not per-corpus every one of the 23 shipped town keys has its
own entry, INCLUDING the two carrying `state: ''` (`newtown_godverse`, `redhill_godverse`),
which a `state`-keyed implementation would have dropped silently. Measured against the
shipped index, not asserted.
3. THE CONTROL: an unknown key must return `matched:false`. Without this arm, "matched is true
for all 23" is a claim that a `matched: true` constant would satisfy.
4. NOT VACUOUS AS A TABLE the 23 towns must not all carry the SAME palette (a character
vector that gives every town identical ground is the §1.3 season bug wearing a hat).
5. THE TWO ORPHANS ARE ON THE GPU `gravel` (Castlemaine) and `reddust` (Darwin, both Red
Hills) were selected by NOTHING since v1. Asserted at the bound material's texture URL on a
live boot, not at the table.
"""
head('GATE R38 §1.2: the per-town character vector (classic by construction · per-town arm · the two orphans on the GPU)')
b, pg, errs = new_page(p)
try:
boot(pg, '')
g = pg.evaluate(JS_R38_GROUND)
c = g['character']
if not c:
FAIL('§1.2: PROCITY.character is absent on the default boot'); return
if c['matched'] is False and c['key'] is None and c['ground'] == R38_V1_GROUND \
and all(v is None for v in c['tint'].values()) and c['kerb'] == R38_V1_KERB and c['sky'] is None:
OK('classic BY CONSTRUCTION: default boot → frozen v1 literals '
f"({c['ground']['base']}/{c['ground']['road']}/{c['ground']['foot']}/{c['ground']['plaza']}), "
'all four tints null, kerb 0x9a948c, sky null')
else:
FAIL(f"§1.2: the default boot's character is NOT the frozen literals: {c}")
tinted = [m for m in g['meshes'] if m['color'] and m['color'].lower() not in ('#ffffff',)]
# the kerb mesh is a flat colour by design (0x9a948c) — it is the only non-white one
tinted = [m for m in tinted if m['color'].lower() != '#9a948c']
if not tinted:
OK(f"the default boot's {g['meshCount']} ground meshes are all untinted (#ffffff) + the 0x9a948c kerb "
'— the shared material is the same object v1 built')
else:
FAIL(f"§1.2: default boot ground carries tints {[m['color'] for m in tinted]} — classic is not byte-identical")
if g['meshCount'] == 5:
OK('ground is still exactly 5 town-wide meshes (the +0-draw mechanism, re-measured)')
else:
FAIL(f"§1.2: ground has {g['meshCount']} meshes, want 5 — the palette swap added draws")
print(f" \033[36m[record]\033[0m default-boot ground fp: "
+ ' / '.join(f"{m['fp']:08x}" for m in g['meshes']))
# ── arms 2/3/4: the table itself, over the SHIPPED index ──
idx = json.loads((ROOT / 'web' / 'assets' / 'towns' / 'index.json').read_text())
towns = idx['towns'] if isinstance(idx, dict) else idx
keys = [t['key'] for t in towns]
blank = [t['key'] for t in towns if not (t.get('state') or '').strip()]
r = pg.evaluate("""async (keys) => {
const m = await import('./js/world/character.js');
const out = {};
for (const k of keys) {
const c = m.townCharacter(k);
out[k] = { matched: !!c.matched, label: c.label, sig: JSON.stringify([c.ground, c.tint, c.sky]) };
}
const bad = m.townCharacter('__no_such_town_r38');
out.__control = { matched: !!bad.matched, label: bad.label, sig: JSON.stringify([bad.ground, bad.tint, bad.sky]) };
out.__null = { matched: !!m.townCharacter(null).matched };
return out;
}""", keys)
unmatched = [k for k in keys if not r[k]['matched']]
if unmatched: FAIL(f"§1.2: {len(unmatched)} shipped town key(s) have NO entry: {unmatched}")
else: OK(f"all {len(keys)} shipped town keys carry their own entry (per-town, not per-corpus)")
if all(r[k]['matched'] for k in blank):
OK(f"the per-corpus trap is closed: {blank} carry state:'' in the index and are matched BY KEY "
'— a state-keyed arm would have dropped exactly these two, silently')
else:
FAIL(f"§1.2: the two state:'' towns {blank} are not matched by key")
if r['__control']['matched'] is False and r['__null']['matched'] is False:
OK("CONTROL FIRES: an unknown key returns matched:false (label '%s') and townCharacter(null) too "
'"matched" is a real bit, not a constant' % r['__control']['label'])
else:
FAIL('§1.2 CONTROL DID NOT FIRE: an unknown key reports matched:%s — the 23 greens above prove nothing'
% r['__control']['matched'])
sigs = {r[k]['sig'] for k in keys}
if len(sigs) == 1:
FAIL(f"§1.2 VACUOUS: all {len(keys)} towns share ONE palette — the character vector produces a constant")
elif len(sigs) >= 20:
OK(f"{len(sigs)} distinct palettes across {len(keys)} towns "
f"({len(keys) - len(sigs)} deliberate godverse↔real sibling repeats)")
else:
WARN(f"§1.2: only {len(sigs)} distinct palettes across {len(keys)} towns")
# the orphans, at the table
orph = {'castlemaine_real': 'gravel', 'darwin_real': 'reddust',
'redhill_real': 'reddust', 'redhill_godverse': 'reddust'}
bad = {k: json.loads(r[k]['sig'])[0]['base'] for k, want in orph.items()
if json.loads(r[k]['sig'])[0]['base'] != want}
if bad: FAIL(f"§1.2: the formerly-orphaned skins are not selected where claimed: {bad}")
else: OK('gravel → Castlemaine · reddust → Darwin + both Red Hills (selected by NOTHING since v1)')
if errs: FAIL(f"§1.2: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
# ── arm 5: the orphans reach the GPU. The table is a claim; the bound texture is the fact. ──
for town, want, skin in (('castlemaine_real', 'ground-gravel.jpg', 'gravel'),
('darwin_real', 'ground-reddust.jpg', 'reddust'),
('hobart_real', 'ground-grass-temperate.jpg', 'grass-temperate')):
b, pg, errs = new_page(p)
try:
boot(pg, f'plansrc=osm&town={town}')
g = pg.evaluate(JS_R38_GROUND)
maps = [m['map'] for m in g['meshes'] if m['map']]
c = g['character']
if not c or not c['matched']:
FAIL(f"§1.2: {town} booted with character matched={c and c['matched']}")
elif want in maps:
OK(f"{town}: character '{c['label']}'{skin} is BOUND ({want} on a live ground mesh), "
f"{g['meshCount']} meshes, sky {c['sky']}")
else:
FAIL(f"§1.2: {town} wants {skin} but the bound ground maps are {maps}")
if errs: WARN(f"§1.2 {town}: {len(errs)} console error(s); first: {errs[0][:120]}")
finally:
b.close()
def smoke_magpie(p):
"""[R38 §1.3] THE MAGPIE — +1 draw on screen, exactly 0 otherwise.
Arms:
1. THE LATCH, kept verbatim from the packet: the day-roll clears `gotYouToday` AND
**localStorage gained no new key**. Both halves matter a positive control first (the latch
must be TRUE before the roll, or "it is false afterwards" is worthless), then the clear, then
the delta-law assertion on storage and on the save blob itself.
2. THE SEASON IS NOT A CONSTANT Lane B's first cut keyed the year offset on `citySeed` alone
and made **24 of 24 towns 214 days from swooping season**; it would have gated green because
`?magpie=1` forces the state. So this arm runs UNFORCED and asserts the season varies across
the shipped corpus, and that at least one town is in season on day 1.
3. TERRITORY SCALES WITH THE ROAD GRAPH synthetic (6.8 km) and bendigo_real (188 km) must not
land on the same count. A fixed-capacity magpie passes every other arm here.
4. +1 DRAW, and the 0 is real same pose, `?magpie=1` vs `?magpie=0`, citizen sim quiet.
"""
head('GATE R38 §1.3: the magpie (the latch + no new storage key · the season is not a constant · territory scales · +1 draw)')
b, pg, errs = new_page(p)
try:
boot(pg, 'magpie=1')
pg.evaluate("() => window.DBG.setSegment(2)")
# Baseline the storage AFTER a save exists. `game.sleep()` writes `procity-save` — that is v7
# behaviour this item must not be blamed for; the arm is "did the MAGPIE add a key", so the
# pre-existing key has to be in the baseline or the gate reports someone else's write.
pg.evaluate("() => { try { window.PROCITY.game.save(); } catch {} }")
st = pg.evaluate("() => { const m = window.PROCITY.magpie; return m ? JSON.parse(JSON.stringify({"
"territories: m.state.territories, roadKm: m.state.roadKm, forced: m.state.forced,"
"inSeason: m.state.inSeason, active: m.state.active, mode: m.state.mode,"
"seasonOffset: m.state.seasonOffset })) : null; }")
if not st:
FAIL('§1.3: PROCITY.magpie is null on ?magpie=1'); return
syn_terr = st['territories']
OK(f"synthetic default: {syn_terr} territories over {st['roadKm']} km of road "
f"(1 per {1000 * st['roadKm'] / max(1, syn_terr):.0f} m), forced={st['forced']}")
# ── arm 1: the latch, and the storage delta law ──
keys0 = pg.evaluate("() => Object.keys(localStorage).sort()")
act = pg.evaluate("() => window.PROCITY.magpie.state.active")
if not act:
FAIL('§1.3: no active territory picked — the latch arm has no subject'); return
pg.evaluate("(a) => window.DBG.teleport(a.x + 6, a.z + 6, 0)", act)
got = None
for _ in range(40):
pg.wait_for_timeout(400)
s = pg.evaluate("() => ({ got: window.PROCITY.magpie.state.gotYouToday,"
" swoops: window.PROCITY.magpie.state.swoopsToday,"
" launches: window.PROCITY.magpie.state.launches,"
" mode: window.PROCITY.magpie.state.mode })")
if s['got']:
got = s; break
if not got:
FAIL(f"§1.3: he never swooped in 16 s at his own pole (last {s}) — the latch arm is vacuous without it")
else:
OK(f"positive control: after {got['swoops']} swoop(s) — gotYouToday TRUE, launches {got['launches']}")
day0 = pg.evaluate("() => window.PROCITY.game.day")
pg.evaluate("() => window.PROCITY.game.sleep()")
pg.wait_for_timeout(700)
after = pg.evaluate("() => ({ got: window.PROCITY.magpie.state.gotYouToday,"
" swoops: window.PROCITY.magpie.state.swoopsToday,"
" rolls: window.PROCITY.magpie.state.dayRolls,"
" day: window.PROCITY.game.day, mode: window.PROCITY.magpie.state.mode })")
if after['got'] is False and after['swoops'] == 0 and after['day'] == day0 + 1:
OK(f"THE DAY ROLL CLEARS THE LATCH: day {day0}{after['day']}, gotYouToday false, swoopsToday 0, "
f"dayRolls {after['rolls']}, mode '{after['mode']}' (he is back on his pole — a swoop cannot "
'be in progress across a night)')
else:
FAIL(f"§1.3: the day roll did not clear the latch: {after}")
keys1 = pg.evaluate("() => Object.keys(localStorage).sort()")
new = [k for k in keys1 if k not in keys0]
if not new:
OK(f"DELTA LAW: localStorage gained NO new key across the swoop and the roll (keys: {keys1})")
else:
FAIL(f"§1.3: localStorage gained {new} — a v8 content layer must not grow the save file")
blob = pg.evaluate("() => { try { return window.PROCITY.game.export(); } catch { return ''; } }")
leaked = [w for w in ('magpie', 'swoop', 'washing', 'yard') if w in blob.lower()]
if leaked: FAIL(f"§1.3: the save blob mentions {leaked}")
else: OK('the save blob mentions none of magpie/swoop/washing/yard — '
+ 'keys: ' + ','.join(sorted(json.loads(blob).keys()) if blob else []))
if errs: FAIL(f"§1.3: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
# ── arm 2: THE SEASON IS NOT A CONSTANT (Lane B's own bug, gated) — UNFORCED ──
b, pg, _ = new_page(p)
try:
boot(pg, '')
forced = pg.evaluate("() => window.PROCITY.magpie ? window.PROCITY.magpie.state.forced : null")
if forced is not False:
FAIL(f"§1.3: the default boot reports forced={forced} — the season arm below would be vacuous")
idx = json.loads((ROOT / 'web' / 'assets' / 'towns' / 'index.json').read_text())
towns = idx['towns'] if isinstance(idx, dict) else idx
keys = [t['key'] for t in towns]
r = pg.evaluate("""async (keys) => {
const m = await import('./js/world/magpie.js');
const seed = (window.PROCITY.plan.citySeed >>> 0);
const out = { synthetic: m.daysToSeason(1, seed, null) };
for (const k of keys) out[k] = m.daysToSeason(1, seed, k);
return out;
}""", keys)
vals = sorted(r.values())
distinct = len(set(vals))
inseason = [k for k, v in r.items() if v == 0]
if distinct <= 1:
FAIL(f"§1.3 VACUOUS SEASON: all {len(r)} towns are {vals[0]} days from swooping season — "
'this is exactly the constant Lane B shipped first (24/24 at 214 days)')
else:
OK(f"the season is keyed per town: {distinct} distinct positions in the year across {len(r)} towns "
f"(min {vals[0]}, max {vals[-1]} days out) — NOT the 24-of-24 constant the first cut produced")
if inseason:
OK(f"unforced, on the default seed, {len(inseason)} town(s) are IN swooping season on day 1: {inseason}")
else:
WARN(f"no town is in season on day 1 of the default seed (nearest {vals[0]} days) — "
'the mechanism varies but nobody arrives to a swoop; design ratio is 42/364 = 11.5%')
# the design ratio itself, over 1000 seeds — the distribution, not one lucky boot
hit = pg.evaluate("""async () => {
const m = await import('./js/world/magpie.js');
let n = 0; for (let s = 1; s <= 1000; s++) if (m.inSwoopSeason(1, s >>> 0, null)) n++;
return n;
}""")
if 60 <= hit <= 200:
OK(f"{hit}/1000 ?seed= values put the synthetic default in season on day 1 "
f"({hit/10:.1f}% against a design ratio of 42/364 = 11.5%)")
else:
FAIL(f"§1.3: {hit}/1000 seeds in season on day 1 — the distribution is not the designed 11.5%")
finally:
b.close()
# ── arm 3: territory scales with the road graph ──
b, pg, _ = new_page(p)
try:
boot(pg, 'plansrc=osm&town=bendigo_real&magpie=1')
st = pg.evaluate("() => { const m = window.PROCITY.magpie; return m ? { t: m.state.territories,"
" km: m.state.roadKm } : null; }")
if not st:
FAIL('§1.3: no magpie on bendigo_real')
elif st['t'] > syn_terr * 3:
OK(f"territory SCALES with the road graph: synthetic {syn_terr} over {'%.1f' % 6.78} km → "
f"bendigo_real {st['t']} over {st['km']} km (1 per {1000*st['km']/st['t']:.0f} m). A fixed-capacity "
'magpie would read the same number in both towns and pass every other arm here.')
else:
FAIL(f"§1.3: bendigo_real ({st['km']} km) has {st['t']} territories against synthetic's {syn_terr} "
'— the count is not scaling with the graph')
finally:
b.close()
# ── arm 4: +1 draw on screen, exactly 0 off. Sim quiet on both sides. ──
res = {}; mpose = None
for label, q in (('magpie=1', 'magpie=1&roster=v1&pop=0'), ('magpie=0', 'magpie=0&roster=v1&pop=0')):
b, pg, _ = new_page(p)
try:
boot(pg, q)
pg.evaluate("() => window.DBG.setSegment(2)")
if mpose is None:
# the pose is taken ONCE, off the ?magpie=1 boot, and reused verbatim on the ?magpie=0
# boot — where PROCITY.magpie is null and there is nothing to ask. Two different poses
# is not an A/B, it is two numbers.
a = pg.evaluate("() => { const t = window.PROCITY.magpie.state.active; return [t.x + 10, t.z + 10]; }")
mpose = [round(a[0], 2), round(a[1], 2)]
pg.evaluate("(m) => window.DBG.teleport(m[0], m[1], 0)", mpose)
pg.wait_for_timeout(900)
i = pg.evaluate("(m) => window.DBG.teleport(m[0], m[1], 0)", mpose)
cnt = pg.evaluate("() => window.PROCITY.magpie ? window.PROCITY.magpie.count : null")
res[label] = (i['drawCalls'], i['tris'], cnt)
finally:
b.close()
d1, t1, c1 = res['magpie=1']; d0, t0, c0 = res['magpie=0']
if c0 is not None:
FAIL(f"§1.3: ?magpie=0 still constructed the layer (count {c0})")
else:
OK('?magpie=0 constructs nothing (PROCITY.magpie is null) — the 0 is by absence, not by a count')
print(f" \033[36m[record]\033[0m the cost pose is {mpose} (his territory + 10 m), sim quiet on both boots")
if c1 == 1 and d1 - d0 == 1:
OK(f"+1 DRAW while he is on screen, measured: {d0}{d1} draws (+1), {t0:,}{t1:,} tris "
f"(+{t1-t0}) with the instance count at {c1}")
elif c1 == 1:
WARN(f"§1.3: bird on screen (count 1) and draws moved {d0}{d1} (want +1); tris +{t1-t0}")
else:
FAIL(f"§1.3: could not put the bird on screen for the cost arm (count {c1}) — the +1 claim is unmeasured")
def smoke_yards(p):
"""[R38 §1.4] BACKYARD AUSTRALIA — the box props ride buildings.js's existing boxMats at +0 draws;
only the washing pays a draw.
Arms:
1. THE SUBJECT IS REAL 181 house/yard lots on the default boot, 181 hoists, N washing items,
the cap spent at 256 by day and **0 at night** (the washing comes in).
2. THE COST DECOMPOSITION, with both halves falsifiable at one pose, sim quiet:
`?yards=0` vs `?washing=0` **draws EQUAL, tris DIFFERENT** (the box props are free)
`?washing=0` vs default **draws +1** (the cloth's own mesh)
If the props were paying a draw the first comparison fails; if `?yards=0` were decoration the
tris would not move.
3. IT SKIPS LOUDLY ON REAL TOWNS §1.4 is synthetic-only until the civic fetch lands. On a real
town the gate asserts the zero is REAL (0 house/yard lots 0 hoists the mesh is never
created) and says so as a SCOPE line. A pass there would be vacuous, and the synthetic arm
above is what makes the zero attributable rather than an empty run.
"""
head('GATE R38 §1.4: backyard Australia (the subject · the cost decomposition · the LOUD SKIP on real towns)')
b, pg, errs = new_page(p)
syn_lots = None
try:
boot(pg, '')
r = pg.evaluate("""() => {
const P = window.PROCITY, L = P.plan.lots || [];
const c = {}; for (const l of L) c[l.use] = (c[l.use] || 0) + 1;
const w = P.washing;
return { uses: c, house: c.house || 0, yard: c.yard || 0,
washing: w ? { hoists: w.state.hoists, items: w.state.items, active: w.state.active,
cap: w.state.cap, count: w.count, night: w.state.night } : null,
flags: { yards: P.flags.yards, washing: P.flags.washing } };
}""")
syn_lots = r['house'] + r['yard']
if syn_lots < 100:
FAIL(f"§1.4: only {syn_lots} house/yard lots on the default boot — the whole item has no subject")
return
OK(f"subject: {r['house']} house + {r['yard']} yard = {syn_lots} lots (the 36 `yard` lots had "
'rendered as blank shopfront sheds since v1)')
w = r['washing']
if not w:
FAIL('§1.4: PROCITY.washing is null on the default boot')
elif w['hoists'] == syn_lots and w['items'] > w['cap']:
OK(f"washing: {w['hoists']} hoists → {w['items']} items, cap {w['cap']}, {w['active']} active "
'(the cap BINDS here — proximity-first spending is doing real work)')
else:
WARN(f"§1.4: washing {w} against {syn_lots} lots")
# night: the washing comes in, and an empty count is a REAL zero
pg.evaluate("() => window.DBG.setSegment(5)")
pg.wait_for_timeout(300)
pg.evaluate("() => window.DBG.teleport(60, 60, 0)")
pg.wait_for_timeout(500)
n = pg.evaluate("() => ({ c: window.PROCITY.washing.count, night: window.PROCITY.washing.state.night })")
if n['c'] == 0 and n['night']:
OK('at NIGHT the washing comes in: count 0 (the vendored early-return ⇒ the layer costs exactly 0 draws)')
else:
FAIL(f"§1.4: at night the washing layer reads {n} — the night zero is not real")
if errs: FAIL(f"§1.4: {len(errs)} console error(s); first: {errs[0][:140]}")
finally:
b.close()
# ── arm 2: the cost decomposition at ONE pose, three boots, citizen sim quiet on all three ──
pose = None
b, pg, _ = new_page(p)
try:
boot(pg, 'roster=v1&pop=0')
pose = pg.evaluate("""() => {
const L = (window.PROCITY.plan.lots || []).filter((l) => l.use === 'house' || l.use === 'yard');
let x = 0, z = 0; for (const l of L) { x += l.x; z += l.z; }
return [ +(x / L.length).toFixed(2), +(z / L.length).toFixed(2) ];
}""")
finally:
b.close()
cost = {}
for label, q in (('default', 'roster=v1&pop=0'), ('washing=0', 'washing=0&roster=v1&pop=0'),
('yards=0', 'yards=0&roster=v1&pop=0')):
b, pg, _ = new_page(p)
try:
boot(pg, q)
pg.evaluate("() => window.DBG.setSegment(2)")
pg.evaluate("(p) => window.DBG.teleport(p[0], p[1], 0)", pose)
pg.wait_for_timeout(700)
i = pg.evaluate("(p) => window.DBG.teleport(p[0], p[1], 0)", pose)
cost[label] = (i['drawCalls'], i['tris'])
finally:
b.close()
dY, tY = cost['yards=0']; dW, tW = cost['washing=0']; dD, tD = cost['default']
print(f" \033[36m[record]\033[0m residential pose {pose}, sim quiet — "
f"?yards=0 {dY}d/{tY:,}t · ?washing=0 {dW}d/{tW:,}t · default {dD}d/{tD:,}t")
if tW == tY:
FAIL(f"§1.4 VACUOUS: ?yards=0 and ?washing=0 render identical triangles ({tW:,}) — ?yards=0 reverts NOTHING")
elif dW <= dY:
note = ('' if dW == dY else
f" — and it is {dY - dW} CHEAPER, because 36 `yard` lots that fell through to buildShopfront()"
' since v1 (facade + signage + awning materials) are now boxMats-only fenced blocks')
OK(f"+0 DRAWS for every box prop: ?washing=0 {dW} draws vs ?yards=0 {dY} draws while triangles move "
f"{tY:,}{tW:,} ({tW-tY:+,}){note}. The fences, hoists, aerials and tanks ride an InstancedMesh "
'that was already being submitted; a per-chunk yards module would have read +1 × up to 31 here.')
else:
FAIL(f"§1.4: the box props ADD {dW - dY} draw(s) (?washing=0 {dW} vs ?yards=0 {dY}) — not +0")
if dD - dW == 1:
OK(f"the washing costs exactly +1 draw: {dW}{dD} ({tW:,}{tD:,} tris, {tD-tW:+,}) — ONE town-wide "
'InstancedMesh at any N, not +1 × up to 31 live chunks')
else:
WARN(f"§1.4: the washing moved draws {dW}{dD} ({dD-dW:+d}) at this pose (want +1); "
f"tris {tW:,}{tD:,}")
# ── arm 3: THE LOUD SKIP. Real towns are 100% use:'shop' until the civic fetch lands. ──
for town in ('newtown_real', 'bendigo_real'):
b, pg, _ = new_page(p)
try:
boot(pg, f'plansrc=osm&town={town}')
r = pg.evaluate("""() => {
const P = window.PROCITY, L = P.plan.lots || [];
let h = 0; for (const l of L) if (l.use === 'house' || l.use === 'yard') h++;
const w = P.washing;
return { lots: L.length, homes: h, washing: w ? { hoists: w.state.hoists, items: w.state.items,
count: w.count } : null, flag: P.flags.yards };
}""")
if r['homes'] == 0 and r['washing'] and r['washing']['hoists'] == 0 and r['washing']['count'] == 0:
print(f" \033[35m⊘ SCOPE\033[0m §1.4 DOES NOT APPLY on {town}: {r['lots']} lots, "
f"**0 house/yard**, 0 hoists, 0 washing items, mesh never created. The flag is ON "
f"({r['flag']}) and the item still costs exactly nothing — measured, not assumed. "
f"The synthetic arm above found {syn_lots} lots, which is what makes this zero a SCOPE "
'and not an empty gate. Extends to real towns when A\'s civic fetch lands districts.')
elif r['homes'] == 0:
FAIL(f"§1.4 on {town}: 0 house/yard lots but the washing layer reads {r['washing']}"
'the "free on real towns" claim is wrong')
else:
OK(f"§1.4 APPLIES on {town} after all: {r['homes']} house/yard lots, washing {r['washing']} "
'— the civic fetch has landed; retire the synthetic-only scope note')
finally:
b.close()
def main():
# [R37] `--only name[,name…]` runs a subset of the smokes by function name (e.g. `--only smoke_tram`).
# Development ergonomics only: the suite's contract is still "run them all"; --only prints a banner
@ -3456,6 +4288,11 @@ def main():
smoke_wantlist_travel(p) # R33 — wantlist · keeper rumor · travel-costs-a-day (+ controls)
smoke_godbay(p) # R34 — GODBAY offline auction (one hammer · bounded · morning paper)
smoke_first_five(p) # R35 — the first five minutes (seeded want · splash · beats + controls)
# ── [R38 v8 WAVE 1] the four inhabitation gates, each with its control demonstrated ──
smoke_ambient(p) # R38 §1.1 — six point sources · hours · day-of-week · pan · THE PATCHED MANIFEST
smoke_character(p) # R38 §1.2 — classic by construction · the per-town arm · the two orphans on the GPU
smoke_magpie(p) # R38 §1.3 — the latch + no new storage key · the season is NOT a constant · +1 draw
smoke_yards(p) # R38 §1.4 — the cost decomposition · the LOUD SKIP on real towns
smoke_tier2(p) # R27 — the kill-the-server gate (charter law #1)
smoke_real_crate(p) # R24 — THE REAL CRATE (per-shop atlas identity · soft fall · no-op off the path)
finally:

View File

@ -20,6 +20,11 @@ is a measurement rather than an argument. The three methods:
frames, and a real walker gets ~830 frames per chunk). Without --settle you measure
a walker who outruns his own streamer, which UNDER-reports.
stepwalk the audit's method: `DBG.teleport()` in 8 m steps (teleport does warmup+render).
stepsettle [R38] the audit's stations at a FIXED yaw, but DRAINED and then SETTLED for
`--holdframes` real rAF frames before sampling so the frame carries the R+1 dispose
residue AND a fully-acquired citizen near-tier at the same time. `stepwalk` under-reports
the crowd (one frame per station); `dwell` under-reports the streamer (clean arrival).
This is the only method that defeats both, and it is what a >300 claim must be measured with.
bookmark the pre-audit method: `DBG.shot(name)` at a named bookmark, clean load.
Every method reports draws AND tris, the worst frame, and where it stood.
@ -293,6 +298,61 @@ STEPWALK_JS = r"""
"""
# ── [R38] the STEPSETTLE method: the audit's fixed-yaw stations, but SETTLED at each one ─────────
# R37 named two under-reporting mechanisms and this round's budget question is what happens when you
# defeat BOTH at once. `stepwalk` walks a station in (so it carries the R+1 dispose residue — up to
# 31 live chunks) but samples ONE frame, so `CitizenSim`'s per-frame near-tier budget (sim.js
# NEAR_MAX 24) has photographed a half-dressed street. `dwell` settles completely but arrives clean
# (27 live chunks). Neither is the worst case. This method is: teleport to the station at a FIXED
# yaw, then hold `hold` real rAF frames so the shell's own frame() runs the sim to steady state, and
# sample the LAST of them. Walked-in chunk residue AND a fully-acquired crowd, in the same frame.
# It is the method Lane B's R38 A/B used (drain, then render through the composer), and it is the
# only one of the four that can answer "is the control's 346 a real transient or an artifact".
STEPSETTLE_JS = r"""
(cfg) => new Promise((resolve) => {
const P = window.PROCITY, D = window.DBG;
const { poly, cum, total } = cfg.path;
const posAt = (s) => {
s = Math.max(0, Math.min(total, s));
let i = 0; while (i < cum.length - 2 && cum[i + 1] < s) i++;
const seg = cum[i + 1] - cum[i] || 1, f = (s - cum[i]) / seg;
const x = poly[i][0] + (poly[i + 1][0] - poly[i][0]) * f;
const z = poly[i][1] + (poly[i + 1][1] - poly[i][1]) * f;
let dx = poly[i + 1][0] - poly[i][0], dz = poly[i + 1][1] - poly[i][1];
const L = Math.hypot(dx, dz) || 1;
return { x, z, dx: dx / L, dz: dz / L };
};
const stations = [];
for (const dir of [1, -1]) {
for (let k = 0; k <= Math.floor(total / cfg.stepm); k++)
stations.push([dir > 0 ? k * cfg.stepm : total - k * cfg.stepm, dir]);
if (cfg.laps < 2) break;
}
const out = [];
let si = 0, held = 0, drained = 0;
const q0 = posAt(stations[0][0]);
D.teleport(q0.x, q0.z, cfg.fixedYaw === null ? Math.atan2(-q0.dx, -q0.dz) : cfg.fixedYaw);
function step() {
const [s, dir] = stations[si];
const pend = P.chunks ? P.chunks.pending : 0;
// drain first (a fully-streamed frame is what a real 4.6 m/s walker is permanently in), then
// settle: the crowd is what the hold is actually for.
if (pend > 0 && drained < cfg.maxDrain) { drained++; return requestAnimationFrame(step); }
if (held < cfg.hold) { held++; return requestAnimationFrame(step); }
const r = P.renderer.info.render, pos = P.player.position;
out.push([r.calls, r.triangles, P.chunks ? P.chunks.count : 0, pend,
+pos.x.toFixed(1), +pos.z.toFixed(1), +s.toFixed(1), dir]);
si++; held = 0; drained = 0;
if (si >= stations.length) return resolve(out);
const [s2, d2] = stations[si], q = posAt(s2);
P.player.teleport(q.x, q.z, cfg.fixedYaw === null ? Math.atan2(-q.dx * d2, -q.dz * d2) : cfg.fixedYaw);
requestAnimationFrame(step);
}
requestAnimationFrame(step);
})
"""
def boot(pg, host, query, seg):
pg.goto(f'{host}/index.html?seed={SEED}&dbg=1' + (('&' + query) if query else ''))
pg.wait_for_function('window.DBG && window.DBG.ready === true', timeout=45000)
@ -361,6 +421,11 @@ def run_one(p, host, town, bootflag, method, seg, stepm, laps, maxm, settle, max
raw = pg.evaluate(STEPWALK_JS, {'path': path, 'stepm': stepm, 'laps': laps,
'fixedYaw': fixed_yaw})
extra = {}
elif method == 'stepsettle':
raw = pg.evaluate(STEPSETTLE_JS, {'path': path, 'stepm': stepm, 'laps': laps,
'fixedYaw': fixed_yaw,
'hold': max(1, holdframes), 'maxDrain': 240})
extra = {'hold_frames_per_station': max(1, holdframes)}
else:
if method == 'dwell':
x, z, yaw = dwell_at
@ -403,7 +468,8 @@ def main():
ap.add_argument('--port', type=int, default=int(os.environ.get('PROCITY_QA_PORT', '8951')))
ap.add_argument('--town', default='synthetic')
ap.add_argument('--boot', default='default', choices=['default', 'classic'])
ap.add_argument('--method', default='walk', choices=['walk', 'stepwalk', 'dwell', 'sweep'])
ap.add_argument('--method', default='walk',
choices=['walk', 'stepwalk', 'stepsettle', 'dwell', 'sweep'])
ap.add_argument('--yaws', type=int, default=12, help='bearings sampled per station (--method sweep)')
ap.add_argument('--extra', default='', help='extra query string appended to every boot (e.g. r=3&shadows=1)')
ap.add_argument('--fixed-yaw', type=float, default=None,