#148 is NOT A BUG: a peer mech does not tick before RunningMission -- by engine design

Chased to the bottom instead of stopping.  The answer is that there was
nothing to fix, and my bench was lying to me.

Entity::Execute (ENTITY.cpp:556, real engine source [T0]) calls PerformAndWatch
ONLY when the app state is RunningMission/EndingMission or the entity
IsPreRunnable(); otherwise it merely WriteSimulationUpdate()s.
Entity::DefaultFlags is DynamicFlag|MasterInstance -- no PreRunFlag.  Only
Player and Director add it, and Mech::Reset sets it for a reset MASTER ("a
reset master must tick").  A REPLICANT mech never gets it.

So a peer mech performs ZERO subsystem ticks until the round actually starts,
however much correctly-replicated data is arriving.  Measured on the observer:

     235  [perf-first] mech 3:161 master      <- own mech, immediately
     402  [torso-rec-rx]                      <- peer torso records arriving
    2754  [perf-first] mech 2:55 REPLICANT    <- peer's FIRST performance
    2758  [torso] PushTwist COPY              <- its torso ticks 4 lines later
    2761  [ent-exec] state=5                  <- RunningMission

The peer starts performing exactly at the RunningMission transition.  That is
the engine doing what it says.

WHICH MEANS THE PREFIX WAS A BENCH ARTIFACT.  BT_AUTOFIRE starts shooting
immediately, during WaitingForLaunch -- something no player can do in a real
match -- so those 60 leading salvos measured a peer whose torso had never run.
Every "ZZZZ...XXXX" pattern in this investigation was that, and the first X
lands within a few lines of the state transition.  #141's fix is unaffected and
remains verified: the segment-cache defect was real and mid-match.

Chain of things ruled out on the way, all measured:
  * record CADENCE is authentic -- sends on RATE CHANGE (payloads are the sweep
    extremes, rate flips sign), peer dead-reckons between them.  12 records for
    12 reversals is correct, not starved.  My "only 13 records" premise was wrong.
  * ComputeTargetTwist clamp -- limits load fine on the copy (+/-2.44346).
  * the torso's own executable flag -- restoring the engine's instance branch
    (f36f013) is a genuine fidelity fix but moved this by nothing.
  * the replicant entity IS offered to the performer, executable=1, from line
    171 -- 2500 lines before its first PerformAndWatch.  The gate was inside
    Execute, not the scheduler.

Adds [perf-first]: a ONE-SHOT per-mech receipt naming entity ID + instance at a
mech's first performance.  Every other per-frame receipt in mech4 is anonymous,
which is precisely why this took so long in a 2-node log -- master and replicant
lines were indistinguishable.  Name the mech.

Gotcha #29 records the bench-design rule this cost: judge a 2-node bench by
PREFIX vs INTERLEAVED, never by raw percentage, and check [ent-exec] state=
before suspecting replication.  missileframe.sh carries the same warning.

#148 to be closed as not-a-bug.

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 10:11:14 -05:00
co-authored by Claude Opus 5
parent bb6605d53b
commit cacca58836
4 changed files with 192 additions and 0 deletions
+21
View File
@@ -2704,6 +2704,27 @@ volatile float gBTReplRenderYaw = -999.0f;
void
Mech::PerformAndWatch(const Time& till, MemoryStream *update_stream)
{
// #148 probe: one-shot per mech, the FIRST time this mech's per-frame
// performance runs. Every other receipt in this file is anonymous, so
// master and replicant lines are indistinguishable in a 2-node log -- which
// is exactly what made the "when does the peer torso start ticking?" search
// go in circles. Name the mech.
if (getenv("BT_NET_TRACE"))
{
static const Mech *s_seen[16]; static int s_seenN = 0;
int known = 0;
for (int si = 0; si < s_seenN; ++si) if (s_seen[si] == this) { known = 1; break; }
if (!known && s_seenN < 16)
{
s_seen[s_seenN++] = this;
DEBUG_STREAM << "[perf-first] mech " << GetEntityID()
<< " instance=" << (GetInstance() == Entity::ReplicantInstance
? "REPLICANT" : "master")
<< " this=" << (const void *)this
<< " subsysCount=" << subsystemCount << "\n" << std::flush;
}
}
// Frame time slice from the simulation clock (same idiom as Mover::Perform).
Scalar dt = till - lastPerformance;
lastPerformance = till;