Save Data & Resume
Settings and three players' progress in savedata.txt, an interrupted mission in resume.txt, and the player's worlds and recordings beside them (as of 2026-11-10).
The player's folder
Since 2026-11-10 every file the player creates lives in one folder, chosen by the host at start
(ChooseUserFolder() in src/GalaxyEggbertCNA/main.cpp) and handed to
src/GalaxyEggbert/Game/UserFolder.hpp, which the save, resume, demo and editor code ask for paths.
| Where the game runs | Folder |
|---|---|
| Linux desktop | $XDG_DATA_HOME/Galaxy Eggbert, else ~/.local/share/Galaxy Eggbert (CNA's StorageDevice root) |
| Windows desktop | %LOCALAPPDATA%\Galaxy Eggbert (implemented; the Windows build is not verified) |
| Any desktop, chosen | --user DIR on the command line, or the GALAXY_USER_DIR environment variable (the test scripts point it at their run folder) |
| Web | /save, stored in the browser's IndexedDB |
| Android | the app's internal storage, which is also its working folder |
If no per-user folder can be found or created, the game says so on the console and keeps the files in the working
folder. On the first start with an empty user folder, a savedata.txt, resume.txt,
demos/ and customworlds/ left in the working folder by an earlier build are copied over
(CopyOldUserFiles()); nothing is moved or overwritten.
<player's folder>/
├── savedata.txt settings and progress (SaveData)
├── resume.txt an interrupted mission (ResumeLog), only while there is one
├── demos/
│ ├── demo001.gdemo ... the player's recordings for the ten demo positions
│ └── shared/ exported recordings
└── customworlds/
├── gamer0/ gamer1/ gamer2/ each player's editor worlds
│ ├── custom_001.vwr ... custom_010.vwr (+ .png thumbnails)
│ ├── backups/
│ └── galaxy/ the player's own galaxy (worldNNN.vwr, name.txt, backups/)
├── shared/ exported worlds and .galaxy packs
└── campaign-backups/, campaign-edits/, campaign-originals/ shipped worlds edited in play
savedata.txt
SaveData (src/GalaxyEggbert/Game/SaveData.hpp, .cpp) is engine-independent. The
file is plain key=value lines, read once when the game is constructed; a missing file or an unknown key
leaves the defaults. It is deliberately not the original's 640-byte binary GameData block, but it keeps the
same contents: global settings plus three players. It is written when a setting changes, when a player is selected, and
at the original's checkpoints (a mission lost or its exit reached), never during a resume replay or a demo. What
SaveData::Write() produces:
soundEnabled=1
jumpRight=1
autoZoom=1
accelActive=0
effectsVolume=100
musicVolume=100
accelSensitivity=50
fullscreen=0
terrainRelief=0
effectsLevel=0
frameRate=60
textureScale=0
selectedGamer=0
gamer0.lives=3
gamer0.missionNumber=0
gamer0.lastWorld=1
gamer0.hasProgress=0
gamer0.doorsUnlocked=0000000000... (200 characters, one per mission number)
gamer1.lives=3
...
gamer2.doorsUnlocked=0000000000...
Settings
| Key | Values (default) | Meaning |
|---|---|---|
soundEnabled | 0/1 (1) | sound on or off |
jumpRight | 0/1 (1) | the on-screen pad's Jump on the right |
autoZoom | 0/1 (1) | the original's automatic zoom while walking |
accelActive, accelSensitivity | 0/1 (0); 0-100 (50) | accelerometer steering |
effectsVolume, musicVolume | 0-100 (100) | Galaxy's two volume sliders |
fullscreen | 0/1 (0) | full screen (desktop); the first run is windowed |
terrainRelief | 0/1 (0) | the Setup's "3D terrain" |
effectsLevel | 0-4 (0) | 3D effects Off, Low, Medium, High, Ultra (an old effects3d=1 reads as High) |
frameRate | 60, 120, 144, 240 (60) | frames drawn per second (Game Loop) |
textureScale | 0 auto, 1, 2 (0) | 1x or 2x sprite sheets, at the next start |
selectedGamer | 0-2 (0) | the player selected in the menu |
Environment variables can override some of these for one run; see Configuration.
Per-player progress
There are three players (SaveData::kGamerCount = 3), shown as A, B and C in the menu. Each has a
GamerSlot:
| Field | Meaning |
|---|---|
lives | lives at the last checkpoint (3 for a new player) |
missionNumber | the mission of the last checkpoint, where Continue starts when there is no resume log |
lastWorld | the world last played; the main hub places Blupi beside its sign |
hasProgress | whether there is a checkpoint at all |
doorsUnlocked | 200 flags indexed by mission number, as the original's door array: opened mission bars (0-179, the menu's "secondary gates") and the main hub's gold bars (180-199, "main gates"); HubDoors reads them |
Each player's own galaxy has a second slot with the same fields, saved as galaxy0.lives= ... and written
only once that player has galaxy progress, so a file from before galaxies existed stays as it was. While a galaxy is
played, the progress accessors read and write that slot; the mode itself is not saved.
Mission resume: resume.txt
An interrupted mission continues from the exact moment it stopped. The original saved the moment's state; Galaxy
saves the mission's start plus every tick's input and replays it (ResumeLog,
src/GalaxyEggbert/Game/ResumeLog.hpp; host code in src/GalaxyEggbertCNA/GalaxyEggbertGameDemo.cpp).
This works because the simulation is deterministic (Game Loop).
- Mission start. When a mission begins in Play,
StartResumeLog()records the start: the build identity, the world's path and the FNV-1a hash of its exact bytes, mission number, lives, game speed, the random seed, Blupi's position and heading, the cheat state (quick, opendoors, showsecret, compass, debug, the fairplay/honest lock), the camera mode, and the wholesavedata.txttext as it was. - Every tick. The tick's input record (Input) is appended; identical consecutive records are stored once with a count.
- Losing focus. When the window or app is deactivated during Play, the log is written to
resume.txt.tmpand renamed toresume.txt. - Deleted on a clean exit, when the mission's exit is reached, and when the file is refused. Demos never write or delete it; editor play-tests do not record one.
- Next start. After the loading screen the game parses
resume.txt. If it is valid, or if the player has a checkpoint, the Resume screen asks Continue or menu. - Continue.
BeginResume()reads the world file again and checks its path and hash, restores the recorded settings, cheats and seed, loads the mission, places Blupi, andReplayBatch()replays up to 600 ticks per update with drawing off and sound muted until the recorded moment is reached. A mission of the player's galaxy continues inside that galaxy. - Fallback. If the file is missing, damaged, of another format version or another build, or the world changed, the game starts the checkpoint mission instead, with the saved lives.
Format version 7
Text, classic locale, floats written with enough digits to read back exactly. Field by field, as
Serialize() and WriteBody() write it:
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 at the start>"
<run count> <tick count>
<count> <key count> <keys...> <mouseX> <mouseY> <wheel> <buttons> <viewportW> <viewportH> <touch count> <id x y state>... <touchControls> <accelValid> <accelReset> <accelY> <cheatLetter>
... (one line per run)
END <FNV-1a 64 of everything above>
- Parsing is all or nothing: the checksum footer must be complete and match, every field is range-checked, keys must be sorted, the tick total must add up; at most 10 000 000 ticks and 128 MB.
- The build identity is the compile date and time plus, on desktops, an FNV-1a hash of the running executable (read on Linux, implemented for Windows and macOS). Android and the web use the compile date and time only. A different build refuses the file, because only an identical binary is sure to replay it identically.
- Version 7 (2026-11-08) added ordered typed letters, touch capability, the compass and the cheat lock; older versions are refused and fall back to the checkpoint.
- The demo files reuse this body (
WriteBody(),ReadBody()) under their own header; see below.
GALAXY_RESUME_SELFTEST=N makes the game, after N
recorded ticks, rebuild the mission from its own log and compare Blupi, counters, objects and world state with the live
game, printing RESUME SELFTEST equal or the parts that differ. The autoplay checks use it.Galaxies
Each player's galaxy is a set of world files in customworlds/gamerN/galaxy/
(world001.vwr for its main hub, world010.vwr ... for world hubs, their missions, and
world199.vwr), with name.txt and a backups/ folder for deleted places. Its progress is
the galaxyN.* slot in savedata.txt. Importing another galaxy resets that progress. An export is a
single .galaxy pack in customworlds/shared/ (a download on the web). See
Your Own Galaxy.
Demos and recordings
- The shipped demos are
Content/demos/demo001.gdemo...demo010.gdemo, read-only game data. - A player's recording for a demo position is
demos/demoNNN.gdemoin the player's folder; the ten positions are shared by all players. Exports go todemos/shared/. - A
.gdemostarts with aGALAXY_DEMOheader and format version, the simulation revision it was recorded with, its title, the recording build (for information only) and an end-state signature, followed by a resume-log body and the same checksum footer. Unlikeresume.txtit is not tied to one build: it is skipped when its format, its simulation revision or its world's hash differs.
See Demos & Recordings and DemoReplay.
Custom worlds
CustomWorldStorage gives each player ten slots,
customworlds/gamerN/custom_001.vwr to custom_010.vwr, each with a .png thumbnail. The first
save after opening a world keeps up to three earlier versions in backups/ (custom_003.1.vwr the newest),
and a deleted slot goes there too. Other .vwr files found in the folder are listed and never deleted.
A shipped world edited in play with the xnjttjpo cheat is handled per platform: the desktop rewrites the
file in worlds3d/ and keeps earlier versions in customworlds/campaign-backups/ and the shipped one in
customworlds/campaign-originals/; the web keeps the edit in /save/customworlds/campaign-edits/ and lays
it over the packed worlds at every start. Pause's Original button restores the shipped world. See
3D World Editor.
Web and Android
- Web.
/saveis an Emscripten IDBFS file system (the build links-lidbfs.js); CNA's storage start-up hook mounts it and persists it to IndexedDB. Exports become downloads and imports use the browser's file picker (src/GalaxyEggbert/Editor/PlatformFiles.hpp). Verified on 2026-11-09 in headless Chrome: a recording and an edited shipped world survived a page reload. Clearing the site's data in the browser deletes the saves. - Android. Before the game starts,
PrepareAndroidAssets()(src/GalaxyEggbertCNA/AndroidAssets.cpp) copies the APK's assets into internal storage and makes it the working folder; the player's files are written beside them. The APK builds and runs in the emulator but has not been played on a device.
Tests
VerifyGESaveData: thesavedata.txtdefaults and round trip, an invalid player number, the galaxy slots.VerifyDemoReplayandVerifyAutoplay: the shared log format and its checks.VerifyGEEditorStorage: slots, backups, sharing and galaxies.- Live: autoplay runs with
GALAXY_RESUME_SELFTEST, and focus-loss, restart and Continue checks on the desktop (plans/audit/behavior_differences.md, BD-0077).
Related: SaveData, ResumeLog, Configuration.