acdream/docs/research/2026-08-22-vm0-default-path-invariance.md
Erik 30e72a2af8 research(vm0): Campaign AR's pack-off path IS the pre-campaign renderer - PASS
Exact-pixel and production-perf comparison of 6c79d35c (+ only the A2
normal files, so no terrain mask is needed) against c51b07ef with no pack:
connected as +Acdream, visible window, one isolated config clone per
variant, pinned clocks. Open field: the only differences are idle pose,
mana digits and a passing flyer. Holtburg: same-binary-twice defines the
dynamic mask (9.8%); in the static 90% both self-diffs have ZERO pixels
with |d|>=8 while base+normals vs HEAD-off has 841/729 - all streaks inside
the animated lifestone. Buildings, ground, trees, sky and UI are clean.

Perf (uncapped Release, no automation observer, ACDREAM_FRAME_PROF=1):
Holtburg CPU p50 4.7 -> 4.1 ms, Arwic 6.0 -> 5.2 ms, GPU unchanged,
alloc/frame 574 KB -> 21 KB. No regression; F5b's '27.8 ms retail CPU' was
the observer.

Three false alarms recorded so nobody repeats them: the isolated gate
settings lack fieldOfView (90 vs the real 86.33 -> a 0.952 zoom); the real
%APPDATA% settings still selected acdream.atmospheric/low (pack ON); a
minimized GLFW window is throttled and never settles.

Tools: tools/vm0/capture-visible.ps1 (pre-campaign gate + -Exe/-Live/
-ConfigDir/-CharacterName/-PreCaptureCommand), tools/vm0/perf-run.sh,
and -BuildingDetailTextures on run-offline-pixel-gate.ps1. Baseline
patches under docs/research/evidence/vm0/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 21:40:40 +02:00

