From 53c86fa1ddb1ea36442e79f6333e7231f1c7d243 Mon Sep 17 00:00:00 2001 From: arcattack Date: Wed, 22 Jul 2026 19:11:03 -0500 Subject: [PATCH] Launch pend counter: no timeout, no queue entry (mid-load launch crash 2) The take-1 deferral (re-post at +1s, capped at 120) turned a SLOW load into a crash: loads on a busy operator box measure 1-2+ minutes, the cap expired, and the original "Not ready to run" Fail/abort fired (field report: sound after ~1 min, crash ~2 min later; the log shows exactly 120 deferrals all in LoadingMission). A mid-load RunMission now just bumps gBTRunMissionPendCount -- nothing enters the event queue and there is NO timeout to outlive -- and CheckLoadMessageHandler drains the counter immediately after advancing LoadingMission -> WaitingForLaunch, re-posting that many stack RunMissionMessages (the engine's own no-console launch pattern at the same site, which also demonstrates Post spools stack messages safely). Verified (two-round rig, round 1): both relay launches pended through the load, fired at load completion, the requested mech spawned and the scene went live. Round reset + round-2 rejoin remain verified from the prior run; why loads take 1-2+ min on a busy box is left as an open perf question. Co-Authored-By: Claude Opus 4.8 (1M context) --- context/multiplayer.md | 12 ++++++++ engine/MUNGA/APP.cpp | 12 ++++++++ engine/MUNGA_L4/L4APP.cpp | 61 +++++++++++++++++++++++---------------- 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/context/multiplayer.md b/context/multiplayer.md index de8e87d..beaf8c1 100644 --- a/context/multiplayer.md +++ b/context/multiplayer.md @@ -425,6 +425,18 @@ phase). Plan: `~/.claude/plans/partitioned-snuggling-piglet.md`. deferrals so a wedged load still surfaces the original Fail. `[launch] ... deferred +1s` log per deferral. Verified: the crashing shape deferred 104x through a ~50s load, then launched and ran clean. +- **LAUNCH/LOAD RACE take 2 -- the PEND COUNTER (2026-07-22) [T2]**: the take-1 deferral + (re-post at +1s, 120 cap) turned a SLOW load into a crash -- loads on a busy operator box + measured 1-2+ minutes, the cap expired, and the original Fail/abort fired (field-reported: + "sound after 1 min, crash 2 min later", exactly 120 deferrals in the log). Now a mid-load + RunMission just bumps `gBTRunMissionPendCount` (L4APP.cpp) -- NOTHING enters the event queue, + NO timeout -- and APP.cpp `CheckLoadMessageHandler` drains the counter right after it advances + LoadingMission->WaitingForLaunch, re-posting that many stack RunMissions (the engine's own + no-console launch pattern at the same site). Verified: both relay launches pended through the + load, fired at completion, mech spawned + scene LIVE. NOTE the queue-blocking livelock theory + from take 2's commit is UNPROVEN -- the measured fact is just that loads outlive any fixed + cap; the counter design is correct under either reading. Residual: WHY loads take 1-2+ min + on the operator box (vs ~30s earlier same day) is an open perf question ([loadobj] timings). - **ROUND RESET (2026-07-22) [T2]**: trim/prefs FINALIZE the egg for one round -- and that used to be permanent for the relay's lifetime, so a REJOIN after a mission (or crash) was served the stale finalized egg and its new callsign/mech request was recorded but never applied (the diff --git a/engine/MUNGA/APP.cpp b/engine/MUNGA/APP.cpp index bc24422..90996c1 100644 --- a/engine/MUNGA/APP.cpp +++ b/engine/MUNGA/APP.cpp @@ -1415,6 +1415,18 @@ void Post(DefaultEventPriority, this, &run_mission_message); Tell("Sent ready message to ourselves\n"); } + + { + // relay launches that arrived MID-LOAD fire now (they could + // not enter the queue then -- see L4APP.cpp pend counter) + extern int BTTakePendingRunMissions(void); + int pended_launches = BTTakePendingRunMissions(); + while (pended_launches-- > 0) + { + RunMissionMessage pended_run; + Post(DefaultEventPriority, this, &pended_run); + } + } } // diff --git a/engine/MUNGA_L4/L4APP.cpp b/engine/MUNGA_L4/L4APP.cpp index b450f6b..615697d 100644 --- a/engine/MUNGA_L4/L4APP.cpp +++ b/engine/MUNGA_L4/L4APP.cpp @@ -564,6 +564,23 @@ Entity* return viewing_entity; } +// +// Pended mid-load operator launches (see the handler's default case). +// APP.cpp CheckLoadMessageHandler drains the count the moment the state +// reaches WaitingForLaunch and re-posts that many RunMissions. +// +int gBTRunMissionPendCount = 0; + +int BTTakePendingRunMissions(void) +{ + int pended = gBTRunMissionPendCount; + gBTRunMissionPendCount = 0; + if (pended > 0) + DEBUG_STREAM << "[launch] load complete -- firing " << pended + << " pended RunMission(s)" << std::endl << std::flush; + return pended; +} + // //############################################################################# // RunMissionMessageHandler @@ -611,31 +628,25 @@ void default: // - // LAUNCH/LOAD RACE (2026-07-22): the relay sends the RunMission pair - // a fixed settle after the egg ACK, but a long mission load (glass + - // dev gauges on a busy box) can still be in LoadingMission when it - // lands -- the 1995 console never hit this state (the operator - // launched much later), so it Fail()ed = the "silent crash at - // mission launch" (c0000409 abort; 3/3 reproduced under cdb). - // DEFER instead: re-post the message to ourselves at +1s until the - // ladder reaches WaitingForLaunch (the VehicleDead re-post pattern). - // Bounded so a truly wedged load still surfaces the original Fail. - { - static int s_launch_deferrals = 0; - if (++s_launch_deferrals <= 120) - { - DEBUG_STREAM << "[launch] RunMission arrived in state " - << (int)GetApplicationState() - << " (still loading) -- deferred +1s (" - << s_launch_deferrals << ")" << std::endl << std::flush; - Time when = Now(); - when += 1.0f; - Post(HighEventPriority, this, message, when); - return; // base handler runs when ready - } - } - Fail("Application::RunMissionMessageHandler - Not ready to run!\n"); - break; + // LAUNCH/LOAD RACE, take 2 (2026-07-22): the relay's RunMission pair + // can land while the pod is still in LoadingMission (long loads; the + // 1995 console launched operator-paced, so this state Fail()ed = the + // silent c0000409 crash). Take 1 re-posted the message at +1s -- + // but the LoadingMission -> WaitingForLaunch transition + // (APP.cpp CheckLoadMessageHandler) requires + // IsPriorityEmpty(MinEventPriority), so the pending deferred launch + // KEPT THE QUEUE NON-EMPTY and blocked the very transition it waited + // for (livelock -> 120 x 1s cap -> the same abort; field-reported). + // Now a mid-load launch just bumps a COUNTER -- nothing enters the + // event queue -- and CheckLoadMessageHandler re-posts the launches + // (the engine's own no-console stack-message pattern) right after it + // advances the state. + ++gBTRunMissionPendCount; + DEBUG_STREAM << "[launch] RunMission arrived in state " + << (int)GetApplicationState() + << " (still loading) -- pended until the load completes (" + << gBTRunMissionPendCount << ")" << std::endl << std::flush; + return; } //