CustomWorldStorage
Where player-made worlds live: ten slots per player, backups, thumbnails, sharing, edited shipped worlds and the player's galaxy.
Location
| Header | src/GalaxyEggbert/Editor/CustomWorldStorage.hpp |
|---|---|
| Sources | src/GalaxyEggbert/Editor/CustomWorldStorage.cpp; web download and upload in src/GalaxyEggbert/Editor/PlatformFiles.{hpp,cpp} |
| Namespace | GalaxyEggbert::Editor (free functions and constants, no class) |
| Engine dependency | None: std::filesystem, the world model (World, MoveObjectRecord), Game/UserFolder.hpp and Game/DemoReplay.hpp. Only PlatformFiles.cpp has Emscripten code. |
Purpose
CustomWorldStorage is the file layer of the 3D world editor and of the player's own galaxy. It names every file, lists what is on disk, and copies, backs up, restores, exports and imports worlds. It is deliberately not recorded in SaveData: the editor scans the directories each time, so a list cannot drift away from the files. Worlds belong to one of the three players (gamer slots 0-2, shown as A-C) chosen on the main menu.
All paths are inside the player's user folder (Game/UserFolder.hpp, since 2026-11-10). On the desktop that is $XDG_DATA_HOME/Galaxy Eggbert (by default ~/.local/share/Galaxy Eggbert) on Linux, %LOCALAPPDATA%\Galaxy Eggbert on Windows (not verified), or a folder given with --user DIR or GALAXY_USER_DIR. The web build uses /save, which is kept in the browser's storage, and Android uses the app's private storage. Before 2026-11-10 the files were in the working folder; an existing player's files are copied over on the first start.
Folder layout
customworlds/ CustomWorldsRoot()
gamer0/ gamer1/ gamer2/ CustomWorldsDir(gamer), players A-C
custom_001.vwr ... custom_010.vwr the ten slots (SlotWorldPath)
custom_003.png a slot's thumbnail (ThumbnailPath)
custom_011.vwr, other names older worlds: listed, never deleted
backups/custom_003.1.vwr backups, .1 newest ... .3 oldest
.demo-seeded, .gallery-seeded seeding markers
galaxy/ GalaxyDir(gamer): the player's galaxy
world001.vwr main hub
world010.vwr ... world140.vwr world hubs 1-14
world011.vwr ... world148.vwr missions 1-8 of each world
world199.vwr bonus world
name.txt, backups/
shared/ SharedWorldsDir(): A-world-3.vwr, A-galaxy.galaxy ...
campaign-backups/ xnjttjpo: backups of edited shipped worlds
campaign-edits/ xnjttjpo: a copy of every save of a shipped world
campaign-originals/ desktop: shipped worlds as they came, before the first edit
On the web the originals are kept in memory in worlds3d-shipped beside worlds3d/, because the page's packed data holds the shipped worlds anyway.
Collaborators
Uses: UserPath() (UserFolder.hpp), World::saveToFile/loadFromFile, PlaceMoveObject for the starter worlds, DemoReplay (recordings made in a galaxy travel with its pack), FreeFileName (numbered export names), and on the web OfferDownload and RequestUpload from PlatformFiles.hpp (a download, and the browser's file picker writing into shared/; elsewhere they do nothing and return false).
Used by: WorldEditor (seeding on EnterBrowser, backups and thumbnails on save), EditorBrowserScreen and GalaxyBrowserScreen (the slot and galaxy screens), GalaxyEggbertGame (opening galaxy worlds, xnjttjpo, Pause's "Original", galaxy demos) and main.cpp (the web's ApplyCampaignEdits at start).
Key members
A selection of the declarations in CustomWorldStorage.hpp, as of 2026-11-10 (Worlds::World is GalaxyEggbert::Worlds::World).
| Member | Signature | What it does |
|---|---|---|
kCustomWorldSlotCount, kBackupGenerations | constexpr int = 10, 3 | Slots per player; backup generations per world. |
CustomWorldsDir | std::filesystem::path CustomWorldsDir(int gamerSlot) | The player's folder, customworlds/gamerN. |
SlotWorldPath | std::filesystem::path SlotWorldPath(int gamerSlot, int slot) | Slot 1-10's file, custom_%03d.vwr; may not exist yet. |
ListOlderCustomWorlds, CopyIntoFreeSlot | std::vector<std::filesystem::path> ListOlderCustomWorlds(int gamerSlot), std::filesystem::path CopyIntoFreeSlot(int gamerSlot, const std::filesystem::path& source) | Worlds outside the ten slots; copying one (or a shared world) into the first free slot, the source untouched. |
NextNewWorldPath | std::filesystem::path NextNewWorldPath(int gamerSlot) | The first free slot's path; empty when all ten are taken. |
BackUpWorld, BackUpWorldTo | bool BackUpWorld(const std::filesystem::path& world), bool BackUpWorldTo(const std::filesystem::path& world, const std::filesystem::path& backupDir) | Keep the file as the newest backup, shifting the older ones and dropping the oldest; the second form keeps them elsewhere (edited shipped worlds). |
RestoreBackup | bool RestoreBackup(const std::filesystem::path& world) | The newest backup takes the world's place and the current world becomes the oldest backup, so repeated restores cycle back to the start. |
DeleteSlotWorld | bool DeleteSlotWorld(const std::filesystem::path& world) | Deletes a world into its backups (and drops its thumbnail). |
ThumbnailPath | std::filesystem::path ThumbnailPath(const std::filesystem::path& world) | The PNG beside the world: the editor's view at its last save. |
SharedWorldsDir, ExportWorld, ListSharedWorlds | std::filesystem::path ExportWorld(int gamerSlot, int slot) | Copies a slot into shared/ as ExportName() (A-world-3.vwr), replacing an earlier export of that slot. |
SeedShowcaseSlot, SeedGallerySlot | bool SeedShowcaseSlot(int gamerSlot, const std::filesystem::path& showcase), bool SeedGallerySlot(int gamerSlot, const std::filesystem::path& gallery) | Slot 1 gets the showcase world (999) once, only if empty; the first free slot gets the gallery world (998), kept current while unedited. |
CampaignBackupsDir, CampaignEditsDir, CampaignOriginalsDir | std::filesystem::path ...() | The folders for shipped worlds edited with xnjttjpo. |
ApplyCampaignEdits | int ApplyCampaignEdits(const std::filesystem::path& worldsDir, const std::filesystem::path& originalsDir = {}) | Lays each kept edit over the shipped world of that name (the web, at every start). |
HasCampaignEdit, KeepCampaignOriginal, RevertCampaignEdit | bool HasCampaignEdit(const std::filesystem::path& world) ... | Whether a shipped world is still its kept edit; keep the original before the first edit; Pause's "Original" puts it back. |
GalaxyDir, GalaxyWorldPath, IsGalaxyMission | std::filesystem::path GalaxyWorldPath(int gamerSlot, int mission), bool IsGalaxyMission(int mission) noexcept | A galaxy place's file, galaxy/world%03d.vwr; valid places are 1, the hubs 10-140, their missions 1-8, and 199. |
CreateEditorStarterWorld, CreateGalaxyStarterWorld | Worlds::World CreateEditorStarterWorld(), Worlds::World CreateGalaxyStarterWorld(int mission) | A new world: a 3 x 3 rock board with a chest and an exit arrow; for a galaxy hub, the board with a world or mission sign (and a return sign) and Blupi's start. |
PackGalaxy, UnpackGalaxy | std::string PackGalaxy(int gamerSlot), std::optional<GalaxyPack> UnpackGalaxy(const std::string& pack) | The whole galaxy as one .galaxy file and back. |
ExportGalaxy, ImportGalaxy, DeleteGalaxy | std::filesystem::path ExportGalaxy(int gamerSlot), int ImportGalaxy(int gamerSlot, const std::filesystem::path& pack, int* demos = nullptr), std::filesystem::path DeleteGalaxy(int gamerSlot) | Export into shared/ (numbered, never replacing); import replacing the player's galaxy (old places into backups); delete after exporting a pack first. |
GalaxyName, SetGalaxyName | std::string GalaxyName(int gamerSlot), bool SetGalaxyName(int gamerSlot, const std::string& name) | The galaxy's name in galaxy/name.txt: letters, digits, spaces, - and ', at most 24 characters. |
The galaxy pack format
A .galaxy file is text headers with raw bytes between them: GALAXY_PACK 1, an optional name <size> entry with the galaxy's name, then world <mission> <size> and that many .vwr bytes per place, demo <position> <size> and the .gdemo bytes for each recording the player made in the galaxy, and finally END with the FNV-1a hash of everything before it in 16 hex digits. UnpackGalaxy refuses a damaged or truncated pack, another version, a duplicated place or position, a place that is not a galaxy's, or a world that does not load. A demo recorded with another simulation revision is left out and the galaxy is kept. Imported demos go to the shared demos folder, never over one of the player's positions.
How it is used
The editor saves through WorldEditor::SaveWorld (in src/GalaxyEggbert/Editor/WorldEditorUpdate.cpp, shortened). The first save of a visit backs the old file up; a save of a shipped world opened from play is also copied into campaign-edits/; a slot's save asks the host for a thumbnail:
void WorldEditor::SaveWorld(const Worlds::World& world)
{
if (backedUpPath_ != worldPath_)
{
(void)(outsideSession_ && !outsideBackupDir_.empty() ? BackUpWorldTo(worldPath_, outsideBackupDir_)
: BackUpWorld(worldPath_));
backedUpPath_ = worldPath_;
}
world.saveToFile(worldPath_);
if (outsideSession_ && !outsideCopyDir_.empty())
{
...
std::filesystem::copy_file(worldPath_, outsideCopyDir_ / worldPath_.filename(),
std::filesystem::copy_options::overwrite_existing, error);
}
if (!outsideSession_)
{
thumbnailRequest_ = ThumbnailPath(worldPath_);
}
...
}
When a player opens the editor, WorldEditor::EnterBrowser calls SeedShowcaseSlot(gamer, "worlds3d/world999.vwr") and SeedGallerySlot(gamer, "worlds3d/world998.vwr"). The galaxy screen opens GalaxyWorldPath() files and creates missing places from CreateGalaxyStarterWorld(). When a galaxy is imported or deleted, the host resets that player's galaxy progress in SaveData.
Rules and invariants
- Nothing is lost silently. Older worlds outside the slots are listed and never deleted. Seeding never overwrites, and the markers stop a deleted or edited slot from being seeded again. Deleting a slot or a galaxy place moves it into its backups. Restoring rotates the backups instead of discarding the current file. Deleting a whole galaxy exports a pack first.
- The disk is the truth. The editor never keeps a list of worlds in
SaveData; screens callRefresh()to rescan. - The shipped folder stays clean. Backups of edited shipped worlds go to
campaign-backups/, and no thumbnail is written beside them. On the desktopxnjttjpoedits the shipped file in place. On the web, where the shipped worlds are packed page data,main.cpplays the kept edits over them at every start, before any world is loaded. - A galaxy is a campaign. It uses the campaign's mission numbering (1, 10-140, 11-148, 199), so hubs, doors, gold bars and signs work without special cases.
- Platform differences are only paths. The web's
/saveand the download and file-picker transfer are persistence differences, not engine ones.
Tests
VerifyGEEditorStorage (tools/VerifyGEEditorStorage.cpp, registered with ge_add_editor_verify, runs in the build directory's verify-run/) covers the ten slots and older worlds, showcase and gallery seeding, the three backup generations and restore cycle, backup on the editor's first save, export and import through the world screen, galaxy paths and starter worlds, backups in another folder for xnjttjpo, the web's campaign edits and Pause's "Original", the galaxy screen's delete and restore, packing (damaged and invalid packs refused), export and import between players, the import list's paging by eight, galaxy names (including the on-screen keyboard), and galaxy recordings in packs. Live QA step files: tools/qa_steps/editor_backups.txt and editor_thumbnails.txt.
Related pages
- WorldEditor: the editing session that saves through these functions
- 3D World Editor and Your Own Galaxy: the player's side
- SaveData and Save Data & Resume: the player's other files in the same user folder
- DemoReplay: recordings that travel in a galaxy pack
- The .vwr format
- Configuration:
--userandGALAXY_USER_DIR