ResumeLog

An interrupted mission saved as its start plus every tick's raw input, replayed deterministically to continue it (resume.txt, format 7).

Location

Headersrc/GalaxyEggbert/Game/ResumeLog.hpp
Sourcessrc/GalaxyEggbert/Game/ResumeLog.cpp; recording and replay in src/GalaxyEggbertCNA/GalaxyEggbertGameDemo.cpp and GalaxyEggbertGame.cpp
NamespaceGalaxyEggbert::Game
Engine dependencyNone. 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

Used by

Key members

A selection of the public API declared in src/GalaxyEggbert/Game/ResumeLog.hpp.

MemberSignatureWhat it does
startResumeStart startBuild 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.
runsstd::vector<ResumeRun> runsInput runs: a count of identical consecutive ticks and their ResumeInput.
Appendvoid Append(const ResumeInput& input)Adds one tick, extending the last run when the input is identical.
TickCountstd::uint64_t TickCount() constTotal ticks recorded.
Serializestd::string Serialize() constThe complete file text: header, body, checksum footer.
Parsestatic 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.
MatchesWorldbool MatchesWorld(const std::string& path, std::uint64_t hash) constTrue only for the same world path and the same hash of its bytes.
WriteBody / ReadBodyvoid WriteBody(std::ostream& out) const
static bool ReadBody(std::istream& in, ResumeLog& parsed)
The shared body (world, start, runs); ReadBody range-checks every field.
AppendChecksum / VerifiedBodystatic 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 / HashFilestatic 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.
Pathstatic 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>

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

Tests

Related pages