#82 peers see a limping mech SKATING: the mech erased its own gimp cell every frame

The binary's one mech+0x40 IS Simulation::simulationState, which rides EVERY
update record header -- so in 1995 a peer's replicant learned the gimp level on
every packet and its gait limped with no gimp-specific replication anywhere.
The port's Mech::PerformAndWatch wrote SetMovementMode(1) every frame ("ground,
non-death, non-airborne"), which erased the level once it was mirrored in for
the warning voice (#78): the wire carried 1, bystanders walked while sliding at
limp speed, and the voice sequence restarted on every damage event (1->4 edge
per tick) instead of announcing once.

- mech4: that per-frame write now writes the AUTHORITATIVE level
  (gimped ? 3/4 : 1) via a new alarm-only bridge BTMechGimpAlarmLevel -- which
  deliberately never consults the cell it feeds, so a respawn-cleared alarm
  cannot re-latch stale gimp out of it.
- BTMechGimpLevel: falls back to the replicated cell, with ONE-CELL precedence
  (a fall/death/limbo state wins, so the normal drivers and their death latch
  run -- what the binary's single cell enforced structurally).
- Reverted the #78 record guard: on a replicant the master's records are
  authoritative, so pinning 3/4 against them would keep a peer limping through
  a respawn.  Fixed at the writer instead.
- [simstomp] trap now scoped to the watched mech with a module-relative return
  address (symcrash-able) -- that is what named the writer.
- Also learned + recorded: zone damage levels replicate only when the EXPLOSION
  TABLE's tier is crossed (peer measured at 0.428 vs master 0.857), so peer-side
  damage state must never be inferred from them.

Verified two-node (scratchpad/night6/mp_skate.sh): the observer's replicant gets
sim=4 and its gait runs 23 -> 25 (wgr entry -> ggl limp cycle).  Field clue that
cracked it: "after respawning a peer DID see the limp" (epilectrik/SAURON).
KB: locomotion.md + gotcha #25.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-07-29 22:29:03 -05:00
co-authored by Claude Fable 5
parent 98907f45af
commit e1c15eb806
6 changed files with 180 additions and 35 deletions
+64 -1
View File
@@ -71,6 +71,25 @@
#endif
#include <stdio.h> // sscanf (offline streamer)
// #78/#82 diagnostic scope: the mech whose GIMP cell the [simstomp] trap in
// SIMULATE.h should watch (set at each leg-threshold crossing). Definition
// lives here so the engine header can extern it without a game dependency.
void *g_btGimpWatchMech = 0;
// The trap body (SIMULATE.h calls it): report WHO reset the gimp cell, with a
// module-relative return address for tools/symcrash.py.
void BTGimpStompTrap(void *sim, unsigned cur, unsigned nw, void *ra)
{
static int s_ds = 0;
if (s_ds++ >= 40)
return;
unsigned long base = (unsigned long)GetModuleHandleA(0);
char buf[160];
sprintf(buf, "[simstomp] %u->%u mech=%p ra=btl4+0x%lx",
cur, nw, sim, (unsigned long)ra - base);
DEBUG_STREAM << buf << std::endl << std::flush;
}
#if !defined(RANDOM_HPP)
# include <random.hpp> // the engine's global RandomGenerator Random
#endif
@@ -502,6 +521,7 @@ void
//
if (damageLevel >= LegHalfStructure && (rightLeg != 0 || leftLeg != 0))
{
g_btGimpWatchMech = (void *)mech; // scope the #78 simstomp trap
unsigned ss = mech->GetSimulationState();
if (getenv("BT_AUDIO_SPATIAL")) { static int s_gm=0; if (s_gm++<30)
DEBUG_STREAM << "[gimp-sim] ss=" << ss << " -> "
@@ -624,13 +644,56 @@ void
// that typedef split is audited and unified, every cross-TU read of the
// alarm goes through here.
//
int BTMechGimpLevel(void *mech_v)
// #82 REPLICANTS (field: "peers see a limping mech SKATING", 2026-07-29): the
// binary's mech+0x40 is ONE cell -- and because that cell IS
// Simulation::simulationState, it rides EVERY update record header
// (Simulation::Write/ReadUpdateRecord), so a peer's replicant learned the gimp
// level for free and its gait machine limped with no extra plumbing.
//
// The port split the cell (graphicAlarm = where mechdmg writes; engine
// simulationState = what replicates). A replicant NEVER re-derives the level:
// zone damage reaches it as a DamageZone update record
// (Entity::ReadDamageUpdateRecord -> DamageZone::ReadUpdateRecord writes
// damageLevel straight from the wire), which never runs
// Mech__DamageZone::TakeDamage -- so the threshold evaluation, and with it
// graphicAlarm 3/4, only ever happens on the damage-resolving node. Its
// self-simulated gait therefore stayed in the WALK states while the replicated
// speed dragged it along at limp pace: the skate.
//
// So read the cell the way the binary had it: the master's authoritative
// graphicAlarm, else the replicated engine cell (which the master now mirrors
// for the warning voice, #78 -- the same value the binary put on the wire).
// Mech::StateCount is 0x21 and the engine base only defines 0/1 there, so 3/4
// are unambiguously the mech's gimp levels (the authored audio watchers on
// SimulationState 3/4 are the binary's own confirmation).
int BTMechGimpAlarmLevel(void *mech_v)
{
if (mech_v == 0)
return 0;
return (int)((Mech *)mech_v)->graphicAlarm.GetLevel();
}
int BTMechGimpLevel(void *mech_v)
{
if (mech_v == 0)
return 0;
Mech *m = (Mech *)mech_v;
const int alarm = (int)m->graphicAlarm.GetLevel();
const int sim = (int)m->GetSimulationState();
// ONE-CELL PRECEDENCE: in the binary a fall / death / limbo state OVERWROTE
// the gimp level in the same cell, so those gaits win and the NORMAL
// drivers (which carry the death latch) must run. Report the alarm
// unchanged there -- never gimp.
if (sim == 2 || (sim >= 5 && sim <= 9))
return alarm;
if (alarm == 3 || alarm == 4)
return alarm; // master authority
if (sim == 3 || sim == 4)
return sim; // the replicated cell
return alarm; // 0/9/... unchanged
}
void BTMechVitalSubsystemKill(void *owner_mech)
{
if (owner_mech != 0)