I had parked this as "needs a per-draw capture recording texture base addresses". It did not. 8AX (1280x720) and ptbase (640x360 at 200%) are the SAME artwork at two resolutions, which is exactly why comparing either against a capture is inconclusive -- and why comparing their DIFFERENCE is not. Compute 8AX - upscale(ptbase), the detail only 8AX has, and ask whether the capture contains it. Both candidates are first mapped into the capture's tone domain with the measured gamma; without that the residual is dominated by the tone difference and the test is blind. main menu corr +0.0475 controls +0.0032 shift, -0.0075 flip 68% of ceiling title corr +0.0634 controls +0.0095 shift, +0.0086 flip 68% of ceiling Two independent screens, both at 68% of the theoretical ceiling (sd of the 8AX-only detail over sd of the capture residual), 7-15x their matched controls. The controls preserve spatial correlation and destroy only alignment, so they are what "no signal" looks like. So the recommendation changes: resolve the name and draw 8AX at 1:1. Upscaling ptbase 2x is wrong, not merely softer. Still do not draw both -- an opaque layer over an identical one costs fill and hides later changes, and ptbase's element is the one carrying the keyframes, so a consumer needs its timing with 8AX's pixels. Also recorded and withdrawn: a cruder pixel-pair test gave 0.00-0.72 for upscales, 0.98 native and 1.01 for the capture -- apparently decisive. Additive noise raises both terms of that ratio equally and drives any value toward 1; fitting a noise term, both "native + noise" and "bilinear + noise" reproduce the observed numbers. The conclusion is right, that test does not establish it, and it is in REFUTED because the number looks conclusive and is not. Not shown: whether ptbase is also drawn underneath. 8AX is ~86% opaque and carries the same art, so it would hide it either way.
50 lines
2.3 KiB
Python
Executable File
50 lines
2.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Does the running game draw the full-res `8AX` or the 640x360 `ptbase` upscaled?
|
|
|
|
Both carry the SAME artwork at two resolutions, so no pixel comparison of the
|
|
backgrounds can separate them. What CAN: the detail 8AX has and an upscale does
|
|
not. Compute `8AX - upscale(ptbase)` -- the 8AX-only detail -- and ask whether
|
|
the live capture contains it.
|
|
|
|
Both candidates are first mapped into the capture's tone domain with the
|
|
measured gamma (docs/re/structures/ui-render-tone-curve.md); without that, the
|
|
capture's residual is dominated by the tone difference and the test is blind.
|
|
|
|
CONTROLS are the point: the same correlation with the 8AX residual shifted 7 px
|
|
and flipped. Those preserve the spatial correlation structure while destroying
|
|
the alignment, so they are what "no signal" looks like here.
|
|
"""
|
|
import sys
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
CASES = [
|
|
("main menu", 1.491, "a715f485_8AX_1280x720.png", "a715f485_ptbase.t32_640x360.png",
|
|
"docs/re/captures/title-builds/live-main-menu.png"),
|
|
("title", 1.338, "a60fcb85_8AX_1280x720.png", "a60fcb85_ptbase2.t32_640x360.png",
|
|
"docs/re/captures/title-builds/live-title-press-a.png"),
|
|
]
|
|
TEX = sys.argv[1] if len(sys.argv) > 1 else "/tmp/tex"
|
|
Y0, Y1, X0, X1 = 40, 300, 20, 600 # background, away from menu text
|
|
|
|
def main():
|
|
for label, gamma, ax_png, pb_png, cap_png in CASES:
|
|
ax = np.asarray(Image.open(f"{TEX}/{ax_png}").convert("L")).astype(float)
|
|
pb = np.asarray(Image.open(f"{TEX}/{pb_png}").convert("L")
|
|
.resize((1280, 720), Image.BILINEAR)).astype(float)
|
|
cap = np.asarray(Image.open(cap_png).convert("L")).astype(float)
|
|
h, w = cap.shape
|
|
ax, pb = ax[:h, :w], pb[:h, :w]
|
|
g = lambda a: 255 * np.power(np.clip(a, 0, 255) / 255.0, gamma)
|
|
axg, pbg = g(ax), g(pb)
|
|
E = (axg - pbg)[Y0:Y1, X0:X1] # detail only 8AX has
|
|
C = (cap - pbg)[Y0:Y1, X0:X1] # how the capture departs from the upscale
|
|
cc = lambda a, b: float(np.corrcoef(a.ravel(), b.ravel())[0, 1])
|
|
ceiling = E.std() / C.std()
|
|
r = cc(C, E)
|
|
print(f"{label}: corr {r:+.4f} controls {cc(C[:, :-7], E[:, 7:]):+.4f} (shift) "
|
|
f"{cc(C, E[::-1]):+.4f} (flip) ceiling {ceiling:.3f} -> {100*r/ceiling:.0f}% of it")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|