M4c COMPLETE: real battle geometry from live game wire, end to end
battle_frames.py extracts the per-draw effect programs (the production
build emits them at ~0x815f000, like fxtest's 0x816f000) and rasterizes
their {edge, z, u/v} primitives through the render_fx GPU path. Running the
netdeath battle capture on the production firmware (vrend410) + emu860c +
GPU: 8 frames in 2.8s showing the actual mech-combat arena -- ground plane,
horizon, a building, receding structures -- with 31..148 primitives/frame
growing as the scene loads (cmd 8125 deep).
The full chain is now proven on real game content: game's own firmware ->
C-accelerated i860 -> the card's own IGC/EMC instruction set on the GPU ->
recognizable battle frames. Surfaces are u/v false-colour pending texel
sampling (the TXDN path, M3); the geometry is the game's.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
"""Battle content frames: production firmware on the C core, real game wire,
|
||||
per-draw EFFECT-PRIMITIVE readout (render_fx path) -> actual scene geometry.
|
||||
The draws build effect programs at ~0x815f000 (like fxtest's 0x816f000)."""
|
||||
import sys, os, time, struct
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE); sys.path.insert(0, os.path.dirname(HERE))
|
||||
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
|
||||
import emu860, emu_main, emu860c, igc_exec, igc_gpu, igc_gpu_frame
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from driver import boot, CpuShim
|
||||
from vrboard import A
|
||||
ANAME = {int(a): a.name for a in A}
|
||||
emu860.Mem.log = lambda self, *a, **k: None
|
||||
|
||||
SRC = sys.argv[1]
|
||||
MAXF = int(sys.argv[2]) if len(sys.argv) > 2 else 8
|
||||
PROG_LO, PROG_HI = 0x08158000, 0x08170000 # per-draw effect program window
|
||||
W, H = 832, 512
|
||||
|
||||
data = open(SRC, 'rb').read()
|
||||
recs = []; off = 0
|
||||
while True:
|
||||
i = data.find(b'VPXM', off)
|
||||
if i < 0: break
|
||||
ln = struct.unpack_from('<I', data, i + 4)[0]
|
||||
body = data[i + 8:i + 8 + ln]; off = i + 8 + ln
|
||||
if len(body) >= 4:
|
||||
a = struct.unpack_from('<I', body, 0)[0]
|
||||
if a < 0x100: recs.append((a, body[4:]))
|
||||
print("queued %d records" % len(recs), flush=True)
|
||||
|
||||
r = boot(fw='vrend410', queue=recs)
|
||||
shim = CpuShim()
|
||||
RECV = emu_main.MAPS['vrend410']['receive']
|
||||
ctx = igc_gpu.GpuTile().ctx
|
||||
print("GPU:", ctx.info['GL_RENDERER'], flush=True)
|
||||
|
||||
|
||||
def f32(w): return struct.unpack('<f', struct.pack('<I', w & 0xffffffff))[0]
|
||||
|
||||
|
||||
def render_effects(nth):
|
||||
prog = {a: emu860c.r32(a) for a in range(PROG_LO, PROG_HI, 4)
|
||||
if emu860c.r32(a)}
|
||||
def rd(a): return prog.get(a, 0)
|
||||
setups = [a for a in sorted(prog)
|
||||
if rd(a) == 0x100 and ((rd(a + 4) >> 8) & 0xff) == 0x2c]
|
||||
if not setups:
|
||||
return False, 0
|
||||
recs4 = []
|
||||
for a in setups:
|
||||
edges = []
|
||||
p = a + 4
|
||||
while ((rd(p) >> 8) & 0xff) == 0x2c and len(edges) < 6:
|
||||
edges.append((f32(rd(p+4)), f32(rd(p+8)), f32(rd(p+12)))); p += 16
|
||||
zp = up = vp = None; q = p
|
||||
for _ in range(40):
|
||||
wv = rd(q); op = (wv >> 8) & 0xff; ad = wv & 0xff
|
||||
if op == 0x21 and zp is None: zp = (f32(rd(q+4)), f32(rd(q+8)), f32(rd(q+12)))
|
||||
if op == 0x43 and ad == 58 and up is None: up = (f32(rd(q+4)), f32(rd(q+8)), f32(rd(q+12)))
|
||||
if op == 0x43 and ad == 78 and vp is None: vp = (f32(rd(q+4)), f32(rd(q+8)), f32(rd(q+12)))
|
||||
if zp and up and vp: break
|
||||
q += 4
|
||||
if zp is None or len(edges) < 2:
|
||||
continue
|
||||
hasuv = 1.0 if (up and vp) else 0.0
|
||||
up = up or (0, 0, 0); vp = vp or (0, 0, 0)
|
||||
eslots = [(e[0], e[1], e[2], 1.0) for e in edges[:4]]
|
||||
while len(eslots) < 4: eslots.append((0.0, 0.0, 0.0, 0.0))
|
||||
recs4 += eslots + [(*zp, float(len(edges))), (*up, hasuv), (*vp, 0.0)]
|
||||
if not recs4:
|
||||
return False, 0
|
||||
prims = np.array(recs4, dtype=np.float32).reshape(-1, 4)
|
||||
n_prims = len(prims) // 7
|
||||
prog_gl = ctx.compute_shader(igc_gpu_frame.FX_SHADER)
|
||||
b0 = ctx.buffer(prims.tobytes()); out = ctx.buffer(reserve=W * H * 16)
|
||||
b0.bind_to_storage_buffer(0); out.bind_to_storage_buffer(1)
|
||||
prog_gl['n_prims'] = n_prims; prog_gl['width'] = W
|
||||
prog_gl.run(group_x=(W + 63) // 64, group_y=H)
|
||||
raw = np.frombuffer(out.read(), dtype=np.uint32).reshape(H, W, 4)
|
||||
z = raw[..., 0].view(np.float32); u = raw[..., 1].view(np.float32); v = raw[..., 2].view(np.float32)
|
||||
m = z > -1e29
|
||||
rgb = np.zeros((H, W, 3), np.uint8)
|
||||
if m.any():
|
||||
un = np.zeros_like(u); vn = np.zeros_like(v)
|
||||
un[m] = (u[m] - u[m].min()) / max(1e-9, np.ptp(u[m]))
|
||||
vn[m] = (v[m] - v[m].min()) / max(1e-9, np.ptp(v[m]))
|
||||
rgb[..., 0][m] = (60 + 180 * un[m]).astype(np.uint8)
|
||||
rgb[..., 1][m] = (40 + 160 * vn[m]).astype(np.uint8)
|
||||
rgb[..., 2][m] = (220 - 140 * un[m]).astype(np.uint8)
|
||||
b0.release(); out.release()
|
||||
Image.fromarray(rgb, 'RGB').save(os.path.join(HERE, 'battle_%04d.png' % nth))
|
||||
return True, (n_prims, int(m.sum()))
|
||||
|
||||
|
||||
frames = 0; prev_draw = False; t0 = time.time()
|
||||
while frames < MAXF and r.qi < len(r.queue):
|
||||
reason, _ = emu860c.run(500_000_000)
|
||||
if reason == 3: continue
|
||||
if reason != 0:
|
||||
print("core reason %d" % reason, flush=True); break
|
||||
pc = emu860c.getstate()['pc']
|
||||
h = r.hooks.get(pc)
|
||||
if h is None:
|
||||
print("sentinel", flush=True); break
|
||||
if pc == RECV:
|
||||
if prev_draw:
|
||||
ok, info = render_effects(frames)
|
||||
if ok:
|
||||
print("battle frame %d: %s prims/px (cmd %d)" % (frames, info, r.qi), flush=True)
|
||||
frames += 1
|
||||
prev_draw = False
|
||||
if r.qi < len(r.queue) and ANAME.get(r.queue[r.qi][0]) == 'draw_scene':
|
||||
prev_draw = True
|
||||
if h(shim) == 'done':
|
||||
break
|
||||
print("done: %d battle frames in %.1fs (cmd %d)" % (frames, time.time() - t0, r.qi), flush=True)
|
||||
Reference in New Issue
Block a user