From 00dfbf8f9fd1727dc0a67f3cd80771237733839f Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Mon, 3 Aug 2026 11:31:45 -0500 Subject: [PATCH] #124 RESOLVED against the authored skeleton: the pick's zone is the struck drawn segment's dzone, ALWAYS -- the cylinder embellishment removed The open question ('what zone did 1995 designate for hull hits') was already answered in the KB's #73 dig [T0+T1]: no software pick writer exists -- the pod's pick was a dpl scene intersection, struck triangle -> DCS segment -> its SKL-authored dzone; the cylinder lottery was only ever the UNAIMED path (missiles/splash/rams). The live segpick dump completes it with the authored data: the madcat's pickable skeleton is 18 segments -- hip, shoulders, guns, thighs, knees, ankles+toes, and ONE hull piece (jointshakey -> dtorso). No ltorso/rtorso/utorso/rear/ door/searchlight segments exist. Aimed fire at any torso panel authentically designates CENTER TORSO; the side/rear/upper panels strip via the unaimed path. Changes: * the shared-carrier -> cylinder override (yesterday's embellishment) is REMOVED: triangle hit -> that segment's dzone, period; * SegAimPoint: the walker's hull anchor moves from the joint ORIGIN (crotch gap -- rays sailed between the legs to the terrain sentinel) to the carrier segment's cull center (the chest); * [picktri]/[pickwin] diagnostics grew tri/seg/zone fields -- three consecutive 'mystery' regressions in this stretch were the STALE-EXE trap (CWD-relative cmake --build from content/ resolved to a nonexistent dir and the old binary kept running) -- the harness doc's warning, hit again. Final zone-walk matrix (spinning target, real MP, full cycles): limbs 55/70 in-zone (legs/feet 6/6; arm strays = hull/occlusion); hull-family aims -> dtorso 56/69; victim consumption matches. The night-10 field table now reads as this model's fingerprint: 'only LCT gets hits' from aimed energy IS the pod -- one hull piece, dtorso. What was actually broken and is now fixed: the sphere mis-picks (rgun-from-a-dtorso-aim class), the unaimed path's 180-degree frame, and the vertical scatter. Co-Authored-By: Claude Fable 5 --- game/reconstructed/btl4vid.cpp | 86 ++++++++++++++++++++++------------ game/reconstructed/btl4vid.hpp | 2 + game/reconstructed/mech4.cpp | 9 ++-- scratchpad/night10/zonewalk.sh | 2 +- 4 files changed, 66 insertions(+), 33 deletions(-) diff --git a/game/reconstructed/btl4vid.cpp b/game/reconstructed/btl4vid.cpp index abc3826..03f0fa7 100644 --- a/game/reconstructed/btl4vid.cpp +++ b/game/reconstructed/btl4vid.cpp @@ -1681,29 +1681,16 @@ int int triBestSeg = -1; int triHit = 0; - // #124: segments claimed as CARRIER by TWO OR MORE zones (the shared hull - // -- e.g. madcat seg 4 carries dtorso AND ltorso AND rtorso AND utorso AND - // the rears) cannot resolve a single zone from geometry. The authentic - // route for their hits is the CYLINDER: return zone -1 with the accurate - // triangle hit point, and the victim's point resolver assigns the panel - // by band/wedge. Unique-carrier segments (legs, feet, arms, gun pods) - // keep their direct zone. - unsigned char segClaims[192]; - memset(segClaims, 0, sizeof(segClaims)); - { - extern int BTMechZoneSegAndName(void *mech_v, int zone_idx, - int *seg_out, const char **name_out); - int zsi; - const char *znm; - for (int zi = 0; zi < 64; ++zi) - { - if (!BTMechZoneSegAndName((void *)mech, zi, &zsi, &znm)) - break; - if (zsi >= 0 && zsi < (int)sizeof(segClaims) - && segClaims[zsi] < 255) - ++segClaims[zsi]; - } - } + // #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.) std::map::iterator sp; for (sp = it->second.segPick.begin(); sp != it->second.segPick.end(); ++sp) @@ -1737,6 +1724,15 @@ int // ---- TRIANGLE TEST (#124): the sphere only nominates ---- BTPickMesh *pm = BTGetPickMesh(obj); + if (pm == 0 && getenv("BT_PICK_LOG")) + { + static int s_nt = 0; + if (s_nt++ < 40) + DEBUG_STREAM << "[picktri] seg=" << sp->first << " zone=" + << sp->second.zone << " NO TRIS (vb=" << (void *)obj->mBgfVB + << " ib=" << (void *)obj->mBgfIB + << " stride=" << obj->mBgfStride << ")\n" << std::flush; + } if (pm != 0) { // ray into object-local space (affine inverse; segment poses are @@ -1797,12 +1793,9 @@ int if (triHit) { bestT = triBestT; - bestZone = triBestZone; + bestZone = triBestZone; // the struck segment's dzone hitAny = 1; - // shared-carrier segment -> the cylinder decides (accurate point kept) - if (triBestSeg >= 0 && triBestSeg < (int)sizeof(segClaims) - && segClaims[triBestSeg] > 1) - bestZone = -1; + (void)triBestSeg; } // #92 probe: which spheres did the ray actually THREAD, and which won? @@ -1865,7 +1858,9 @@ int static int s_pl = 0; if ((s_pl++ % 60) == 0) DEBUG_STREAM << "[pickwin] zone=" << bestZone - << " score=" << bestScore << " t=" << bestT << "\n" << std::flush; + << " score=" << bestScore << " t=" << bestT + << " tri=" << triHit << " triSeg=" << triBestSeg + << " triZone=" << triBestZone << "\n" << std::flush; } hit_out[0] = ray_start[0] + bestT * ray_dir[0]; @@ -1902,6 +1897,39 @@ int return 1; } +// The carrier-SEGMENT variant: anchor at segPick[seg]'s cull center (the +// piece's visual middle). For hull-family zones no object claims the zone, +// and the zone table's carrier is the hull joint whose ORIGIN sits in the +// crotch gap -- rays aimed there sail between the legs to the terrain +// sentinel. The cull center is the chest. +int + BTL4VideoRenderer::SegAimPoint(Entity *mech, int seg, float out3[3]) +{ + std::map::iterator it = mMechRenderTrees.find(mech); + if (it == mMechRenderTrees.end() || it->second.wrecked) + return 0; + std::map::iterator sp = it->second.segPick.find(seg); + if (sp == it->second.segPick.end() || sp->second.obj == NULL + || sp->second.obj->mCullRadius <= 0.0f) + return 0; + D3DXMATRIX l2w = sp->second.obj->GetLocalToWorld(); + D3DXVECTOR3 cw; + D3DXVec3TransformCoord(&cw, &sp->second.obj->mCullCenter, &l2w); + out3[0] = cw.x; out3[1] = cw.y; out3[2] = cw.z; + return 1; +} + +int BTMechSegAimPoint(void *mech, int seg, float out3[3]) +{ + if (mech == NULL || application == NULL) + return 0; + BTL4VideoRenderer *renderer = + (BTL4VideoRenderer *)application->GetVideoRenderer(); + if (renderer == NULL) + return 0; + return renderer->SegAimPoint((Entity *)mech, seg, out3); +} + int BTMechZoneAimPoint(void *mech, int zone, float out3[3]) { if (mech == NULL || application == NULL) diff --git a/game/reconstructed/btl4vid.hpp b/game/reconstructed/btl4vid.hpp index 67afa72..243fcdb 100644 --- a/game/reconstructed/btl4vid.hpp +++ b/game/reconstructed/btl4vid.hpp @@ -803,6 +803,8 @@ extern void BTDrawReticle(struct IDirect3DDevice9 *device); // above). 0 when the zone has no pick geometry. int ZoneAimPoint(Entity *mech, int zone, float out3[3]); + int + SegAimPoint(Entity *mech, int seg, float out3[3]); protected: // diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index c343c96..0ee997c 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -6127,14 +6127,17 @@ void << target->GetEntityID() << "\n" << std::flush; } extern int BTMechZoneAimPoint(void *mech, int zone, float out3[3]); + extern int BTMechSegAimPoint(void *mech, int seg, float out3[3]); int haveAim = 0; if (BTMechZoneSegAndName(target, s_zwZone, &segIdx, &zname) && segIdx >= 0) { - // prefer the zone's VISUAL center (its pick geometry's - // cull-center); the segment ORIGIN sits at the joint - // and made feet/lower legs strike the part above. + // anchor order: the zone's own pick geometry -> the + // CARRIER segment's cull center (hull-family zones own + // no geometry; the joint ORIGIN sits in the crotch gap + // and rays sail between the legs) -> the raw joint. haveAim = BTMechZoneAimPoint(target, s_zwZone, sp) + || BTMechSegAimPoint(target, segIdx, sp) || BTResolveSegmentWorld((void *)target, segIdx, sp, rows); } if (haveAim) diff --git a/scratchpad/night10/zonewalk.sh b/scratchpad/night10/zonewalk.sh index 849e4fc..ce86393 100644 --- a/scratchpad/night10/zonewalk.sh +++ b/scratchpad/night10/zonewalk.sh @@ -24,7 +24,7 @@ BT_SPIN_SELF=15 BT_DMG_LOG=1 BT_DEATH_LOG=1 BT_MP_LOG=1 BT_MATCHLOG=1 \ bt_launch zw_b.log ZW.EGG 0x0C -net 1601 sleep 2 # SHOOTER second (foreground -- owns the pick focus): the walker. -BT_ZONE_WALK=6 BT_GOTO=enemy BT_GOTO_STOP=90 BT_KEY_NOFOCUS=1 \ +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_MATCHLOG=1 \ bt_launch zw_a.log ZW.EGG 0x03 -net 1501 sleep 5