From 1d3616cceb106ae40c2bacaa61f890e0ba0a33e9 Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Tue, 4 Aug 2026 17:57:14 -0500 Subject: [PATCH] #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 --- context/combat-damage.md | 36 +++++---- game/reconstructed/btl4vid.cpp | 101 +++++++++++++++++++++++--- scratchpad/night11/zonewalk_madcat.sh | 37 ++++++++++ 3 files changed, 147 insertions(+), 27 deletions(-) create mode 100644 scratchpad/night11/zonewalk_madcat.sh diff --git a/context/combat-damage.md b/context/combat-damage.md index fc7ff5c..7839085 100644 --- a/context/combat-damage.md +++ b/context/combat-damage.md @@ -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 diff --git a/game/reconstructed/btl4vid.cpp b/game/reconstructed/btl4vid.cpp index 12aba7d..2b04e97 100644 --- a/game/reconstructed/btl4vid.cpp +++ b/game/reconstructed/btl4vid.cpp @@ -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::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 &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::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 &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::iterator sp; for (sp = it->second.segPick.begin(); sp != it->second.segPick.end(); ++sp) diff --git a/scratchpad/night11/zonewalk_madcat.sh b/scratchpad/night11/zonewalk_madcat.sh new file mode 100644 index 0000000..bdb29c3 --- /dev/null +++ b/scratchpad/night11/zonewalk_madcat.sh @@ -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