LocalConsole: the in-process marshal that ends missions

Domain correction from playtest: hand-fed eggs are a developer shortcut
- a mission only ends on a console command, so the clock hits 00:00 and
counts up forever. Even single-player games need a console marshal.

RPL4CONSOLE is that console. Like the real one it lives on its own
thread: it owns the mission clock and raises the stop request at the
selected length; the app-manager per-frame hook (new gPerFrameHook seam
in APPMGR, called while the application global is live - the loop
condition NULLs it on exit, which ate the first attempt) executes the
engine-safe part, dispatching the same StopMissionMessage TeslaConsole
sent. Final scores flow in through a new RP-layer sink
(gConsoleScoreSink in RPCNSL): RPPlayer feeds it the same score it
sends a real console at mission end.

It also inherits the launcher role: the application tears down after a
stop (arcade pods were relaunched per mission by TeslaLauncher), so
WinMain respawns the process when the console ended the mission,
landing back on the race-setup screen. L4NetworkManager grows
FeedLocalEgg (the single-user egg-inject path, callable mid-session)
for the future in-process loop.

Verified end to end: menu -> 3:00 race -> stop dispatched exactly on
time -> final score collected (host 1 = 4113) -> process respawned with
the front end up. -egg runs stay unmarshaled (the dev shortcut).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-12 18:10:02 -05:00
co-authored by Claude Fable 5
parent 58eb25a792
commit 9f79508257
15 changed files with 351 additions and 0 deletions
+15
View File
@@ -5,6 +5,10 @@
HWND ghWnd = 0;
// per-frame observer slot (see RunMissions); the game layer registers
// the single-player local-console marshal here
void (*gPerFrameHook)() = NULL;
ApplicationManager* ApplicationManager::CurrentAppManager = NULL;
ApplicationManager::ApplicationManager(HINSTANCE hInstance, HWND hWnd, Scalar frame_rate) : Node(ApplicationManagerClassID), runningApplications(this)
@@ -74,6 +78,17 @@ Start_Of_Frame:
{
Check(application);
foregroundTasksRun++;
//------------------------------------------------------------------
// Give the per-frame observer a slice while 'application' is live
// (the loop condition NULLs the global on exit). The single-player
// local-console marshal hangs off this; NULL when unregistered.
//------------------------------------------------------------------
if (gPerFrameHook != NULL)
{
(*gPerFrameHook)();
}
if (!application->ExecuteForeground(end_of_frame, frameDuration))
{
if (!application->Shutdown(current_application.GetSize()))
+4
View File
@@ -4,6 +4,10 @@
extern HWND ghWnd;
// per-frame observer called once per RunMissions frame, after the
// foreground pass (the single-player local-console marshal uses this)
extern void (*gPerFrameHook)();
class ApplicationManager : public Node
{
public: