Files
arcattackandClaude Fable 5 8ed6184d65 Combat: AUTHENTIC weapon groups -- streamed per-mech button bindings + the real fire chain (task #5)
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>
2026-07-11 13:06:29 -05:00

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)")