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>
54 lines
2.8 KiB
Python
54 lines
2.8 KiB
Python
import struct, math, numpy as np
|
|
from PIL import Image
|
|
exec(open("scratchpad/render_bicone.py").read().split("V,UV,F=tess")[0]) # reuse parse+tex+helpers up to tess
|
|
# rebuild raw (no tess)
|
|
Va=np.array(V); UVa=np.array(UV)
|
|
print("RAW verts",len(V),"faces",len(F))
|
|
def sample_bilin(u,v):
|
|
uu=(u%1.0)*w-0.5; vv=(v%1.0)*h-0.5
|
|
x0=np.floor(uu).astype(int); y0=np.floor(vv).astype(int); fx=uu-x0; fy=vv-y0
|
|
x0m=x0%w;x1m=(x0+1)%w;y0m=y0%h;y1m=(y0+1)%h
|
|
return (texL[y0m,x0m]*(1-fx)*(1-fy)+texL[y0m,x1m]*fx*(1-fy)+texL[y1m,x0m]*(1-fx)*fy+texL[y1m,x1m]*fx*fy)
|
|
def clip_near(poly,zn=0.1):
|
|
out=[];n=len(poly)
|
|
for i in range(n):
|
|
A=poly[i];B=poly[(i+1)%n]
|
|
if A[2]>=zn: out.append(A)
|
|
if (A[2]>=zn)!=(B[2]>=zn):
|
|
t=(zn-A[2])/(B[2]-A[2]); out.append(tuple(A[k]+(B[k]-A[k])*t for k in range(5)))
|
|
return out
|
|
def render3(eyeUp,spin,twist,contrast,fov=75.0,LO=(0.15,0.12,0.35),HI=(0.98,0.94,1.0),W=400,H=300,radfreq=1.0):
|
|
LO=np.array(LO);HI=np.array(HI)
|
|
qx=Va[:,0]; qy=Va[:,1]-eyeUp; qz=Va[:,2]
|
|
c,s=math.cos(spin),math.sin(spin); rx=qx*c-qy*s; ry=qx*s+qy*c; rz=qz
|
|
P=np.stack([rx,ry,rz],axis=1); f=(H/2)/math.tan(math.radians(fov)/2)
|
|
img=np.zeros((H,W,3)); zbf=np.full((H,W),1e18)
|
|
for (a,b,cc) in F:
|
|
tri=[(P[i][0],P[i][1],P[i][2],UVa[i][0]+twist*UVa[i][1],UVa[i][1]*radfreq) for i in (a,b,cc)]
|
|
cp=clip_near(tri)
|
|
if len(cp)<3:continue
|
|
for k in range(1,len(cp)-1):
|
|
A,B,C=cp[0],cp[k],cp[k+1]
|
|
def pr(p):return(W/2+f*p[0]/p[2],H/2-f*p[1]/p[2],p[2],p[3],p[4])
|
|
xa,ya,za,ua,vaa=pr(A);xb,yb,zb2,ub,vbb=pr(B);xc,yc,zc,uc,vcc=pr(C)
|
|
minx=max(int(min(xa,xb,xc)),0);maxx=min(int(max(xa,xb,xc))+1,W-1)
|
|
miny=max(int(min(ya,yb,yc)),0);maxy=min(int(max(ya,yb,yc))+1,H-1)
|
|
if minx>=maxx or miny>=maxy:continue
|
|
den=((yb-yc)*(xa-xc)+(xc-xb)*(ya-yc))
|
|
if abs(den)<1e-9:continue
|
|
ys,xs=np.mgrid[miny:maxy+1,minx:maxx+1];xf=xs+.5;yf=ys+.5
|
|
l1=((yb-yc)*(xf-xc)+(xc-xb)*(yf-yc))/den; l2=((yc-ya)*(xf-xc)+(xa-xc)*(yf-yc))/den; l3=1-l1-l2
|
|
m=(l1>=0)&(l2>=0)&(l3>=0)
|
|
if not m.any():continue
|
|
iz=l1/za+l2/zb2+l3/zc; z=1/iz
|
|
u=(l1*ua/za+l2*ub/zb2+l3*uc/zc)*z; v=(l1*vaa/za+l2*vbb/zb2+l3*vcc/zc)*z
|
|
lum=np.clip((sample_bilin(u,v)-0.5)*contrast+0.5,0,1)
|
|
col=LO[None,:]+(HI-LO)[None,:]*lum[...,None]
|
|
yi=ys[m];xi=xs[m];zz=z[m];cl=zz<zbf[yi,xi]
|
|
zbf[yi[cl],xi[cl]]=zz[cl]; img[yi[cl],xi[cl]]=col[m][cl]
|
|
return (np.clip(img,0,1)*255).astype(np.uint8)
|
|
ig=np.array(Image.open("scratchpad/ingame_warp2.png").convert("RGB").resize((400,300)))
|
|
row=[ig]+[render3(eu,0.5,0.0,1.0) for eu in (0.0,4.0,8.25)]
|
|
Image.fromarray(np.concatenate(row,axis=1)).save("scratchpad/cmp_notess.png")
|
|
print("saved cmp_notess.png [IN-GAME | RAW(no-tess) eyeUp=0,4,8.25]")
|