A spool and its egg are one artifact
Cyd's question, and the answer is yes. A spool records what MOVED and never the track it moved through, so it cannot be replayed without the egg the race was run on - and frontend.egg is rewritten by the next race set up on the machine. A recording kept on its own therefore stops being playable the moment somebody picks a different track, silently, and by then the egg that would have opened it is gone. So the egg is saved with it: SPOOLS\<timestamp>.egg beside SPOOLS\<timestamp>.spl, and last.egg beside last.spl. The console sets the path, because the console is where the egg's name is actually known. And playback looks for it. Given no -egg it takes the spool's name, swaps the extension, and uses that if it is there. Naming the egg by hand is not just tedious, it is dangerous: a spool played against a DIFFERENT track loads perfectly happily and shows nonsense, and frontend.egg is exactly the wrong egg by default because it belongs to whatever was set up last. Verified on the way here: the header this build writes reads back exactly as playback expects it - major version 3, host 2 local (the camera), host 3 remote (the racer, matching every packet's fromHost), 8263 packets after a 24 byte header ending precisely at EOF. Playback then loaded it with no complaint about application ID or version, which is the check that failed before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -200,6 +200,23 @@ SpoolRecorder::SpoolRecorder():
|
||||
headerWritten(False),
|
||||
packetsRecorded(0)
|
||||
{
|
||||
eggPath[0] = '\0';
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
void
|
||||
SpoolRecorder::SetEggPath(const char *path)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
if (path == NULL)
|
||||
{
|
||||
eggPath[0] = '\0';
|
||||
return;
|
||||
}
|
||||
strncpy(eggPath, path, sizeof(eggPath) - 1);
|
||||
eggPath[sizeof(eggPath) - 1] = '\0';
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -364,6 +381,47 @@ void
|
||||
spool->SaveAs(filename);
|
||||
CopyFileA(filename, "last.spl", FALSE);
|
||||
|
||||
//
|
||||
// The egg beside it, under the same stem.
|
||||
//
|
||||
// A spool is only half a recording: it says what moved, never the
|
||||
// track it moved through, and playback will not start without the egg
|
||||
// the race was run on. frontend.egg is rewritten by the next race set
|
||||
// up on this machine, so a recording kept on its own quietly stops
|
||||
// being playable as soon as somebody picks another track. Kept
|
||||
// together they stay one artifact for as long as the folder does.
|
||||
//
|
||||
if (eggPath[0] != '\0')
|
||||
{
|
||||
char egg_copy[MAX_PATH];
|
||||
|
||||
strncpy(egg_copy, filename, sizeof(egg_copy) - 1);
|
||||
egg_copy[sizeof(egg_copy) - 1] = '\0';
|
||||
|
||||
size_t length = strlen(egg_copy);
|
||||
if (length > 4)
|
||||
{
|
||||
strcpy(egg_copy + length - 4, ".egg");
|
||||
if (CopyFileA(eggPath, egg_copy, FALSE))
|
||||
{
|
||||
CopyFileA(eggPath, "last.egg", FALSE);
|
||||
DEBUG_STREAM << "Record: kept the egg beside it as "
|
||||
<< egg_copy << "\n" << std::flush;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_STREAM << "Record: could NOT copy the egg '" << eggPath
|
||||
<< "' - the spool will not replay without it\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_STREAM << "Record: no egg path known, so none kept - this spool"
|
||||
<< " will need the matching egg supplied by hand to replay\n"
|
||||
<< std::flush;
|
||||
}
|
||||
|
||||
DEBUG_STREAM << "Record: wrote " << filename << " - "
|
||||
<< packetsRecorded << " packets, " << (written / 1024) << "KB"
|
||||
<< (full ? " (truncated - buffer filled)" : "")
|
||||
|
||||
+12
-1
@@ -83,7 +83,17 @@ public:
|
||||
void
|
||||
Record(const NetworkPacket *packet);
|
||||
|
||||
// Write SPOOLS\<timestamp>.spl and copy it to last.spl.
|
||||
// Where the egg for this race lives, so it can be kept beside the
|
||||
// recording. A spool records what MOVED and never the track it moved
|
||||
// through, so the two are one artifact: without its egg a spool cannot
|
||||
// be replayed, and frontend.egg is overwritten by the next race set up
|
||||
// on this machine. Saving them together is what stops a recording
|
||||
// going stale the moment somebody picks a different track.
|
||||
void
|
||||
SetEggPath(const char *path);
|
||||
|
||||
// Write SPOOLS\<timestamp>.spl, the matching .egg beside it, and copy
|
||||
// the spool to last.spl.
|
||||
void
|
||||
Save();
|
||||
|
||||
@@ -119,6 +129,7 @@ protected:
|
||||
Logical full;
|
||||
Logical headerWritten;
|
||||
int packetsRecorded;
|
||||
char eggPath[260];
|
||||
};
|
||||
|
||||
// The process-wide recorder, made on first use.
|
||||
|
||||
+40
-3
@@ -215,11 +215,48 @@ L4PlaybackNetworkManager::L4PlaybackNetworkManager():
|
||||
const char *egg_name =
|
||||
((L4Application *) application)->GetEggNotationFileName();
|
||||
|
||||
//
|
||||
// Fall back to the egg saved with the recording.
|
||||
//
|
||||
// Every spool is written with its egg beside it under the same stem,
|
||||
// and last.egg alongside last.spl, precisely so that a recording is one
|
||||
// self-contained thing. Having to name the egg by hand invites naming
|
||||
// the WRONG one - frontend.egg is rewritten by the next race set up on
|
||||
// the machine, and a spool played against a different track would load
|
||||
// happily and show nonsense.
|
||||
//
|
||||
static char found_egg[MAX_PATH];
|
||||
|
||||
if (egg_name == NULL || strlen(egg_name) == 0)
|
||||
{
|
||||
DEBUG_STREAM << "Playback: no egg given. A spool records the race but"
|
||||
<< " not the track it was run on, so -egg must name the one it"
|
||||
<< " was recorded with.\n" << std::flush;
|
||||
CString spool_name = ((L4Application *) application)->GetSpoolFileName();
|
||||
const char *spool_text =
|
||||
(!spool_name) ? "last.spl" : (const char *) spool_name;
|
||||
|
||||
strncpy(found_egg, spool_text, sizeof(found_egg) - 1);
|
||||
found_egg[sizeof(found_egg) - 1] = '\0';
|
||||
|
||||
size_t length = strlen(found_egg);
|
||||
if (length > 4)
|
||||
{
|
||||
strcpy(found_egg + length - 4, ".egg");
|
||||
|
||||
FILE *probe = fopen(found_egg, "r");
|
||||
if (probe != NULL)
|
||||
{
|
||||
fclose(probe);
|
||||
egg_name = found_egg;
|
||||
DEBUG_STREAM << "Playback: using the egg saved with the"
|
||||
<< " recording, '" << found_egg << "'\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (egg_name == NULL || strlen(egg_name) == 0)
|
||||
{
|
||||
DEBUG_STREAM << "Playback: no egg. A spool records the race but not the"
|
||||
<< " track it was run on, and no egg was found beside the spool -"
|
||||
<< " name one with -egg.\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "..\munga\appmgr.h"
|
||||
#include "..\munga\appmsg.h"
|
||||
#include "..\munga\console.h"
|
||||
#include "..\munga\spooler.h"
|
||||
#include "..\rp\rpcnsl.h"
|
||||
#include "..\munga_l4\l4app.h"
|
||||
#include "..\munga_l4\l4net.h"
|
||||
@@ -669,6 +670,13 @@ Logical
|
||||
strncpy(gEggPath, egg_path, sizeof(gEggPath) - 1);
|
||||
gEggPath[sizeof(gEggPath) - 1] = '\0';
|
||||
|
||||
//
|
||||
// The recording needs this too. A spool says what moved; the egg says
|
||||
// what it moved through, and the console is where the egg's name is
|
||||
// actually known.
|
||||
//
|
||||
SpoolRecorder_Get()->SetEggPath(gEggPath);
|
||||
|
||||
//
|
||||
// The wire image of the egg: file newlines become NULs, exactly
|
||||
// what the arcade console sent (RPMission.ToEggFileMessages)
|
||||
|
||||
Reference in New Issue
Block a user