test(render): Campaign V slice V11 step 0 — the #256/#257 discriminator, both arms

Issues #256 (server-spawned signs and portals go invisible after repeated
portal runs, while staying interactive) and #257 (working set grows to
~1.5 GB over the same session) were observed together in one long live
Vulkan session, and both filings demanded the same thing before V11 deletes
the OpenGL backend: run the churn on GL too. Vulkan-only growth or drift
would mean the new arm's resource lifecycle is broken, and deleting its only
reference implementation while that was true would be wrong even with the
cutover signed.

So the discriminator is built and run first, and it can stop the slice.

tools/run-portal-churn-soak.ps1 generates a route of N cycles over three
portal-bearing stops taken from the two existing connected routes, runs it
once per backend from one binary, and measures three things the existing
instruments do not measure together:

  * working set and private bytes, sampled from the OS every two seconds and
    joined to each checkpoint by timestamp -- the client's own snapshot has
    no view of its own working set, which is exactly #257's quantity;
  * the published-versus-live pair already in the checkpoint JSON, because
    "alive in the object table, gone from the presentation" is #256's whole
    symptom and a drift between those halves at the SAME stop across cycles
    is what would show it;
  * a within-arm capture comparison -- cycle 1 against cycles 10, 20 and 30
    at a pinned viewpoint -- plus a difference map and a row histogram,
    because a number cannot tell an absent object from a walking NPC and the
    map can.

Every teleloc carries the identity quaternion so the heading repeats, and
the four determinism levers the differential gate forces are forced here for
the same reason: an unpinned sun would swamp the signal.

Result at 90 transits per arm, 91 checkpoints, zero errors, graceful exits:
neither arm reproduces either symptom. Working set means agree to 1 MiB
(GL 1864, VK 1863) and warm-half drift is NEGATIVE on both (-48.0, -27.0).
GPU accounting is exactly constant per arm. worldEntities holds 10,382 at
all thirty cycles on both. The difference maps show every building, the
portal, the statue and the treeline still drawn at cycle 30.

That refutes the one outcome that would have blocked V11, and it does not
identify the pre-existing bug -- so both issues stay OPEN with the negative
recorded, and the follow-up named: walked portal transits rather than
/teleloc, which do not take the same path into the transit state machine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-28 23:24:45 +02:00
parent 122fe8a7e2
commit db4426d5ef
2 changed files with 701 additions and 0 deletions

View file

