#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
+21 -15
View File
@@ -102,21 +102,27 @@ the DCS = the struck SEGMENT → its `dzone` (the SKL segment→zone map, `GetSe
`targetDamageZone`. The cylinder lottery (STEP 6) was only ever the UNAIMED path. The port's
whole-mech box pick + "STEP-6 zone under the boresight" funnels AIMED fire through the unaimed
lottery — which is exactly the night-6 report (#73): aim at the arm, get the spray.
**COMPLETED 2026-08-03 (#124, the zone-walk matrix): the pick is now TRIANGLE-ACCURATE and the
zone rule is settled against the authored skeleton [T0 data + T2 matrix].** The sphere
approximation measurably mis-picked (aim dead-on dtorso -> picked rgun/ruleg; its own comments
admitted a foot could be unhittable behind its own knee) — `MechSegmentPick` now runs a sphere
PRE-FILTER then Moller-Trumbore nearest-hit over the threaded segments' posed meshes (CPU
triangle cache from each object's own BGF buffers, `BTGetPickMesh`). **The zone is the struck
segment's SKL dzone, ALWAYS**: the madcat's pickable skeleton authors 18 segments — hip,
shoulders, guns, thighs, knees, ankles+toes, and ONE hull piece (`jointshakey` -> dtorso). No
ltorso/rtorso/utorso/rear/door/searchlight segments exist, so aimed fire at any torso panel
authentically designates CENTER TORSO — the side/rear/upper panels strip only via the UNAIMED
path (missiles/splash/rams through the cylinder). A brief shared-carrier->cylinder override was
an embellishment and was removed same-day. Final matrix (two-node, spinning target, servo
walker): limbs 55/70 in-zone (legs/feet 6/6), hull-family aims -> dtorso 56/69, victim
consumption matching. **The night-10 field audit table is this model's fingerprint** — 'only
LCT gets hits' from aimed energy IS the pod's design. Bench: [[test-harness]] §zone-walk.
**COMPLETED 2026-08-03 (#124, the zone-walk matrix): the pick is TRIANGLE-ACCURATE** [T2 matrix].
The sphere approximation measurably mis-picked (aim dead-on dtorso -> picked rgun/ruleg) —
`MechSegmentPick` runs a sphere PRE-FILTER then Moller-Trumbore nearest-hit over the threaded
segments' posed meshes (CPU triangle cache from each object's own BGF buffers, `BTGetPickMesh`).
**⚠ CORRECTED 2026-08-04 (field pushback from era players — they were RIGHT): the 08-03 zone rule
("the struck segment's SKL dzone, ALWAYS" -> every aimed torso hit = CENTER TORSO) was WRONG.**
The authentic rule: **the pick's zone is the struck PATCH's authored `dz_*` tag** — the art
zone-tags the hull PER PANEL (`MAD_TOR.BGF` carries `dz_utorso`×36 / `dz_ltorso`×18 /
`dz_rtorso`×18 / `dz_dtorso`×16 + all four rear panels + `dz_searchlight` across its patches
[T1 bytes]); the dpl hit result kept GEOGROUP granularity (`dplHitGeoGroup` in the renderer's
result block [T0]); and the binary's segment->zone map `@49db20` has NO runtime caller (sole
caller = `CreateStreamedDamageZone`, load-time — raw call-scan [T1]), so no segment-level
collapse mechanism even exists. Aimed fire in the pod stripped the exact hull panel under the
reticle. The segment-level model looked airtight because LIMBS agree at both granularities (one
zone per limb mesh) — the collapse only bit the hull, and Oracle's night-10 audit ("only LCT gets
hits") was the BUG's fingerprint, not the pod's design. Port: `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 those
panels), segment dzone as the untagged-patch fallback; `ZoneAimPoint` aims hull zones at their
patch CENTROIDS so the zone walker can exercise per-panel. Bench: [[test-harness]] §zone-walk
(madcat variant, scratchpad/night11/zonewalk_madcat.sh).
**TWIST-SIGN VERIFIED (2026-08-04) [T2 live]:** the frame adapter's inferred angular flip
(`SelectSlice: theta -= TorsoHeading()`, vs the binary's `+=` before the z-reflection) is
CORRECT — bench: target torso pinned 0/+140°/140° (`BT_FORCE_TWIST`, sim-level
+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)
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# #124-PATCH verify: per-panel hull zone resolution vs a MADCAT target.
# cured by the #124 triangle-pick work? The zone-walk matrix vs a LOKI target:
# shooter servo-aims every zone (feet included) through the real reticle path,
# 3 laser pulses each; read A's [walk] FIRE lines vs B's [dmghit] zone= lines.
# Same rig as scratchpad/night10/zonewalk.sh, target vehicle=madcat, ~7 min tail.
set -x
. /c/git/bt411/scratchpad/night6/bench_common.sh
cd /c/git/bt411/content || exit 1
bt_assert_player_env
taskkill //F //IM btl4.exe > /dev/null 2>&1
sleep 2
rm -f zwm_a.log zwm_b.log
bt_expert_egg MP.EGG ZWM.EGG
sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/" ZWM.EGG
# pilot 1 (node A, shooter) = madcat (proven servo constraints);
# pilot 2 (node B, target) = LOKI (the #92 chassis)
python - << 'EOF'
lines = open('ZWM.EGG').read().splitlines(True)
n = 0
for i, l in enumerate(lines):
if l.startswith('vehicle='):
n += 1
lines[i] = 'vehicle=madcat\n' if n == 1 else 'vehicle=madcat\n'
open('ZWM.EGG', 'w').writelines(lines)
print('vehicles set:', n)
EOF
BT_SPIN_SELF=15 BT_DMG_LOG=1 BT_DEATH_LOG=1 BT_MP_LOG=1 \
bt_launch zwm_b.log ZWM.EGG 0x0C -net 1601
sleep 2
BT_ZONE_WALK=6 BT_PICK_LOG=1 BT_GOTO=enemy BT_GOTO_STOP=90 BT_KEY_NOFOCUS=1 \
BT_DMG_LOG=1 BT_RANGE_LOG=1 BT_MP_LOG=1 \
bt_launch zwm_a.log ZWM.EGG 0x03 -net 1501
sleep 5
python ../tools/btconsole.py ZWM.EGG 127.0.0.1:1501 127.0.0.1:1601 > zwm_relay.log 2>&1 &
sleep 400 # ~7 min: enough for a full zone walk incl. both feet