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>
98 lines
4.5 KiB
Python
98 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Discriminate the two competing punch-chunk role models from the authored eye:
|
|
Model A (mask/hull): visible = g1 (coarse hull) MINUS g0 silhouette (g0 = dmg_set mask)
|
|
Model B (lattice): visible = g0 alone (g1/g2 hidden as 'damage plates')
|
|
For each mech render, per punch patch set:
|
|
cov0 = screen coverage of g0 (if ~solid over the view, drawing it opaque blinds -> kills B)
|
|
cov1 = coverage of g1
|
|
covA = coverage of (g1 AND NOT g0) -> Model A visible frame
|
|
openA = fraction of g1 footprint punched open by g0
|
|
Also save a visual 3-panel per mech: g0 | g1 | A-composite.
|
|
"""
|
|
import os, re, math, sys, struct
|
|
import numpy as np
|
|
from PIL import Image
|
|
import importlib.util
|
|
spec = importlib.util.spec_from_file_location("pg", os.path.join(os.path.dirname(__file__), "punch_geom.py"))
|
|
# punch_geom.py runs a loop on import; import its parse via exec of the functions only
|
|
src = open(os.path.join(os.path.dirname(__file__), "punch_geom.py")).read()
|
|
src = src.split("for path in sys.argv")[0]
|
|
ns = {}
|
|
exec(src, ns)
|
|
parse = ns["parse"]
|
|
|
|
ROOT = r"C:\git\bt411"
|
|
MECHS = {"madcat":("MAD","MAX"), "bhk1":("BLH","BLX"), "thor":("THR","THX"),
|
|
"owens":("OWN","OWX"), "sunder":("SND","SNX"), "loki":("LOK","LOX"),
|
|
"vulture":("VUL","VUX"), "avatar":("AVA","AVX")}
|
|
W, H = 320, 240
|
|
|
|
def jointeye(skl_path):
|
|
t = open(skl_path, encoding="latin1").read()
|
|
m = re.search(r"\[jointeye\]", t)
|
|
blk = t[m.start():m.start()+400]
|
|
g = lambda k: float((re.search(rf"{k}=([-\d.e]+)", blk) or [None,"0"])[1])
|
|
return np.array((g("tranx"), g("trany"), g("tranz")))
|
|
|
|
def raster_mask(tris, eye, fwd):
|
|
"""double-sided coverage rasterization -> bool mask"""
|
|
up = np.array([0.,1.,0.])
|
|
r = np.cross(up, fwd); r /= np.linalg.norm(r); u2 = np.cross(fwd, r)
|
|
tanf = math.tan(math.radians(75)/2); aspect = W/H
|
|
m = np.zeros((H,W), bool)
|
|
for tri in tris:
|
|
s = []
|
|
ok = True
|
|
for p in tri:
|
|
d = p - eye
|
|
cx, cy, cz = np.dot(d,r), np.dot(d,u2), np.dot(d,fwd)
|
|
if cz <= 0.02: ok = False; break
|
|
s.append((cx/(cz*tanf*aspect)*0.5*W + 0.5*W, -cy/(cz*tanf)*0.5*H + 0.5*H))
|
|
if not ok: continue
|
|
(ax,ay),(bx,by),(cx,cy) = s
|
|
x0=max(0,int(min(ax,bx,cx))); x1=min(W-1,int(math.ceil(max(ax,bx,cx))))
|
|
y0=max(0,int(min(ay,by,cy))); y1=min(H-1,int(math.ceil(max(ay,by,cy))))
|
|
den=(by-cy)*(ax-cx)+(cx-bx)*(ay-cy)
|
|
if abs(den)<1e-9 or x1<x0 or y1<y0: continue
|
|
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>=-1e-6 and w1>=-1e-6 and w2>=-1e-6: m[py,px]=True
|
|
return m
|
|
|
|
def tris_of(g):
|
|
V = g["verts"]
|
|
return [np.array([V[a][:3],V[b][:3],V[c][:3]]) for (a,b,c) in g["faces"] if max(a,b,c)<len(V)]
|
|
|
|
rows = []
|
|
for name,(pfx,xpfx) in MECHS.items():
|
|
eye = jointeye(os.path.join(ROOT,"content","VIDEO",pfx+".SKL"))
|
|
P = parse(os.path.join(ROOT,"content","VIDEO","GEO",xpfx+"_COP.BGF"))
|
|
views = [("fwd", np.array([0.,0.,-1.]))]
|
|
if name == "madcat": views.append(("rear", np.array([0.,0.,1.])))
|
|
for vname, fwd in views:
|
|
m0 = np.zeros((H,W),bool); m1 = np.zeros((H,W),bool)
|
|
for pt in P:
|
|
if not pt["punch"] or len(pt["geoms"])<3: continue
|
|
m0 |= raster_mask(tris_of(pt["geoms"][0]), eye, fwd)
|
|
m1 |= raster_mask(tris_of(pt["geoms"][1]), eye, fwd)
|
|
A = m1 & ~m0
|
|
tot = W*H
|
|
cov0 = m0.sum()/tot*100; cov1 = m1.sum()/tot*100; covA = A.sum()/tot*100
|
|
openA = (m1&m0).sum()/max(1,m1.sum())*100
|
|
print(f"{name:8s} {vname:4s} g0(maskB-drawn?)={cov0:5.1f}% g1(hull)={cov1:5.1f}% "
|
|
f"A=hull-minus-mask visible={covA:5.1f}% hull punched open={openA:5.1f}%")
|
|
img = np.zeros((H, W*3, 3), np.uint8); img[:] = (150,160,190) # 'sky'
|
|
img[:, :W][m0] = (200,60,40) # g0 footprint
|
|
img[:, W:2*W][m1] = (60,60,70) # g1 footprint
|
|
pane = np.zeros((H,W,3),np.uint8); pane[:] = (150,160,190); pane[A] = (35,25,45)
|
|
img[:, 2*W:] = pane # Model A composite
|
|
rows.append((f"{name}-{vname}", img))
|
|
|
|
out = np.concatenate([r[1] for r in rows], 0)
|
|
Image.fromarray(out).save(os.path.join(os.path.dirname(__file__), "PUNCH_ROLES.png"))
|
|
print("order:", [r[0] for r in rows])
|
|
print("panels: LEFT g0 footprint (red) | MID g1 hull footprint (grey) | RIGHT Model A visible (dark = frame)")
|