The recovered system: fire channels = LBE4ControlsManager buttonGroups (0x40/0x45/0x46/0x47); default groups = the per-mech type-6 controls-map resource in BTL4.RES, installed by the T0 CreateStreamedMappings the port already called -- it needed only the TriggerState attribute (id 0x13 PINNED to the binary value; fireImpulse@0x31C is the binary's TriggerState) and an input feed. Keyboard/harness now push press/release edges into the button groups; the gBT*Trigger bypasses, per-type keyboard split and 1,0 pulse hack are retired -- weapons sharing a button fire TOGETHER (madcat Trigger = 4 weapons). Myomers @4b9550/@4b95b8 misattribution corrected (they are MechWeapon ConfigureMappables/ChooseButton). Verified 2-node: kill through the authentic chain (12 hits vs ~36 pre-groups). Config-mode session (regrouping UI) = the remaining stage, KB-scoped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
85 lines
3.5 KiB
Python
85 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Offline cockpit-shell renderer (warp-style iteration harness).
|
|
Loads a *_COP.BGF via bgf_parse and rasterizes it from a cockpit eye with a
|
|
z-buffer over a placeholder world (sky+ground), so we can iterate the frame/
|
|
window reconstruction against the cabinet reference WITHOUT a full game build.
|
|
|
|
Hypothesis under test: the PUNCH patch is the windshield -> DROP it (transparent);
|
|
keep the non-punch frame patches (dark 'softer'-ramp gray).
|
|
Env knobs (iterate the camera): EX EY EZ EZOFF TZ FOV ; KEEP_PUNCH=1 keeps it.
|
|
"""
|
|
import sys, os, math
|
|
import numpy as np
|
|
from PIL import Image
|
|
from bgf_parse import parse
|
|
|
|
W, H = 480, 360
|
|
def envf(k,d):
|
|
v=os.getenv(k); return float(v) if v not in (None,"") else d
|
|
|
|
def render(path, out):
|
|
P = parse(path)
|
|
tris=[]
|
|
for pt in P:
|
|
V=pt["verts"]
|
|
for (a,b,c) in pt["faces"]:
|
|
if max(a,b,c)>=len(V): continue
|
|
tris.append((np.array(V[a][:3]),np.array(V[b][:3]),np.array(V[c][:3]),pt["punch"]))
|
|
allv=np.array([v[:3] for pt in P for v in pt["verts"]])
|
|
lo,hi=allv.min(0),allv.max(0); cen=(lo+hi)/2
|
|
drop_punch = os.getenv("KEEP_PUNCH") is None
|
|
|
|
eye=np.array([envf("EX",0.0), envf("EY",float(cen[1])), envf("EZ",float(hi[2])+envf("EZOFF",0.4))])
|
|
target=np.array([eye[0], eye[1], envf("TZ",float(lo[2])-1.0)])
|
|
up=np.array([0.0,1.0,0.0])
|
|
f=target-eye; f/=np.linalg.norm(f)+1e-9
|
|
r=np.cross(f,up); r/=np.linalg.norm(r)+1e-9
|
|
u=np.cross(r,f)
|
|
fov=math.radians(envf("FOV",75.0)); tanf=math.tan(fov/2); aspect=W/H
|
|
|
|
img=np.zeros((H,W,3),np.float32)
|
|
for y in range(H):
|
|
t=y/H
|
|
img[y,:]= (np.array([0.55,0.52,0.68])+(0.30*(0.5-t))*np.array([0.7,0.7,0.6])) if t<0.5 \
|
|
else (np.array([0.72,0.68,0.78])-(0.15*(t-0.5))*np.array([0.5,0.5,0.4]))
|
|
zb=np.full((H,W),1e9,np.float32)
|
|
frame=np.array([0.28,0.24,0.34])
|
|
|
|
def proj(p):
|
|
d=p-eye; cx=np.dot(d,r); cy=np.dot(d,u); cz=np.dot(d,f)
|
|
if cz<=0.01: return None
|
|
return np.array([(cx/(cz*tanf*aspect))*0.5*W+0.5*W, -(cy/(cz*tanf))*0.5*H+0.5*H, cz])
|
|
|
|
drawn=0
|
|
for (p0,p1,p2,punch) in tris:
|
|
if drop_punch and punch: continue
|
|
s=[proj(p0),proj(p1),proj(p2)]
|
|
if any(x is None for x in s): continue
|
|
s0,s1,s2=s
|
|
col=frame*(1.0-0.05*min(1.0,(s0[2]+s1[2]+s2[2])/12.0))
|
|
raster(img,zb,s0,s1,s2,col); drawn+=1
|
|
Image.fromarray((np.clip(img,0,1)*255).astype(np.uint8)).save(out)
|
|
print(f"{os.path.basename(path)} -> {out} eye={eye.round(2)} tgt={target.round(2)} "
|
|
f"fov={math.degrees(fov):.0f} drop_punch={drop_punch} drawn={drawn}/{len(tris)}")
|
|
|
|
def raster(img,zb,s0,s1,s2,col):
|
|
xs=[s0[0],s1[0],s2[0]]; ys=[s0[1],s1[1],s2[1]]
|
|
x0=max(0,int(min(xs))); x1=min(W-1,int(max(xs))); y0=max(0,int(min(ys))); y1=min(H-1,int(max(ys)))
|
|
if x1<x0 or y1<y0: return
|
|
ax,ay=s0[0],s0[1]; bx,by=s1[0],s1[1]; cx,cy=s2[0],s2[1]
|
|
den=(by-cy)*(ax-cx)+(cx-bx)*(ay-cy)
|
|
if abs(den)<1e-6: return
|
|
for py in range(y0,y1+1):
|
|
for px in range(x0,x1+1):
|
|
w0=((by-cy)*(px+.5-cx)+(cx-bx)*(py+.5-cy))/den
|
|
w1=((cy-ay)*(px+.5-cx)+(ax-cx)*(py+.5-cy))/den
|
|
w2=1-w0-w1
|
|
if w0<0 or w1<0 or w2<0: continue
|
|
z=w0*s0[2]+w1*s1[2]+w2*s2[2]
|
|
if z<zb[py,px]: zb[py,px]=z; img[py,px]=col
|
|
|
|
if __name__=="__main__":
|
|
path=sys.argv[1] if len(sys.argv)>1 else "content/VIDEO/GEO/MAX_COP.BGF"
|
|
out=sys.argv[2] if len(sys.argv)>2 else "scratchpad/cop_offline.png"
|
|
render(path,out)
|