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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
9414268138
commit
8431e69b1a
+53
-69
@@ -8,6 +8,7 @@
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <array>
|
||||
|
||||
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<const void*> 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<std::pair<size_t, uint64_t>, 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<BgfDrawBatch> 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/
|
||||
|
||||
Reference in New Issue
Block a user