Hosted races stage properly: the owner no longer launches itself

Round five reached the race - full mesh on all three machines, eggs,
ACKs, mission running - but the owner raced ALONE. Cause: the engine
self-runs a pod at WaitingForLaunch when its console host is not
online (the arcade no-console fallback), and the owner''s in-process
console never connects to its own pod. On fast-loading owners the
self-run beat the console''s staging gate, so RunMission was never
sent and the members sat staged at black screens until someone hit
the & emergency-abort key.

gConsoleMarshalsLaunch (APPMGR) now tells the engine an in-process
console owns the launch: the network-race install sets it and the
owner holds at WaitingForLaunch with everyone else; plain single
player leaves it False and auto-runs as always. Verified on loopback:
all pods staged - RUN is back in the hosted-race log and both the
hosted race and the single-player cycle pass.

Also: unhandled-exception minidumps (rpl4crash.dmp beside the exe,
dbghelp loaded lazily) so test-machine crashes hand back stacks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-12 23:40:04 -05:00
co-authored by Claude Fable 5
parent 7ad53e03b5
commit 2d5057c528
5 changed files with 64 additions and 4 deletions
+8 -1
View File
@@ -1386,15 +1386,22 @@ void
console_host = GetHostManager()->GetConsoleHost();
if (
!gConsoleMarshalsLaunch &&
(
(console_host == NULL) ||
(
console_host != NULL &&
console_host->GetConnectStatus() != Host::OnLineConnectionStatus
)
)
)
{
//
// In the absence of the console just post the message to run
// In the absence of the console just post the message to
// run. An IN-PROCESS console (hosted network race) has no
// connection to this pod but still owns the launch - the
// gConsoleMarshalsLaunch flag holds us at WaitingForLaunch
// until every pod in the mesh is staged.
//
RunMissionMessage run_mission_message;
Post(DefaultEventPriority, this, &run_mission_message);
+3
View File
@@ -9,6 +9,9 @@ HWND ghWnd = 0;
// the single-player local-console marshal here
void (*gPerFrameHook)() = NULL;
// an in-process console owns the launch (hosted network races)
Logical gConsoleMarshalsLaunch = False;
ApplicationManager* ApplicationManager::CurrentAppManager = NULL;
ApplicationManager::ApplicationManager(HINSTANCE hInstance, HWND hWnd, Scalar frame_rate) : Node(ApplicationManagerClassID), runningApplications(this)
+6
View File
@@ -8,6 +8,12 @@ extern HWND ghWnd;
// foreground pass (the single-player local-console marshal uses this)
extern void (*gPerFrameHook)();
// True when an in-process console marshals the launch: suppresses the
// no-console self-run in CheckLoadMessageHandler so a hosted network
// race stages at WaitingForLaunch until every pod is ready. Plain
// single player leaves it False and auto-runs exactly as always.
extern Logical gConsoleMarshalsLaunch;
class ApplicationManager : public Node
{
public:
+38
View File
@@ -68,6 +68,42 @@ const char* const ProgName = "rpl4";
Application *rpl4App;
HWND hWnd;
//
// Unhandled-exception minidump: rpl4crash.dmp lands beside the exe so
// a crash on a test machine hands back a stack. dbghelp loads lazily -
// no link-time dependency.
//
static LONG WINAPI RPL4CrashDumpFilter(EXCEPTION_POINTERS *exception)
{
HMODULE dbghelp = LoadLibraryA("dbghelp.dll");
if (dbghelp != NULL)
{
typedef BOOL (WINAPI *MiniDumpWriteDumpFn)(
HANDLE, DWORD, HANDLE, MINIDUMP_TYPE,
PMINIDUMP_EXCEPTION_INFORMATION,
PMINIDUMP_USER_STREAM_INFORMATION,
PMINIDUMP_CALLBACK_INFORMATION);
MiniDumpWriteDumpFn write_dump =
(MiniDumpWriteDumpFn) GetProcAddress(dbghelp, "MiniDumpWriteDump");
if (write_dump != NULL)
{
HANDLE file = CreateFileA("rpl4crash.dmp", GENERIC_WRITE, 0,
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (file != INVALID_HANDLE_VALUE)
{
MINIDUMP_EXCEPTION_INFORMATION dump_info;
dump_info.ThreadId = GetCurrentThreadId();
dump_info.ExceptionPointers = exception;
dump_info.ClientPointers = FALSE;
write_dump(GetCurrentProcess(), GetCurrentProcessId(), file,
MiniDumpNormal, &dump_info, NULL, NULL);
CloseHandle(file);
}
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
@@ -91,6 +127,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
logfile.open(filename);
std::cout.rdbuf(logfile.rdbuf());
SetUnhandledExceptionFilter(RPL4CrashDumpFilter);
// load up our environment variables
//controls
if(getenv("L4CONTROLS") == NULL)
+6
View File
@@ -531,6 +531,8 @@ void
gNetworkRace = False;
gRemotePodCount = 0;
gPilotNameCount = 0;
// single player launches itself (the engine's no-console self-run)
gConsoleMarshalsLaunch = False;
InstallCommon(mission_seconds);
}
@@ -546,6 +548,10 @@ Logical
gRemotePodCount = 0;
gPilotNameCount = 0;
// the owner pod must stage at WaitingForLaunch with everyone else -
// this console launches the whole mesh at once
gConsoleMarshalsLaunch = True;
strncpy(gEggPath, egg_path, sizeof(gEggPath) - 1);
gEggPath[sizeof(gEggPath) - 1] = '\0';