#52 SKATE detector (ungated field forensic) -- negative-verified; local repro eludes

The night-12 field logs eliminated record starvation (zero [ghost] during
three observed skating windows), so the bug lives in gait APPLICATION on
peers.  This adds the [skate] detector to the death-handler tick: a
replicant moving >0.08 u/frame for 90+ frames with BOTH animation
channels idle (legCycleSpeed + bodyCycleSpeed ~ 0) logs one line per
episode + a SKATE matchlog record carrying the discriminating inputs
(legCyc/bodyCyc/cmdSpd/destroyed/mode).

Honest history: the first build keyed on legCycleSpeed alone and
false-fired on every healthy movement phase -- the current peer
architecture poses joints from the BODY channel (s_peerLegCh=0,
AdvanceBodyAnimation mj=1), so legCycleSpeed==0 is NORMAL there.  Caught
same-session by the [gimpfeed] silence (AdvanceLegAnimation never runs
on peers); corrected to channel-agnostic before anything shipped.

Bench (skatebench2.sh, 2-node, autodrive walker + kill every ~40s):
7 death/respawn cycles, ZERO skate hits either side -- no false fires,
and light local conditions do NOT reproduce the field skating.  Next
provocations: leg-GIMPED walker (the #82 family transition) and 6-player
load; otherwise the detector rides the next cut and the field names the
failing case for us.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-05 12:41:55 -05:00
co-authored by Claude Fable 5
parent 6d7946a264
commit 9604f4c492
4 changed files with 137 additions and 1 deletions
+60 -1
View File
@@ -1206,7 +1206,8 @@ void
}
MechDeathHandler::MechDeathHandler(Mech *mech) // @0042a984
: owner(mech), prevMode(0), ghostFrames(0), ghostLogged(0)
: owner(mech), prevMode(0), ghostFrames(0), ghostLogged(0),
skatePrevX(0.0f), skatePrevZ(0.0f), skateFrames(0), skateLogged(0)
{
// per-zone last-damage cache, zeroed (binary this[0x10], size mech+0x11c).
int count = (mech != 0 && mech->damageZoneCount > 0) ? mech->damageZoneCount : 0;
@@ -1229,6 +1230,64 @@ void
if (owner == 0)
return;
// #52 SKATE DETECTOR (PORT, ungated -- the night-12 forensic): a REPLICANT
// whose position advances while its LEG CHANNEL is not cycling is the
// field "skating" signature. One line per episode, carrying the three
// inputs that split the failure: bodyTargetSpeed (the replicated commanded
// speed the leg SM feeds from -- ~0 means the RECORD side starved the
// gait), legCycleSpeed (~0 with a live demand means the SM is STUCK), and
// IsMechDestroyed (1 means the gait feed block was SKIPPED -- the stale
// post-respawn flag suspect). ~90 frames of sustained skate to trip; the
// threshold 0.08 u/frame ~ 2.4 u/s at 30 fps, well above reckoner jitter.
if (owner->GetInstance() == Entity::ReplicantInstance)
{
const float px = (float)owner->localOrigin.linearPosition.x;
const float pz = (float)owner->localOrigin.linearPosition.z;
const float dx = px - skatePrevX, dz = pz - skatePrevZ;
const float step = sqrtf(dx * dx + dz * dz);
skatePrevX = px; skatePrevZ = pz;
// CHANNEL-AGNOSTIC (corrected same-day): the peer's joints are posed by
// the BODY channel in the current authentic architecture (mech4
// s_peerLegCh=0 -> AdvanceBodyAnimation mj=1), so legCycleSpeed==0 is
// NORMAL there -- the first detector build false-fired on every healthy
// movement phase. Skating = moving with BOTH channels idle.
const float cyc = fabsf((float)owner->legCycleSpeed)
+ fabsf((float)owner->bodyCycleSpeed);
const int movingNoLegs = (step > 0.08f && step < 5.0f // 5+: teleport/warp
&& cyc < 0.05f) ? 1 : 0;
if (movingNoLegs)
{
if (++skateFrames > 90 && !skateLogged)
{
skateLogged = 1;
DEBUG_STREAM << "[skate] replicant " << owner->GetEntityID()
<< " SKATING: " << skateFrames << " frames moving ("
<< step << " u/frame) with legCyc="
<< (float)owner->legCycleSpeed
<< " bodyCyc=" << (float)owner->bodyCycleSpeed
<< " bodyTargetSpeed=" << (float)owner->bodyTargetSpeed
<< " destroyed=" << (int)owner->IsMechDestroyed()
<< " mode=" << (int)owner->MovementMode()
<< " at (" << px << "," << pz << ")\n" << std::flush;
if (BTMatchLogActive())
BTMatchLog("SKATE", "mech=%d:%d frames=%d step=%.3f cyc=%.3f "
"cmdSpd=%.2f destroyed=%d mode=%d x=%.1f z=%.1f",
BTMatchHostOf(owner->GetEntityID()), (int)owner->GetEntityID(),
skateFrames, step, cyc,
(float)owner->bodyTargetSpeed,
(int)owner->IsMechDestroyed(), (int)owner->MovementMode(),
px, pz);
}
}
else if (skateFrames > 0)
{
if (skateLogged)
DEBUG_STREAM << "[skate] replicant " << owner->GetEntityID()
<< " recovered after " << skateFrames << " frames\n" << std::flush;
skateFrames = 0; skateLogged = 0;
}
}
// #108 GHOST DETECTOR (PORT, ungated -- the night-9 forensic): a visible
// REPLICANT that stops receiving update records is exactly what "everyone
// shoots thin air" looks like from the other side. ReadUpdateRecord