#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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018SgmXGNMXavXiafKXf9MDC
This commit is contained in:
Joe DiPrima
2026-08-08 09:28:04 -05:00
co-authored by Claude Opus 5
parent f01de8cbfa
commit e6c5ac951e
+18 -7
View File
@@ -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
}
}