119 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# VM0 — is Campaign AR's pack-off path the pre-campaign renderer?
**Date:** 2026-08-22 · **Campaign:** VM slice VM0 · **Status:** CLOSED — PASS
**Question:** with no render pack selected, does `c51b07ef` (Campaign AR) draw
the same pixels and cost the same as `6c79d35c` (pre-campaign main), outside
the two intended parity changes (A2 terrain normals, #226 building detail)?
**Answer:** yes, to the measured noise floor — and the pack-off path is
faster and allocates less.
## Binaries
| Name | Source | Notes |
|---|---|---|
| `base` | `6c79d35c` | + `evidence/vm0/baseline-configdir.patch` (HEAD's 13-line `ACDREAM_CONFIG_DIR/DATA_DIR/CACHE_DIR` hunk in `ApplicationPathSet`, path resolution only) so it can run from an isolated config |
| `base+normals` | `6c79d35c` + `evidence/vm0/baseline-normals.patch` (only `LandblockMesh.cs` + `TerrainVertex.cs` from the campaign) + the config-dir hunk | isolates A2 so the key comparison needs no terrain mask |
| `HEAD` | `c51b07ef` | `BuildingDetailTextures` off and on |
All Release. Worktrees `.claude/worktrees/vm0-base` and `vm0-base-normals`
were throwaway; the two patches reproduce them.
## Method that finally worked (and the three that did not)
Every run: connected to the local ACE as `+Acdream` (name-selected via
`--session-config`; ACE reorders the roster by last login, so an index is not
stable), **visible normal window** (the product condition), one **isolated
config clone** per variant under `artifacts/vm0/cfg-*` with
`renderPack = retail/off`, pinned `ACDREAM_DAY_GROUP=0`,
`ACDREAM_WORLD_TIME=0.5`, `ACDREAM_SKY_PHASE_SECONDS=0`, `ACDREAM_MSAA_SAMPLES=0`,
1280×720, 12 s settle, one screenshot. Helper:
`tools/vm0/capture-visible.ps1` (the `6c79d35c` pixel-gate script with `-Exe`,
`-Live`, `-ConfigDir`, `-CharacterName`, `-PreCaptureCommand`).
Three earlier attempts produced 8891 % pixel differences that were **all
configuration, none renderer** — recorded so nobody repeats them:
1. **Hidden-window captures vs old-tool captures differed by a 0.952 zoom.**
HEAD's gate writes an isolated `settings.json` with no `fieldOfView`, so
HEAD rendered at the default 90° gameFOV while the old binary read the real
file's `86.33°`. Not a renderer change. Consequence: captures from the new
`run-offline-pixel-gate.ps1` and from the pre-campaign tool are **not
comparable** to each other; each tool is self-consistent.
2. **Connected HEAD frames were brighter with tree shadows.** The real Roaming
`settings.json` still held `renderPack = acdream.atmospheric/low` from the
owner's live gate — the pack was ON. (The reviewer had been reading and
editing a stale `%LOCALAPPDATA%\acdream\settings.json`; the client's
Windows config root is `%APPDATA%\acdream`.) Every HEAD run must pin the
selection explicitly.
3. **A minimized HEAD window rendered brighter than hidden/visible.** Windows
throttles an iconified GLFW surface; the 12 s settle had not completed.
Minimized is not a product condition and is not used.
## Pixel results
Exact per-pixel comparison (`max |ΔRGB|`), no tolerance.
**Open field (logout spot, 60.7S 89.1W, few dynamics):**
| Pair | px differing | notes |
|---|---|---|
| base vs base+normals (A2 only) | 12.7 %, mean Δ 1.5 | terrain shading, low amplitude |
| **base+normals vs HEAD-off (KEY)** | **4.3 %, mean Δ 9** | all of it: idle-pose outline, regenerating mana digits, one ambient flyer crossing the camera — ground, trees and UI clean (`diff-I: HEAD-off vs base+normals (KEY).png`) |
| HEAD-off vs HEAD-on (detail) | 3.3 % | no buildings in view; same noise class |
**Holtburg (`/telepoi Holtburg`, buildings, NPCs, animated lifestone):**
the same binary twice differs in 2535 % of pixels by ±17 (sun/time
sub-steps), so a raw count is meaningless. Pixels with |Δ| ≥ 8 between two
logins of the *same* binary define the dynamic mask (lifestone, NPCs, particle
emitter, radar blips, digits; 9.8 % of the frame after a 6 px dilation). In
the remaining 90.2 %:
| Pair | |Δ| ≥ 8 px in static region | max |
|---|---|---|
| base+normals self (two logins) | **0** | 7 |
| HEAD-off self (two logins) | **0** | 7 |
| **base+normals vs HEAD-off (KEY, run 1)** | **841 (0.10 %)** | 128 |
| **base+normals vs HEAD-off (KEY, run 2)** | **729 (0.09 %)** | 128 |
| base vs base+normals (A2 only) | 7,884 (0.95 %) | 212 |
| HEAD-off vs HEAD-on (detail) | 513 (0.06 %) | 255 |
The 841/729 KEY pixels are streaks inside the animated lifestone crystal that
escaped the mask dilation (`evidence/vm0/holtburg-KEY-static-strong-diff.png`);
buildings, roofs, ground, trees, sky and every UI element are clean. The A2
row is the expected shading change on terrain. The detail row is below the
threshold on building shells because the fallback blend's mean factor is
1.033 (+3 %); VM1's single-pass re-port (10 %) will make it measurable.
Evidence: `docs/research/evidence/vm0/*.png`, full captures under
`artifacts/vm0/` (not committed).
## Production performance (no observer)
`tools/vm0/perf-run.sh`: connected, **uncapped Release**, no
`ACDREAM_AUTOMATION_ARTIFACT_DIR` (so no render-scene observer),
`ACDREAM_FRAME_PROF=1`, same isolated config, 75 s at each spot; last six
5-second `[frame-prof]` windows (`evidence/vm0/perf-frame-prof.txt`):
| Spot | Binary | CPU p50 / p95 / p99 | GPU p50 / p95 | alloc KB/frame | Gen0 per 5 s |
|---|---|---|---|---|---|
| Holtburg | base | 4.7 / 5.1 / 5.5 ms | 0.4 / 0.5 | 574 | 1213 |
| Holtburg | **HEAD off** | **4.1 / 4.4 / 4.8 ms** | 0.4 / 0.5 | **21** | 01 |
| Arwic (dense) | base | 6.0 / 6.5 / 7.0 ms | 0.7 / 0.8 | 582 | 10 |
| Arwic (dense) | **HEAD off** | **5.2 / 5.6 / 6.1 ms** | 0.7 / 0.9 | **29** | 01 |
No regression. The pack-off path is ~13 % cheaper on CPU with ~25× less
managed allocation per frame; GPU unchanged. This also settles review F5b:
the "27.8 ms retail-path CPU" in the Stage-2 connected report was the
automation observer, not the product.
## Verdict
VM0 PASSES. Campaign AR's default path is the pre-campaign renderer plus the
two declared parity changes. The F1 blocker is cleared.
## Carried forward
- The new pixel gate's isolated settings should carry `fieldOfView` (and
`gamma`) so its captures mean the same as the product's — filed for VM7's
tool tidy; not a renderer issue.
- `run-offline-pixel-gate.ps1` gained `-BuildingDetailTextures` (VM0).