ResumeLog
An interrupted mission saved as its start plus every tick's raw input, replayed deterministically to continue it (resume.txt, format 7).
Location
| Header | src/GalaxyEggbert/Game/ResumeLog.hpp |
|---|---|
| Sources | src/GalaxyEggbert/Game/ResumeLog.cpp; recording and replay in src/GalaxyEggbertCNA/GalaxyEggbertGameDemo.cpp and GalaxyEggbertGame.cpp |
| Namespace | GalaxyEggbert::Game |
| Engine dependency | None. Plain data and text I/O; the host fills it from CNA input. |
Purpose
The original game saved "the current moment" of a mission when the application was put away
and offered to continue it on the next start. Galaxy Eggbert reaches the same result differently:
because the simulation is a fixed 1/60 s tick and fully deterministic, a mission's moment can be
rebuilt from where it started and what the player pressed. ResumeLog holds exactly that:
a ResumeStart record (world, Blupi's place, lives, speed, seed, cheats, settings) and a
run-length-encoded list of ResumeInput snapshots, one per tick. On Continue the host
replays the whole list at high speed, undrawn and muted, and hands control back to the player at
the exact tick where the game was left.
The checkpoint in SaveData (mission number and lives) remains the
fallback whenever no valid log exists. The behaviour and its platform limits are recorded as
BD-0077 in plans/audit/behavior_differences.md.
Collaborators
Uses
UserFolderfor the path (resume.txtin the player's folder).- SaveData's text format, embedded as
start.saveData.
Used by
- GalaxyEggbertGame:
StartResumeLog(),ReadResumeLog(),BeginResume(),ReplayBatch(),CheckResumeSelftest(),OnDeactivated(),OnExiting(). - DemoReplay: a
.gdemofile carries the same body (WriteBody/ReadBody) under its own header. - CustomWorldStorage:
HashBytesidentifies a galaxy world a demo was recorded in.
Key members
A selection of the public API declared in src/GalaxyEggbert/Game/ResumeLog.hpp.
| Member | Signature | What it does |
|---|---|---|
start | ResumeStart start | Build identity, world path and hash, mission, lives, speed, seed, Blupi's x/y/z/yaw, the cheat toggles (quick, open doors, show secret, compass, debug), the cheat-input lock, camera mode, and the settings as text. |
runs | std::vector<ResumeRun> runs | Input runs: a count of identical consecutive ticks and their ResumeInput. |
Append | void Append(const ResumeInput& input) | Adds one tick, extending the last run when the input is identical. |
TickCount | std::uint64_t TickCount() const | Total ticks recorded. |
Serialize | std::string Serialize() const | The complete file text: header, body, checksum footer. |
Parse | static bool Parse(const std::string& text, const std::string& build, ResumeLog& result) | Validates and reads a file for the given build. Transactional: result is unchanged on failure. |
MatchesWorld | bool MatchesWorld(const std::string& path, std::uint64_t hash) const | True only for the same world path and the same hash of its bytes. |
WriteBody / ReadBody | void WriteBody(std::ostream& out) conststatic bool ReadBody(std::istream& in, ResumeLog& parsed) | The shared body (world, start, runs); ReadBody range-checks every field. |
AppendChecksum / VerifiedBody | static std::string AppendChecksum(const std::string& body)static std::optional<std::string> VerifiedBody(const std::string& text) | Adds and checks the END <hash> footer; a truncated or damaged text yields nullopt. |
HashBytes / HashFile | static std::uint64_t HashBytes(std::string_view bytes)static std::optional<std::uint64_t> HashFile(const std::filesystem::path& path) | 64-bit FNV-1a over raw bytes (NULs included). A file that cannot be read has no hash. |
Path | static std::string Path() | resume.txt in the player's folder. |
Format 7
Text with the classic locale and round-trip float precision. Strings are quoted. Layout as written by Serialize() and WriteBody():
GALAXY_RESUME 7
"<build identity>"
"<world path>" <world hash>
<mission> <lives> <speed> <seed> <x> <y> <z> <yaw> <quick> <openDoors> <showSecret> <compass> <cheatsLocked> <debug> <cameraMode>
"<savedata.txt text at the start>"
<run count> <tick count>
<count> <n keys> <keys...> <mouseX> <mouseY> <wheel> <buttons> <viewW> <viewH> <n touches> [<id> <x> <y> <state>]... <touchControls> <accelValid> <accelReset> <accelY> <cheatLetter>
...
END <FNV-1a of everything above>
- Input is raw: held key codes (sorted), mouse, wheel and buttons, viewport size, touches, and the accelerometer's Y value, after the gamepad has been merged into keys and before the game interprets anything.
- Typed cheat letters are recorded as ordered events in
cheatLetter(-1keyboard snapshot only,0no letter,a-za letter pressed that tick), so a fast-typed word replays the same way. - Touch capability (
touchControls) is stored per tick, so a replay does not ask the current device whether it has a touch screen. - Compass, debug overlay and the cheat-input lock (
cheatsLocked: 1 forfairplay's lock, 2 forhonest's, which lasts until the game quits) are part of the start record. - Build identity is the compile date and time; on desktop builds it adds
fnv1a64=<hash>of the running executable (Linux verified; the Windows and macOS code paths exist but are not verified). Android, the web build and an unreadable executable keep the compile-time identifier only.
How it is used
Recording starts when a mission is in Play after its fade-in (StartResumeLog()); every
following tick appends tickInput_. Nothing is written during play. The file is written
only when the window is deactivated in Play (focus lost, application sent to the background),
atomically through a temporary file (src/GalaxyEggbertCNA/GalaxyEggbertGame.cpp):
if ((replayingResume_ || phase_ == GalaxyEggbert::Def::GamePhase::Play) &&
recordingResume_ && !missionStartPending_)
{
const std::string temporary = ResumeLog::Path() + ".tmp";
std::ofstream out(temporary, std::ios::binary | std::ios::trunc);
out << resumeLog_.Serialize();
...
if (out) std::filesystem::rename(temporary, ResumeLog::Path(), error);
...
}
else
{
DeleteResumeFile();
}
The file is deleted on a clean exit (OnExiting), when the mission's exit is reached, and
when it is deactivated outside Play. After the start-up wait, ReadResumeLog() parses it;
a valid log (or a SaveData checkpoint) leads to the Resume screen. Continue
calls BeginResume(), which checks the world before touching anything, restores
settings, cheats, camera and speed, loads the mission from the verified bytes, places Blupi, and
then ReplayBatch() runs up to 600 ticks per frame with drawing suppressed and sound
muted until the log is consumed. A galaxy mission's log names the galaxy's world file, so Continue
returns into that galaxy.
Self-test. With GALAXY_RESUME_SELFTEST=<ticks> the game, at that tick of
a mission, records a signature of its state (Blupi, counters, objects, a hash of every block,
clocks, camera, cheat input, debug overlay, the Hide trail), re-parses its own log, resumes from
it, and prints RESUME SELFTEST equal or RESUME SELFTEST different: <parts>
(exit code 1). The autoplay scripts use it to prove a replay rebuilds the same game.
Rules and invariants
- Refused, and
resume.txtdeleted, when: the checksum footer is missing or wrong (truncated or damaged), the header is notGALAXY_RESUME 7(every older version is refused), the build identity differs from the running build, any field is out of range, or text follows the footer. - Refused at Continue when the world file at the recorded path is missing, unreadable, does not load, or its bytes hash differently: an edited or updated world is never replayed into.
- Bounds: at most 10 000 000 ticks, 256 keys and 32 touches per tick, a 128 MiB file, mission 1-999, game speed 0, 1, 2, 4 or 8.
- Determinism is the contract: no wall-clock time, unseeded randomness or frame-rate dependence in the Play path, or old logs replay into a different game.
- Editor play-tests and main-menu demos never record or touch the resume file; a demo leaves it byte for byte as it was.
Tests
VerifyAutoplay(tools/VerifyAutoplay.cpp) also holds the resume checks: run-length merging, a version-7 round trip of world identity, compass, debug, cheat lock and every raw input, ordered cheat letters, the recorded touch capability, refusal of version 6 and version 1 even with a valid checksum, of a changed world hash or path, of a foreign build, of every truncation, of a damaged checksum and trailing text, of zero-length and unbounded runs and out-of-range input, FNV-1a against known test vectors, and file hashing across buffer boundaries.VerifyDemoReplay: a demo is not accepted as a resume file and a resume file is not accepted as a demo.- Live:
GALAXY_RESUME_SELFTESTduring autoplay runs (see Autoplay).
Related pages
- Save Data & Resume
- SaveData, DemoReplay, Autoplay
- GalaxyEggbertGame
- Game Loop (the fixed tick the replay depends on)
- Testing