From e6c5ac951e964e078facfbe99f393ccaf182d51d Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Sat, 8 Aug 2026 09:28:04 -0500 Subject: [PATCH] #141 sweep: every hand-composed segment->world now goes through the engine accessor Finishing the audit the #141 fix implied. The unfaithful pattern mw.Multiply(seg->GetSegmentToEntity(), mech->localToWorld); appeared at FOUR sites, not one. GetSegmentToEntity only recomputes when segmentModified is already set (SEGMENT.cpp:262); the thing that sets it is the binary's FUN_00424da8 == JointedMover::GetSegmentToWorld, which tests AreJointsModified() and marks the whole segment table dirty. Compose by hand and you read whatever cache is there -- fresh on the local mech (the render pass refreshes it every frame), BIND POSE on any replicant. Swept (the muzzle path was fixed in f01de8c): * BTResolveWeaponMuzzle -- weapon muzzle (already done) * BTGetMechSegmentWorldPos @1066 -- generic segment->world bridge * damage-effect anchor @2148 -- peer effects anchored to the bind pose * energy-beam gun port @8916 -- SAME exposure as the missile launch: a peer's BEAM would originate from the untwisted gun port too Repo-wide grep now shows exactly one GetSegmentToEntity call outside SEGMENT.cpp -- JMOVER.cpp:153, which is inside GetSegmentToWorld itself, after the refresh. That is the correct one. No regression (scratchpad/night13/missileframe.sh): master n=165 max 2.1719 mean 1.2781 >0.1rad 100% REPLICANT n=165 max 2.0907 mean 0.8201 >0.1rad 64% and the peer failures remain a clean PREFIX with zero interleaved cases -- i.e. only the window before the peer has any replicated twist to carry, which is correct behaviour, not a miss. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_018SgmXGNMXavXiafKXf9MDC --- game/reconstructed/mech4.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 32498bd..ae60ad5 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -1062,8 +1062,13 @@ int { if (seg->GetIndex() == seg_index) { - AffineMatrix mw; - mw.Multiply(seg->GetSegmentToEntity(), m->localToWorld); + // #141 sweep: go through the engine accessor, which is the + // binary's FUN_00424da8 (the joints->segments refresh). A hand + // composed GetSegmentToEntity() x localToWorld reads a STALE + // cache on any mech whose segments were not refreshed this frame + // -- i.e. every REPLICANT. See BTResolveWeaponMuzzle. + LinearMatrix mw; + m->GetSegmentToWorld(*seg, &mw); p = mw; // Point3D = matrix translation break; } @@ -2144,8 +2149,11 @@ void { if (seg->GetIndex() == segment_index) { - AffineMatrix mw; - mw.Multiply(seg->GetSegmentToEntity(), mech->localToWorld); + // #141 sweep: engine accessor (== the binary's FUN_00424da8), + // not a hand-composed product -- otherwise a peer's damage + // effect anchors to the BIND-POSE segment. + LinearMatrix mw; + mech->GetSegmentToWorld(*seg, &mw); fxPos = mw; // Point3D = matrix translation break; } @@ -8912,9 +8920,12 @@ void } if (s_portCache[energyOrdinal] != 0) { - AffineMatrix mw; - mw.Multiply(s_portCache[energyOrdinal]->GetSegmentToEntity(), - localToWorld); + // #141 sweep: engine accessor (== the binary's FUN_00424da8). + // This is the BEAM muzzle -- the same stale-cache exposure the + // missile launch had, so a peer's beam would also originate + // from the bind-pose gun port instead of the twisted torso. + LinearMatrix mw; + GetSegmentToWorld(*s_portCache[energyOrdinal], &mw); mz = mw; // Point3D = matrix translation } }