#!/usr/bin/env python3 """Hypothesis-discrimination renderer for the cockpit PUNCH panes. For every mech, render the *_COP shell from the authored eye three ways: H1 punch panes DROPPED entirely (current port behavior) H2 punch panes 50% dark translucent (triangle_fifty / stipple average) H3 punch panes OPAQUE (state-gated opaque-until-damaged, undamaged state) Non-punch frame = dark cockpit purple; punch pane (when visible) = warm tint so we can tell WHICH pixels the pane owns. World = bright sky/ground gradient so translucency dimming is measurable. Output: scratchpad/HYP_MATRIX.png plus per-mech rows, and per-mode luminance stats printed for the pane-covered pixels. """ import re, os, math, sys import numpy as np from PIL import Image, ImageDraw import importlib.util spec = importlib.util.spec_from_file_location("bgf_parse", os.path.join(os.path.dirname(__file__), "bgf_parse.py")) bgf = importlib.util.module_from_spec(spec); spec.loader.exec_module(bgf) 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 = 300, 225 FRAME_COL = (52, 42, 78) # dark cockpit purple (reference frame color family) PANE_COL = (40, 32, 60) # the pane's own material color (black mech skin family) PANE_TINT = (255, 120, 60) # H3 overlay tint so opaque panes are identifiable 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 (g("tranx"), g("trany"), g("tranz")) def world_bg(): img = np.zeros((H, W, 3), np.float32) for y in range(H): t = y / H if t < 0.52: # sky: bright grey-lavender like the reference footage k = t / 0.52 img[y,:] = (1-k)*np.array((196,192,214)) + k*np.array((214,208,224)) else: # ground: light purple terrain k = (t-0.52)/0.48 img[y,:] = (1-k)*np.array((178,168,198)) + k*np.array((150,140,178)) return img def project(tri, e, r, u2, f, tanf, aspect): s = [] for p in tri: d = p - e; cx = np.dot(d, r); cy = np.dot(d, u2); cz = np.dot(d, f) if cz <= 0.02: return None s.append((cx/(cz*tanf*aspect)*0.5*W + 0.5*W, -cy/(cz*tanf)*0.5*H + 0.5*H, cz)) return s def raster(buf, zb, s, col, mask=None): (ax,ay,az),(bx,by,bz),(cx,cy,cz) = s x0=max(0,int(min(ax,bx,cx))); x1=min(W-1,int(max(ax,bx,cx))) y0=max(0,int(min(ay,by,cy))); y1=min(H-1,int(max(ay,by,cy))) den=(by-cy)*(ax-cx)+(cx-bx)*(ay-cy) if abs(den)<1e-6 or x1= len(V): continue tri = np.array([V[a][:3],V[b][:3],V[c][:3]]) n = np.cross(tri[1]-tri[0], tri[2]-tri[0]); nl = np.linalg.norm(n) if nl < 1e-9: continue n/=nl; fc = tri.mean(0) inward = n if np.dot(n, cen-fc) >= 0 else -n if np.dot(inward, eye-fc) <= 0: continue # single-sided s = project(tri, eye, r, u2, f, tanf, aspect) if s is None: continue shade = 0.55 + 0.45*abs(np.dot(inward, f)) yield s, shade # pass 1: opaque non-punch frame for s, shade in faces(False): raster(img, zb, s, tuple(ch*shade for ch in FRAME_COL)) # pass 2: punch panes per hypothesis if mode == "H3": # opaque for s, shade in faces(True): col = tuple(0.6*ch*shade + 0.4*tc*shade for ch,tc in zip(PANE_COL, PANE_TINT)) raster(img, zb, s, col, pane_mask) elif mode == "H2": # 50% dark translucent: blend pane color with what's behind pane_z = np.full((H,W), 1e9) pane_sh = np.zeros((H,W), np.float32) dummy = np.zeros((H,W,3), np.float32) for s, shade in faces(True): m = np.zeros((H,W), bool) raster(dummy, pane_z, s, (0,0,0), m) pane_sh[m] = shade vis = pane_z < zb # pane in front of frame at this pixel pane_mask |= vis pc = np.array(PANE_COL, np.float32) img[vis] = 0.5*img[vis] + 0.5*(pc[None,:]*pane_sh[vis,None]) # H1: nothing (dropped) lum = img.mean(axis=2) stats = (lum[pane_mask].mean() if pane_mask.any() else float('nan'), pane_mask.mean()) return np.clip(img,0,255).astype(np.uint8), stats if __name__ == "__main__": rows = [] print(f"{'mech':8s} {'mode':3s} pane_px% pane_lum") for m in MECHS: tiles = [] for mode in ("H1","H2","H3"): im, (plum, pfrac) = render(m, mode) print(f"{m:8s} {mode} {100*pfrac:6.1f}% {plum:7.1f}") fr = Image.fromarray(im); d = ImageDraw.Draw(fr) d.text((4,3), f"{m} {mode}", fill=(20,20,25)) d.text((3,2), f"{m} {mode}", fill=(255,255,140)) tiles.append(np.array(fr)) g = np.full((H,4,3),255,np.uint8) row = np.concatenate([tiles[0],g,tiles[1],g,tiles[2]],axis=1) rows.append(row); rows.append(np.full((4,row.shape[1],3),255,np.uint8)) out = np.concatenate(rows[:-1],axis=0) p = os.path.join(ROOT,"scratchpad","HYP_MATRIX.png") Image.fromarray(out).save(p) print("saved", p)