#124 CORRECTION: aimed hull hits resolve the struck dz_* PANEL -- players were right

The 08-03 zone rule (struck SEGMENT's SKL dzone, ALWAYS -> every aimed
torso hit = center torso) was wrong, and era players' pushback caught it.
Byte-level proof: MAD_TOR.BGF zone-tags the hull PER PANEL (dz_utorso x36,
dz_ltorso/rtorso x18, dz_dtorso x16, all four rear panels, searchlight);
the dpl hit result kept GEOGROUP granularity (dplHitGeoGroup, T0); and the
binary's segment->zone map @49db20 has NO runtime caller (raw call-scan:
sole caller = CreateStreamedDamageZone, load time) -- no segment-level
collapse mechanism exists.  Oracle's night-10 'only LCT gets hits' audit
was the BUG's fingerprint, not the pod's design.

Fix: MechSegmentPick attributes the struck triangle to its draw op (index
range) and takes the op's .DZM-bound zone -- the #87 armour-darkening
bindings, the same authored patch->zone mapping that already paints the
panels -- with the segment dzone as the untagged fallback.  ZoneAimPoint
now aims hull zones at their patch CENTROIDS (all hull zones previously
shared the chest cull-center), which also upgrades the zone walker.

Bench (2-node zone-walk vs spinning madcat, zonewalk_madcat.sh): every
hull panel resolves individually -- utorso 11/12 in-zone, no L/R
mirroring, all four rear panels register; misses are the panel facing the
shooter mid-spin (correct geometry, not misattribution).  Victim applied
254 hits spread across every panel family.  Was 102/102 hull aims ->
dtorso before the fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-04 17:57:14 -05:00
co-authored by Claude Fable 5
parent 98234c8b15
commit 1d3616cceb
3 changed files with 147 additions and 27 deletions
+89 -12
View File
@@ -1711,17 +1711,22 @@ int
int triBestZone = -1;
int triBestSeg = -1;
int triHit = 0;
d3d_OBJECT *triBestObj = 0; // #124-PATCH: winning object + tri
size_t triBestK = 0; // (triangle ordinal in its IB)
// #124 RESOLVED against the authored data (2026-08-03): the pick's zone is
// the struck DRAWN segment's own SKL dzone, ALWAYS -- the 1995 model (dpl
// scene intersection -> DCS -> segment dzone; combat-damage.md "the #73
// pick dig" [T0+T1]). The madcat's pickable skeleton authors ONE hull
// piece (jointshakey -> dtorso) and no ltorso/rtorso/utorso/rear/door
// segments -- aimed fire at any torso panel authentically designates
// dtorso; the side/rear/upper panels are UNAIMED-path territory (missiles/
// splash through the cylinder). The night-10 audit table is this model's
// exact fingerprint. (An earlier shared-carrier -> cylinder override here
// was an embellishment and is removed.)
// #124 CORRECTED (2026-08-04, field pushback vindicated): the pick's zone
// is the struck PATCH's authored dz_* tag, with the segment's SKL dzone as
// the fallback for untagged patches. The earlier "struck segment's dzone,
// ALWAYS" model collapsed the hull to dtorso -- but the art zone-tags the
// hull PER PANEL (MAD_TOR carries dz_utorso/ltorso/rtorso/dtorso + all four
// rear panels + searchlight across its patches), the dpl hit result kept
// GEOGROUP granularity (dplHitInstance/DCS/GeoGroup/Geometry), and the
// binary's segment->zone map (@49db20) has NO runtime caller (sole caller =
// CreateStreamedDamageZone, load-time) -- there was nothing to do the
// segment-level collapse with. Era players' "I stripped the left torso in
// the pod" stands. The triangle -> patch attribution below reuses the .DZM
// zone->material bindings (#87's armour darkening -- the same authored
// mapping that already paints those panels per-zone).
std::map<int, MechRenderTree::SegPick>::iterator sp;
for (sp = it->second.segPick.begin(); sp != it->second.segPick.end(); ++sp)
@@ -1799,8 +1804,10 @@ int
if (wt >= 0.0f && wt < triBestT)
{
triBestT = wt;
triBestZone = sp->second.zone;
triBestZone = sp->second.zone; // segment dzone (fallback)
triBestSeg = sp->first;
triBestObj = obj;
triBestK = k; // first index of the tri
triHit = 1;
}
}
@@ -1824,8 +1831,31 @@ int
if (triHit)
{
bestT = triBestT;
bestZone = triBestZone; // the struck segment's dzone
bestZone = triBestZone; // segment dzone unless a patch tags it
hitAny = 1;
// #124-PATCH: attribute the struck triangle to its DRAW OP (each op is
// an index RANGE in the same IB the pick mesh copied) and take the
// op's .DZM-bound zone -- the authored dz_* panel. dmgBinds is the
// #87 armour mapping: {obj, op, zone} for every material a zone paints.
if (triBestObj != 0)
{
const std::vector<MechRenderTree::DmgBind> &binds = it->second.dmgBinds;
for (size_t b = 0; b < binds.size(); ++b)
{
if (binds[b].obj != triBestObj)
continue;
L4DRAWOP *op = triBestObj->GetDrawOp(binds[b].op);
if (op == 0 || op->bgfPrimCount <= 0)
continue;
size_t first = (size_t)op->bgfStartIndex;
size_t count = (size_t)op->bgfPrimCount * 3;
if (triBestK >= first && triBestK < first + count)
{
bestZone = binds[b].zone; // the struck PANEL's zone
break;
}
}
}
(void)triBestSeg;
}
@@ -1910,6 +1940,53 @@ int
std::map<Entity*, MechRenderTree>::iterator it = mMechRenderTrees.find(mech);
if (it == mMechRenderTrees.end() || it->second.wrecked)
return 0;
// #124-PATCH: a zone with .DZM-bound panel patches aims at the PATCH
// CENTROID (the authored dz_* panel -- upper/left/right/rear torso are
// distinct patch clusters on ONE torso object, so the old cull-center
// answer aimed every hull zone at the same chest point). Centroid =
// average of the bound ops' triangle vertices, world-transformed.
{
double acc[3] = { 0, 0, 0 };
long nAcc = 0;
const std::vector<MechRenderTree::DmgBind> &binds = it->second.dmgBinds;
for (size_t b = 0; b < binds.size(); ++b)
{
if (binds[b].zone != zone || binds[b].obj == NULL)
continue;
d3d_OBJECT *obj = binds[b].obj;
L4DRAWOP *op = obj->GetDrawOp(binds[b].op);
if (op == 0 || op->bgfPrimCount <= 0)
continue;
BTPickMesh *pm = BTGetPickMesh(obj);
if (pm == 0)
continue;
D3DXMATRIX l2w = obj->GetLocalToWorld();
size_t first = (size_t)op->bgfStartIndex;
size_t last = first + (size_t)op->bgfPrimCount * 3;
if (last > pm->idx.size())
continue;
for (size_t k = first; k < last; ++k)
{
unsigned int vi = pm->idx[k];
if ((size_t)vi * 3 + 2 >= pm->pos.size())
continue;
D3DXVECTOR3 lp(pm->pos[vi*3], pm->pos[vi*3+1], pm->pos[vi*3+2]);
D3DXVECTOR3 wp;
D3DXVec3TransformCoord(&wp, &lp, &l2w);
acc[0] += wp.x; acc[1] += wp.y; acc[2] += wp.z;
++nAcc;
}
}
if (nAcc > 0)
{
out3[0] = (float)(acc[0] / nAcc);
out3[1] = (float)(acc[1] / nAcc);
out3[2] = (float)(acc[2] / nAcc);
return 1;
}
}
d3d_OBJECT *best = 0;
std::map<int, MechRenderTree::SegPick>::iterator sp;
for (sp = it->second.segPick.begin(); sp != it->second.segPick.end(); ++sp)