Round reset + device-creation retry + load-time pump (rejoin trilogy)

1. ROUND RESET (the "picked Hellbringer, got a Blackhawk" field bug):
   trim/prefs finalize the session egg for ONE round, and that was
   permanent for the relay's lifetime -- a rejoin after a mission or
   crash got the stale finalized egg and its new callsign/mech request
   was recorded but never applied.  _maybe_reset_round() (both drop
   paths) restores the original egg/roster/launch state once every pod
   is gone; live beacons re-key by tag position (conn.seat_tag).
   Verified two-round e2e: blkhawk mission -> exit -> RESET -> lok1
   request -> egg rewritten -> the Hellbringer spawned.

2. DEVICE-CREATION RETRY: CreateDevice(HARDWARE_VERTEXPROCESSING) fails
   transiently when a just-exited instance still holds the adapter
   (caught live; also the parked "exit 139" join crash).  The old
   double-failure path called PostQuitMessage(1) -- which does NOT stop
   execution -- and the NULL mDevice deref right after was the real
   crash.  Now: HW->SW retry with 500ms backoff and a 20s deadline,
   then a clean error box + ExitProcess.

3. LOAD-TIME PUMP: mission load is thousands of back-to-back
   synchronous model loads; the starved message pump ghosted the window
   "Not Responding" mid-load.  BTLoadPump() (hooked from
   d3d_OBJECT::LoadObject) pumps + repaints the wait screen at most
   every 150ms pre-run; no-op once the scene is live.

Also: round-reset originals captured after the roster parse (the first
cut crashed the relay constructor -- caught by the two-round test).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-22 18:57:50 -05:00
co-authored by Claude Opus 4.8
parent 638cfca18d
commit 3a9b35fc06
4 changed files with 134 additions and 7 deletions
+60 -7
View File
@@ -3474,15 +3474,41 @@ DPLRenderer::DPLRenderer(
// the view; the GPU sat idle, Present <1ms). Try HARDWARE first (any modern
// or pod GPU has fixed-function T&L); fall back to software if it fails, as
// the original error message always intended.
V(gD3D->CreateDevice(*mPrimaryIndex, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &mPresentParams, &mDevice));
if (FAILED(hr))
// DEVICE-CREATION RETRY (2026-07-22): a transient failure is REAL -- a
// just-exited instance can still hold the adapter for a moment (caught
// live: a round-2 pod died here while round-1 tore down). The old code
// had TWO bugs: no retry, and on double failure PostQuitMessage(1) did
// NOT stop execution -- the NULL mDevice deref right below was the
// actual crash (the intermittent "exit 139" / silent join death).
// Retry HW->SW with a short backoff; on true exhaustion exit CLEANLY
// with a visible message.
{
DEBUG_STREAM<<"Couldn't create HARDWARE_VERTEXPROCESSING device."<<std::endl<<std::flush;
V(gD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &mPresentParams, &mDevice));
if (FAILED(hr))
unsigned long device_deadline = GetTickCount() + 20000UL;
for (;;)
{
PostQuitMessage(1);
hr = gD3D->CreateDevice(*mPrimaryIndex, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING, &mPresentParams, &mDevice);
if (SUCCEEDED(hr) && mDevice != NULL)
break;
DEBUG_STREAM << "Couldn't create HARDWARE_VERTEXPROCESSING device (hr=0x"
<< std::hex << (unsigned)hr << std::dec << ")." << std::endl << std::flush;
hr = gD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING, &mPresentParams, &mDevice);
if (SUCCEEDED(hr) && mDevice != NULL)
break;
DEBUG_STREAM << "SOFTWARE_VERTEXPROCESSING failed too (hr=0x"
<< std::hex << (unsigned)hr << std::dec
<< ") -- retrying in 500ms" << std::endl << std::flush;
if (GetTickCount() >= device_deadline)
{
MessageBoxA(NULL,
"Could not create a Direct3D device after 20 seconds.\n\n"
"Another program may be holding the graphics adapter.\n"
"Close other 3D applications and try again.",
"BattleTech -- graphics init failed", MB_OK | MB_ICONERROR);
ExitProcess(1); // CLEAN exit, not the NULL-deref crash
}
Sleep(500);
}
}
@@ -8858,6 +8884,33 @@ void BTWaitScreenPaint(const char *line1, const char *line2)
// with live game frames.
Logical gBTSceneHasPresented = False;
//
// BTLoadPump -- throttled message pump + wait-screen repaint for the LONG
// SYNCHRONOUS stretches of mission load (thousands of small model/texture
// loads back-to-back starve the message pump; the window ghosts "Not
// Responding" and the animation freezes -- user report 2026-07-22). Called
// from the model-load hot path (L4D3D LoadObject); at most one pump per
// 150ms so the load itself is unaffected. Pre-run states only: once the
// scene is live the render loop owns pumping and this is a no-op.
//
void BTLoadPump(void)
{
if (gBTSceneHasPresented)
return;
static unsigned long s_lastPump = 0;
unsigned long now = GetTickCount();
if (now - s_lastPump < 150UL)
return;
s_lastPump = now;
MSG msg;
while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessageA(&msg);
}
BTWaitScreenPaint("LOADING MISSION", "stand by");
}
void DPLRenderer::ExecuteIdle()
{
HRESULT hr;