Mission review gets far enough to load the race
Starting the MR port, since the pods want it too. Four changes, and one correction of my own making. A release Fail now says what failed. It expanded to a bare abort() that threw the message away, and because the compiler merges identical cold paths, every Fail in a function became one anonymous stub - a crash named the function and nothing else. It now prints message, file and line. It also writes that line to rpl4-fail.log and closes the file. rpl4.log is std::cout with an ofstream's streambuf swapped in, and the text written there did NOT survive the exit however it was flushed; rather than guess where it stopped, the one line that matters goes to its own file, and fclose is a promise it reached the disk. Its own file because rpl4.log is already open for writing and Windows will not share it. -pb plays a spool back without recording a new one. Mode 2 has been in RPL4.CPP all along - one spool file instead of two, no spooling application - with no way to ask for it. -mr starts a recorder alongside the playback and that recorder is the half that cannot cope away from a pod bay. Which is the third fix: L4SpoolingNetworkManager::StartConnecting walks the hosts named in the egg, and called host->GetHostID() on whatever FindHost returned. FindHost answers NULL for a host that is in the egg but not connected - impossible in an arcade, ordinary everywhere else - and it crashed before the mission could start. The table is read back one pair per egg host in egg order, so a missing host cannot be skipped without shifting every entry after it; it writes the pair, says so, and carries on. And RPL4.CPP chose between the playback application and an idle one in silence. For the first few seconds those look identical from outside, so a spool that failed to load was indistinguishable from one that had not started yet. It now says which, and why not. The correction: I reported a failure in L4AudioRenderer::Initialize on audiomr.ini's clipping_radius. That was mine. I was running cdb from the source tree, so the game looked for audio\audiomr.ini beside the debugger rather than beside itself and found nothing. With the working directory right, audio initialises fine and always did. Where it stands: -pb loads last.spl - "2973012 bytes to play" - creates RPL4PlaybackApplication and runs without crashing. Whether it puts the race on the screen is the next thing to look at, and that wants eyes rather than a log. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+11
@@ -91,3 +91,14 @@ RedPlanet-*.zip
|
||||
# in the history: a minidump carries process memory and the sender's own
|
||||
# file paths.
|
||||
/Crashdmp/
|
||||
|
||||
# Runtime files the game writes beside itself when run from the tree.
|
||||
# environ.ini in particular is GENERATED from the template in
|
||||
# RP_L4/RPL4ENVIRON.cpp - a copy committed here would be a second source
|
||||
# of truth that nothing updates.
|
||||
bindings.txt
|
||||
environ.ini
|
||||
rpl4.log
|
||||
rpl4-fail.log
|
||||
last.spl
|
||||
SPOOLS/
|
||||
|
||||
+19
-2
@@ -58,6 +58,23 @@
|
||||
|
||||
#define Check(p)
|
||||
#define Check_Signature(p)
|
||||
//#define Fail(m) Fail_To_Debugger(m,__FILE__,__LINE__)
|
||||
#define Fail(m) abort();
|
||||
|
||||
//
|
||||
// A release build used to answer Fail("clipping_radius not defined") with a
|
||||
// bare abort(), throwing the message away - and because the compiler merges
|
||||
// identical cold paths, every Fail in a function became the same anonymous
|
||||
// stub. A crash then told you the function and nothing else, which cost an
|
||||
// afternoon of disassembly to learn that a mission review build stops in
|
||||
// L4AudioRenderer::Initialize without ever saying which of its nine checks
|
||||
// was the one that tripped.
|
||||
//
|
||||
// The debug build has always routed this to a function that prints the
|
||||
// message with its file and line. Release now does too. The termination is
|
||||
// unchanged - still abort(), still exit code 0xC0000409 - so nothing
|
||||
// downstream sees anything different; it just says what happened on the way
|
||||
// out.
|
||||
//
|
||||
extern void Fail_With_Message(const char *message, const char *file, int line);
|
||||
|
||||
#define Fail(m) Fail_With_Message(m, __FILE__, __LINE__)
|
||||
#define Cast_Object(type, ptr) ((type)(ptr))
|
||||
@@ -133,6 +133,70 @@ void Verify_Failed(char *Message, char *File, int Line)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
//
|
||||
//
|
||||
// The release build's Fail. Says what went wrong before it goes.
|
||||
//
|
||||
// Terminates exactly as the old bare abort() did - same call, same
|
||||
// 0xC0000409 - so no caller, script or crash handler sees a change. The
|
||||
// only difference is the line in the log naming the check that failed and
|
||||
// where it lives.
|
||||
//
|
||||
// The emergency shutdowns are worth doing before the process ends even
|
||||
// though it is ending: the gauge renderer and the controls manager both
|
||||
// hold hardware, and a display left mid-mode is a nuisance to whoever has
|
||||
// to pick the machine up afterwards - which in a pod bay is not the
|
||||
// person who was playing.
|
||||
//
|
||||
void
|
||||
Fail_With_Message(const char *Message, const char *File, int Line)
|
||||
{
|
||||
if (!AlreadyFailed)
|
||||
{
|
||||
AlreadyFailed = True;
|
||||
|
||||
DEBUG_STREAM << "FAIL " << File << "(" << Line << "): " << Message
|
||||
<< "\n" << std::flush;
|
||||
|
||||
//
|
||||
// And again, somewhere that cannot be lost.
|
||||
//
|
||||
// rpl4.log is std::cout with its streambuf swapped for an ofstream's
|
||||
// (see WinMain), and the text written above did NOT survive to disk
|
||||
// across the exit - flushed as far as the stream and no further.
|
||||
// Rather than guess at where it stops, write the one line that
|
||||
// matters to its own file and close it: fclose is a promise the
|
||||
// bytes are on the disk, and a file opened and closed inside this
|
||||
// function cannot be left dangling by whatever happens next.
|
||||
//
|
||||
// Its own file rather than an append to rpl4.log because that one is
|
||||
// already open for writing and Windows will not share it.
|
||||
//
|
||||
FILE *record = fopen("rpl4-fail.log", "w");
|
||||
|
||||
if (record != NULL)
|
||||
{
|
||||
fprintf(record, "FAIL %s(%d): %s\n", File, Line, Message);
|
||||
fclose(record);
|
||||
}
|
||||
|
||||
//
|
||||
// Hardware last, and after the record is safely written: the gauge
|
||||
// renderer and the controls manager both hold devices, and a display
|
||||
// left mid-mode is a nuisance for whoever picks the machine up - in
|
||||
// a pod bay, not the person who was playing. If one of these faults
|
||||
// on the way down, the failure has already been recorded.
|
||||
//
|
||||
SystemClock::timer.Shutdown();
|
||||
ControlsManager::Shutdown();
|
||||
GaugeRenderer::EmergencyShutdown();
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
//
|
||||
//#############################################################################
|
||||
//#############################################################################
|
||||
|
||||
@@ -240,6 +240,9 @@ Logical
|
||||
{
|
||||
DEBUG_STREAM << "\n" << argv[0] <<
|
||||
" -egg <filename> -net <memory_address> -solids -h -help\n"
|
||||
" -mr mission review: play a spool back and\n"
|
||||
" record a new one alongside it\n"
|
||||
" -pb play a spool back only, no recording\n"
|
||||
" -windowed windowed, title bar and all\n"
|
||||
" -fit borderless over the whole monitor,\n"
|
||||
" render size chosen to match\n"
|
||||
@@ -403,6 +406,23 @@ Logical
|
||||
{
|
||||
suppressGauges = TRUE;
|
||||
missionReviewMode = 1;
|
||||
}
|
||||
//
|
||||
// Play a spool back without recording a new one.
|
||||
//
|
||||
// Mode 2 was always in RPL4.CPP - it takes one spool file
|
||||
// instead of two and skips the spooling application - there was
|
||||
// simply no way to ask for it. -mr starts a recorder alongside
|
||||
// the playback, and that recorder is the half that falls over
|
||||
// on a machine which is not a pod bay: it walks the hosts named
|
||||
// in the egg expecting to find every one of them connected.
|
||||
//
|
||||
// Watching a race back does not need a recorder at all.
|
||||
//
|
||||
else if (!stricmp(W2A(argv[i]), "-pb"))
|
||||
{
|
||||
suppressGauges = TRUE;
|
||||
missionReviewMode = 2;
|
||||
} else if (!(*parser)(&i, argc, argv))
|
||||
{
|
||||
return False;
|
||||
|
||||
+30
-2
@@ -124,10 +124,38 @@ void
|
||||
|
||||
ResolveAddress(host_name, &net_address);
|
||||
Host *host = host_mgr->FindHost(net_address);
|
||||
|
||||
|
||||
//
|
||||
// FindHost answers NULL for a host named in the egg that is not
|
||||
// actually connected, and this went straight on to call
|
||||
// host->GetHostID(). In an arcade every station in the egg is on
|
||||
// the wire, so the case could not arise; anywhere else it is the
|
||||
// ordinary state of affairs, and it crashed the spooling
|
||||
// application before the mission could even start.
|
||||
//
|
||||
// The table below is read back one pair per egg host, in egg
|
||||
// order, so a missing host cannot simply be skipped - that would
|
||||
// shift every entry after it. Write the pair, say what happened,
|
||||
// and carry on.
|
||||
//
|
||||
if (host == NULL)
|
||||
{
|
||||
DEBUG_STREAM << "Spool: host '" << host_name
|
||||
<< "' is in the egg but not connected - recording it as"
|
||||
<< " remote with no ID. Packets from it will not map back.\n"
|
||||
<< std::flush;
|
||||
|
||||
*(Logical*)spool->GetPointer() = True;
|
||||
spool->AdvancePointer(sizeof(Logical));
|
||||
|
||||
*(HostID*)spool->GetPointer() = (HostID) 0;
|
||||
spool->AdvancePointer(sizeof(HostID));
|
||||
continue;
|
||||
}
|
||||
|
||||
*(Logical*)spool->GetPointer() = (host != host_mgr->GetLocalHost());
|
||||
spool->AdvancePointer(sizeof(Logical));
|
||||
|
||||
|
||||
*(HostID*)spool->GetPointer() = host->GetHostID();
|
||||
spool->AdvancePointer(sizeof(HostID));
|
||||
}
|
||||
|
||||
@@ -638,12 +638,26 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
if (!spoolFileName)
|
||||
spoolFileName = "last.spl";
|
||||
|
||||
//
|
||||
// Say which of the two this is. The choice was made in silence,
|
||||
// and the two look identical from outside for the first few
|
||||
// seconds - one plays a race back, the other sits there - so a
|
||||
// spool that failed to load was indistinguishable from a spool
|
||||
// that loaded and had not started yet.
|
||||
//
|
||||
SpoolFile *spool = spool_mgr->GetStoredSpoolFile(spoolFileName);
|
||||
if (spool)
|
||||
{
|
||||
DEBUG_STREAM << "Playback: " << spoolFileName
|
||||
<< " loaded, " << (int) spool->GetBytesRemaining()
|
||||
<< " bytes to play\n" << std::flush;
|
||||
new_app = new RPL4PlaybackApplication(hInstance, hWnd, &resources, spool);
|
||||
} else
|
||||
{
|
||||
DEBUG_STREAM << "Playback: could not load '" << spoolFileName
|
||||
<< "' - starting idle with nothing to show. It must exist, be"
|
||||
<< " no larger than " << (int) SPOOL_SIZE
|
||||
<< " bytes, and have been written by this build.\n" << std::flush;
|
||||
new_app = new RPL4IdleApplication(hInstance, hWnd, &resources);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user