acdream/docs/research/evidence/vm0/compare-offline.py
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

31 lines
1.6 KiB
Python

import sys, numpy as np
from PIL import Image
root = r"artifacts/vm0"
def load(n): return np.asarray(Image.open(f"{root}/{n}/screenshots/world-offline.png").convert("RGB")).astype(np.int16)
def cmp(a, b, name, maskTop=0):
A, B = load(a), load(b)
assert A.shape == B.shape, (A.shape, B.shape)
d = np.abs(A - B).max(axis=2)
H, W = d.shape
region = d[maskTop:, :]
diff = region > 0
n = int(diff.sum()); tot = region.size
rows = np.where(diff.any(axis=1))[0]
print(f"{name}: {n} / {tot} px differ ({100.0*n/tot:.5f}%)"
+ (f" rows {rows.min()+maskTop}-{rows.max()+maskTop}, max|d|={int(region.max())}, mean|d| over differing={region[diff].mean():.2f}" if n else " IDENTICAL"))
if n:
hist = np.bincount(region[diff].astype(np.int64), minlength=8)
print(f" |d| histogram 1..7+: {hist[1:8].tolist()} ...>=8: {int((region[diff]>=8).sum())}")
vis = np.zeros((H, W, 3), np.uint8)
vis[..., 0] = np.clip(d * 16, 0, 255).astype(np.uint8) # red = difference amplified x16
base = (A.astype(np.float32) * 0.25).astype(np.uint8)
vis = np.maximum(vis, base)
Image.fromarray(vis).save(f"{root}/diff-{name}.png")
return n
print("== whole frame, exact ==")
cmp("base", "base-normals", "A2-normals-only")
cmp("base-normals", "head-off", "HEAD-off_vs_base+normals")
cmp("head-off", "head-on", "detail-on_vs_off")
cmp("base", "head-off", "HEAD-off_vs_base")
print("== top-280 sky band masked (the tool's documented self-noise band) ==")
cmp("base-normals", "head-off", "HEAD-off_vs_base+normals_masked", 280)