the #35-class sweep + the games-night load crash: four lifecycle-window null-derefs guarded

THE CLASS (established by Gitea #35's 515 fix, confirmed growing tonight):
code the binary could run bare because a pod's mission world always existed,
crashing in the port's MP join/teardown/respawn windows where mission, player,
or a roster link is briefly NULL.  Swept every GetMissionPlayer()/
GetPlayerVehicle()/GetCurrentMission() chain in the tree (23+7 sites).

GUARDED (the 515 pattern -- authentic behavior whenever the object exists,
skip/degrade + loud log when mid-teardown):

1. DPLRenderer::SortAndReloadNameBitmaps -- runs in the round-STOP path (the
   end-of-round circle), raw mission+player+entity-manager derefs.
2. DPLRenderer::LoadOrdinalBitmaps -- called from (1); raw
   GetMissionPlayer()->GetInstance() evaluated on EVERY machine (the camera-
   director test derefs before it branches).
3. DPLRenderer::LoadNameBitmaps -- same window, raw GetPlayerCount() chain.
4. Reservoir::Reservoir master-gate block -- THE CAPTURED GAMES-NIGHT CRASH
   ([crash] btl4+0x4990d = this ctor inlined into CreateReservoirSubsystem,
   AV reading NULL+0x1d0):  linkedSinks.Resolve() derefed raw; +0x1d0 is
   exactly the masterScale FILD of the resolved master heat-sink bank
   (heatSinkCount @0x1D0 -- the offset the crash named).  Observed trigger: a
   DUPLICATE-PILOT egg (the seat-ghost bug put one identity in two seats)
   poisoning the ID registry, so Resolve() nulled during viewpoint-entity
   construction and at least two pods died loading the same egg.  Now: loud
   '[spawn] FATAL-AVOIDED' + degrade to the inactive-copy shape.  The
   duplicate-pilot ROOT CAUSE is the seat-ghost fix (tracked separately);
   this guard makes the failure survivable and self-identifying either way.

CLASSIFIED SAFE, not touched: btl4mppr/btplayer/mechmppr/btl4gau3 chains
(guarded or null-tolerant by design); APP.cpp's five Check(player) sites
(mission state machine -- player exists by construction, original engine
ordering); CAMMGR/CULTURAL GetCurrentMission chains (construction-time).

VERIFIED: build clean (0 errors, no new unresolved externs); solo smoke on the
guarded exe -- zero FATAL-AVOIDED lines (guards silent on the healthy path),
subsystems simulating, no faults.  STILL OWED: a full launch->stop rig cycle
(exercises the guarded round-stop path live) and the duplicate-pilot-egg repro
-- both blocked on port 1500 while the operator's console session is up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-27 00:03:31 -05:00
co-authored by Claude Opus 5
parent c7dcdf26eb
commit e28dcbc161
2 changed files with 55 additions and 1 deletions
+31
View File
@@ -9963,6 +9963,22 @@ void
void DPLRenderer::SortAndReloadNameBitmaps()
{
//
// NULL-GUARD (2026-07-26, the #35-class sweep). This resort runs in the
// round-STOP path (the end-of-round "circle") and derefs the current
// mission and the mission player raw -- the binary could, because a pod's
// mission world always existed when it ran. The port's MP stop/teardown
// windows (double-stop, abort-during-stop) can leave either briefly NULL
// -- the exact shape of the 515 audio-clip crash. The refresh is
// cosmetic; skip it when the world is mid-teardown.
//
if (application == 0
|| application->GetCurrentMission() == 0
|| application->GetMissionPlayer() == 0
|| application->GetEntityManager() == 0)
{
return;
}
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Get the Entity Group of Players
@@ -9993,6 +10009,15 @@ void DPLRenderer::SortAndReloadNameBitmaps()
//
void DPLRenderer::LoadOrdinalBitmaps()
{
// NULL-GUARD (2026-07-26, the #35-class sweep): runs from the round-stop
// resort above; player/mission can be mid-teardown NULL in the port's MP
// windows. Ordinals are the camera-director's cosmetic overlay -- skip.
if (application == 0
|| application->GetMissionPlayer() == 0
|| application->GetCurrentMission() == 0)
{
return;
}
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Check if we have a Director on this machine,
@@ -10062,6 +10087,12 @@ unsigned int*
//
void DPLRenderer::LoadNameBitmaps()
{
// NULL-GUARD (2026-07-26, the #35-class sweep): same teardown window as
// SortAndReloadNameBitmaps above.
if (application == 0 || application->GetCurrentMission() == 0)
{
return;
}
int player_count = application->GetCurrentMission()->GetPlayerCount();
for (int ii=0; ii<player_count; ++ii)
{