sweep_leaf_ramp dumps the nested ptloop01/ptloop02 leaf keyframes -- position, alpha, rotation, scale and time -- which is where the ramp the port asked for actually lives. sweep_positions now also pools alpha against position per strip and prints the slope, with the quantisation stated: NDC prints to two decimals, so one frame's dx is 6.4 px and alpha is one level, and at three or four frames the two declared slopes (+0.0814 and -0.0651) are inside that noise. It is a direction and magnitude check, not a discrimination. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
79 lines
3.5 KiB
Python
Executable File
79 lines
3.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Where the two rotated sweep strips are, per frame, and how their alpha ramps.
|
|
|
|
sweep_positions.py <xenia_re_ui_draws_NN.log> [...]
|
|
|
|
The blend map reports a quad's SIZE. That identifies an element and says nothing
|
|
about whether it is on screen: a parked quad is still a draw call. This prints the
|
|
NDC x-range and the per-vertex colour of every 8-vertex ADDITIVE draw, per frame,
|
|
so "submitted" and "visible" stop being the same observation.
|
|
|
|
NDC x is in [-1, +1] across the surface, so a strip overlaps the screen iff
|
|
x_min < 1 and x_max > -1. Movement between frames is the loop running.
|
|
"""
|
|
import re
|
|
import sys
|
|
|
|
VIS_LO, VIS_HI = -1.0, 1.0
|
|
|
|
|
|
def quads(line):
|
|
vs = [(float(a), float(b), c) for a, b, c in
|
|
re.findall(r"\[(-?\d+\.\d+),(-?\d+\.\d+),z=[-\d.]+,col=([0-9A-F]{8})\]", line)]
|
|
return [vs[i:i + 4] for i in range(0, len(vs) - 3, 4)]
|
|
|
|
|
|
def main():
|
|
for path in sys.argv[1:]:
|
|
print("=== %s ===" % path)
|
|
lines = open(path).read().splitlines()
|
|
rows = []
|
|
frame = 0
|
|
print("%-6s %-5s %-6s %-18s %-18s %-8s %s"
|
|
% ("frame", "draw", "quad", "NDC x range", "NDC y range", "col", "on screen?"))
|
|
for i, line in enumerate(lines):
|
|
m = re.match(r"--- frame (\d+) ---", line)
|
|
if m:
|
|
frame = int(m.group(1))
|
|
continue
|
|
m = re.search(r"^\s*(\d+) prim=\d+ indices=(\d+).* blend=0x01010101", line)
|
|
if not m or i + 1 >= len(lines):
|
|
continue
|
|
for k, q in enumerate(quads(lines[i + 1])):
|
|
xs = [p[0] for p in q]
|
|
ys = [p[1] for p in q]
|
|
rows.append((frame, m.group(1), k, xs, ys, q[0][2]))
|
|
on = min(xs) < VIS_HI and max(xs) > VIS_LO and min(ys) < VIS_HI and max(ys) > VIS_LO
|
|
print("%-6d %-5s %-6d %7.2f .. %7.2f %7.2f .. %7.2f %-8s %s"
|
|
% (frame, m.group(1), k, min(xs), max(xs), min(ys), max(ys),
|
|
q[0][2], "ON SCREEN" if on else "parked off screen"))
|
|
# ── alpha vs position, pooled per strip ────────────────────────────
|
|
# The disc declares the ramp in the ptloop01/ptloop02 LEAF keyframes:
|
|
# pteff03 x -39 -> 1521 over t 150..540, alpha 128 -> 255
|
|
# pteff03a x 1111 -> -839 over t 150..630, alpha 128 -> 255
|
|
# which predict d(alpha)/dx of +0.0814 and -0.0651 per design pixel.
|
|
# This measures the same slope off the GPU. NDC prints to two decimals,
|
|
# so one frame's dx is quantised to 6.4 px and alpha to 1 level -- with
|
|
# only a handful of frames the two predictions are INSIDE that noise and
|
|
# this cannot separate them. It is a direction and a magnitude check.
|
|
by_h = {}
|
|
for f, d, q, xs, ys, col in rows:
|
|
h = round((max(ys) - min(ys)) / 2 * 720)
|
|
if h < 900:
|
|
continue
|
|
by_h.setdefault(h, []).append((min(xs), int(col[0:2], 16)))
|
|
print("alpha vs position, per tall strip (design px, alpha level):")
|
|
for h, pts in sorted(by_h.items()):
|
|
pts.sort()
|
|
span_x = (pts[-1][0] - pts[0][0]) * 640
|
|
span_a = pts[-1][1] - pts[0][1]
|
|
slope = span_a / span_x if span_x else float("nan")
|
|
print(" h=%-5d n=%d x %.0f..%.0f px alpha %d..%d d(alpha)/dx %+.4f"
|
|
% (h, len(pts), pts[0][0] * 640, pts[-1][0] * 640,
|
|
pts[0][1], pts[-1][1], slope))
|
|
print()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|