Front end: post-mission results screen (kills/deaths scoreboard)

Completes the marshal chain: menu -> mission -> timed stop -> results ->
relaunch.  BT had no mission-end score flow; this builds one from the
data the Comm MFD pilotList already tracks.

- btl4console: at the timed stop (game thread, state still live) snapshot
  each roster pilot's kills/deaths via the BTResolveRosterPilot /
  BTPilotKills / BTPilotDeaths bridges; expose BTLocalConsole_ResultCount
  / GetResult.
- btl4fe: BTFrontEnd_ShowResults -- a GDI green-on-black 'MISSION COMPLETE'
  scoreboard (PILOT / KILLS / DEATHS, slot 0 named from the menu), any
  key or ~12s to dismiss; store the last pilot name.
- btl4main: show the scoreboard after a marshaled mission, before the
  relaunch.

Verified: MISSION COMPLETE screen renders with the pilot row and the
snapshotted score (0/0 on a no-combat test run; the data path is the
same one the live Comm MFD reads).

Phase 5 (front end) COMPLETE: menu + egg builder + marshal + timed stop
+ results + single-binary (relaunch) loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-16 22:48:09 -05:00
co-authored by Claude Fable 5
parent 9d660fa50b
commit e74957758b
5 changed files with 161 additions and 3 deletions
+46 -2
View File
@@ -13,6 +13,13 @@
#include "appmgr.hpp" // gPerFrameHook
#include "btl4console.hpp"
// The pilot roster + score bridges (btl4gau3.cpp / mechmppr.cpp / btplayer.cpp):
// the pilotList reads them for the Comm MFD, and the marshal snapshots them at
// the stop so the results screen has the final scores.
extern void *BTResolveRosterPilot(int slot);
extern int BTPilotKills(void *pilot);
extern int BTPilotDeaths(void *pilot);
namespace
{
enum Phase { PhaseIdle, PhaseWaiting, PhaseRunning, PhaseStopped };
@@ -22,6 +29,27 @@ namespace
unsigned long gStartTick = 0;
Application *gWatched = 0;
// Final scores snapshotted at the stop (roster order; slot 0 = local).
enum { maxResults = 8 };
struct Result { int kills, deaths; };
Result gResults[maxResults];
int gResultCount = 0;
void SnapshotResults()
{
gResultCount = 0;
for (int slot = 0; slot < maxResults; ++slot)
{
void *pilot = BTResolveRosterPilot(slot);
if (pilot == 0)
break; // past the last occupied roster slot
gResults[gResultCount].kills = BTPilotKills(pilot);
gResults[gResultCount].deaths = BTPilotDeaths(pilot);
++gResultCount;
}
DEBUG_STREAM << "[marshal] snapshot " << gResultCount << " pilot score(s)\n" << std::flush;
}
//
// The game-thread tick (called every frame while an application is live).
//
@@ -59,9 +87,11 @@ namespace
else if (gMissionSeconds > 0 &&
(GetTickCount() - gStartTick) >= (unsigned long)(gMissionSeconds * 1000))
{
// The console clock expired: stop the race exactly as the
// arcade console did (StopMissionMessage on our own pod).
// The console clock expired: snapshot the final scores (the
// game state is still live here), then stop the race exactly
// as the arcade console did (StopMissionMessage on our pod).
DEBUG_STREAM << "[marshal] time expired -- stopping mission\n" << std::flush;
SnapshotResults();
Application::StopMissionMessage message(0);
application->Dispatch(&message);
gPhase = PhaseStopped;
@@ -92,6 +122,20 @@ int BTLocalConsole_MissionCompleted()
return (gPhase == PhaseStopped) ? 1 : 0;
}
int BTLocalConsole_ResultCount()
{
return gResultCount;
}
int BTLocalConsole_GetResult(int index, int *kills, int *deaths)
{
if (index < 0 || index >= gResultCount)
return 0;
if (kills) *kills = gResults[index].kills;
if (deaths) *deaths = gResults[index].deaths;
return 1;
}
void BTLocalConsole_Uninstall()
{
gPerFrameHook = 0;