diff --git a/scratchpad/night13/close148.py b/scratchpad/night13/close148.py new file mode 100644 index 0000000..fed7b16 --- /dev/null +++ b/scratchpad/night13/close148.py @@ -0,0 +1,81 @@ +"""Night 13: close #148 as not-a-bug with the measured chain. ASCII only.""" +import sys +sys.path.insert(0, r"C:\git\bt411\scratchpad\night7") +import gitea + +BODY = """**NOT A BUG -- closing (2026-08-08).** There was nothing to fix here. The bench was lying, +and this ticket's own premise was wrong twice over. + +## The answer + +`Entity::Execute` (`ENTITY.cpp:556`, real engine source [T0]) calls `PerformAndWatch` **only** when + +```cpp +application->GetApplicationState() == Application::RunningMission + || application->GetApplicationState() == Application::EndingMission + || IsPreRunnable() +``` + +and otherwise merely `WriteSimulationUpdate()`s. `Entity::DefaultFlags` is +`DynamicFlag|MasterInstance` -- **no `PreRunFlag`**. Only `Player` and `Director` add it in their +DefaultFlags, 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, no matter how +much correctly-replicated data is arriving for it. Measured on the observer node: + +``` + 235 [perf-first] mech 3:161 master <- own mech, immediately + 402 [torso-rec-rx] <- peer's torso records start 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 it does. + +## So the symptom was a BENCH ARTIFACT + +`BT_AUTOFIRE` starts shooting immediately, during `WaitingForLaunch` -- something no player can do +in a real match. Those leading salvos measured a peer whose torso, gait and subsystems had never +run. Every `ZZZZ...XXXX` prefix in this investigation was that, and the first `X` lands within a +few lines of the state transition. + +**#141 is unaffected and stays fixed** -- its segment-cache defect was real and mid-match. + +## Ruled out along the way (all measured, all recorded so nobody repeats them) + +* **The record cadence is authentic.** My "only 13 records in 5 minutes" premise was wrong. The + payloads are the sweep EXTREMES with `rate` flipping sign at each one -- the master sends on + **rate change** and the peer dead-reckons `atUpd + rate * elapsed` between them. 12 records for + 12 direction reversals is correct, not starved. +* **The `ComputeTargetTwist` clamp.** The copy's limits load correctly (`limL=2.44346 + limR=-2.44346 enab=1`), so `Min/Max` was not pinning `targetTwist` to zero. +* **The torso's own executable flag.** `Entity::Perform` picks its predicate by instance + (`IsNonReplicantExecutable` vs `IsReplicantExecutable`, differing on + `|| lastUpdate >= lastPerformance`), and Mech's tick loop had dropped that branch. Restoring it + (`f36f013`) is a genuine fidelity fix and is kept -- but it moved this bug by nothing. +* **The scheduler.** The replicant entity IS offered to the performer with `executable=1` from + line 171, ~2500 lines before its first `PerformAndWatch`. The gate was inside `Execute`, not in + who gets offered. + +## What came out of it + +* `[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 exactly why this took + so long to see in a 2-node log. +* `[torso-copy]` now prints `limL/limR/enab`; `[launchframe]` prints the shooter's live torso twist. +* **Gotcha #29** in `context/reconstruction-gotchas.md`: judge a 2-node bench by PREFIX vs + INTERLEAVED, never by raw percentage; check `[ent-exec] state=` before suspecting replication. + `missileframe.sh` carries the same warning inline. + +## Still worth checking separately + +`#37` (MadCat torso BACKWARDS) and `#70` (torso twist stops after respawn) were flagged here as +possibly sharing a cause. They do **not** share this one -- it is not a defect. They should be +re-tested against the #141 segment-cache fix instead, which is a real mid-match change.""" + +gitea.comment(148, BODY) +gitea.call("/issues/148", method="PATCH", payload={"state": "closed"}) +print("closed #148")