@ -211,6 +211,50 @@ question costs far more to answer.
sign/portal visible; a soak instrument counts published-vs-live entities per
transit and holds at zero drift.
### Discriminator result (2026-07-28, V11 step 0) — NEGATIVE ON BOTH ARMS
The discriminator this issue demanded before V11 was built and run:
`tools/run-portal-churn-soak.ps1`, 30 cycles x 3 portal-bearing stops
(Holtburg town / Facility Hub interior / Aerlinthe island, all taken from the
existing connected routes, every teleloc carrying the identity quaternion so the
heading repeats), **90 transits per arm**, once on `gl` and once on `vulkan`
from the same binary at `122fe8a7`. Both arms: 91 checkpoints, **zero** error
lines, graceful exit, desktop witness `RENDERED`.
The pixel half is a *within-arm* comparison — the Holtburg capture at cycle 1
against cycles 10, 20 and 30 from the same process at a pinned viewpoint with
MSAA off and all four determinism levers forced. An object that stopped being
drawn would appear as a contiguous blob in the difference map.
| Arm | c01 vs c10 | c01 vs c20 | c01 vs c30 |
|---|---|---|---|
| gl | 12,366 px | 14,492 px | 17,082 px |
| vulkan | 12,774 px | 15,234 px | 16,183 px |
**The two arms are the same to within noise, and the difference maps show
nothing missing.** Every building, the portal cone, the statue, the distant
treeline, the scenery and the NPC render identically at cycle 30 and cycle 1 on
both backends. What differs is: the player avatar's animation phase, the
portal's own scrolling texture, one corpse, the stamina readout — plus, on GL
only, a scatter of single-pixel alpha fringes across the ground that Vulkan does
not produce. Publication counters corroborate: `worldEntities` is **10,382 at
every one of the 30 cycles on both arms**, `ownedCompositeTextures` is 471,
`trackedGpuTextures` is 437 (GL) / 372 (VK) — all three constant from warmup to
the last transit, with zero drift.
**Verdict: the "Vulkan-arm-only lifecycle bug" hypothesis is refuted for this
workload, so V11 is not blocked by this issue.** But neither arm *reproduced*
the symptom, so this does not identify the pre-existing bug either — it rules
out the one outcome that would have made deleting GL wrong.
**This issue stays OPEN.** The next attempt must differ from this route the way
the user's session did: real portal *use* (walk into a portal, F751 wormhole,
materialization) rather than `/teleloc`, and Coldeve's town portal network
rather than these three stops. `/teleloc` and a walked portal do not take the
same path into the transit state machine, and that difference is now the leading
suspect. Artifacts: `artifacts/v11-churn/` (per-checkpoint CSVs, memory sample
CSVs, difference maps, `churn-soak.json`).
---
## #257 — Working set balloons to ~1.5 GB over a live portal-churn session
@ -240,6 +284,52 @@ are the model to hold this to.
**Acceptance:** a repeated-portal soak (≥30 transits) holds working set flat
after warmup, with per-category GPU accounting drift at zero.
### Discriminator result (2026-07-28, V11 step 0) — NEGATIVE ON BOTH ARMS
Same run as #256's: `tools/run-portal-churn-soak.ps1`, 30 cycles x 3 stops =
**90 transits per arm**, `gl` then `vulkan` from one binary at `122fe8a7`, with
the OS sampling working set and private bytes every 2 s (547 / 548 samples per
arm) and each checkpoint joined to its nearest sample by timestamp.
Holtburg, every fifth cycle:
| Cycle | GL WS | GL priv | GL GPU | VK WS | VK priv | VK GPU |
|---|---|---|---|---|---|---|
| 1 | 1878.1 | 1949.6 | 321.5 | 1893.3 | 1848.7 | 58.6 |
| 5 | 1728.6 | 1783.3 | 332.3 | 1905.7 | 1843.7 | 58.6 |
| 10 | 1902.7 | 1963.6 | 332.3 | 1928.5 | 1867.6 | 58.6 |
| 15 | 1911.7 | 1972.9 | 332.3 | 1933.8 | 1870.6 | 58.6 |
| 20 | 1976.1 | 2042.5 | 332.3 | 1757.8 | 1693.7 | 58.6 |
| 25 | 1874.3 | 1940.7 | 332.3 | 1875.4 | 1812.0 | 58.6 |
| 30 | 1920.4 | 1994.5 | 332.3 | 1774.5 | 1709.1 | 58.6 |
(MiB. The GL/VK GPU-byte levels are not comparable to each other — the two
backends' `GpuMemoryTracker` coverage differs — but each is comparable to
itself, and **each is exactly constant**: GL pins at 332.3 MiB from cycle 2
onward and never moves again; Vulkan sits at 58.6 MiB for all 30 cycles.)
Whole-run statistics over every OS sample:
| Arm | min | mean | max | peak private | warm 1st-half mean | warm 2nd-half mean | drift |
|---|---|---|---|---|---|---|---|
| gl | 1713 | **1864** | 2210 | 2282 | 1927 | 1880 | **-48.0** |
| vulkan | 1734 | **1863** | 2173 | 2112 | 1894 | 1867 | **-27.0** |
**Neither arm grows.** The warm-half drift is *negative* on both, the two means
agree to 1 MiB, and handle counts are flat (GL +1, VK -4 over 30 cycles).
**Verdict: not reproduced, on either backend, so V11 is not blocked.** The
~1.9 GB band is a *level*, not a leak: this route holds three very different
landblocks' content live, which is a different residency profile from V8's
stationary 877 MiB (VK) / 944 MiB (GL) vehicles. The 1.5 GB the user saw is
inside this band, so the reported number may simply be what a multi-region
session costs — but that is a hypothesis this run cannot confirm, because it
did not reproduce unbounded growth to explain.
**This issue stays OPEN**, coupled to #256 and blocked on the same follow-up:
re-run with walked portal transits rather than `/teleloc`. Artifacts:
`artifacts/v11-churn/`.
---
## #253 — Attribute/skill icons: not centered in their cells, and fully opaque