# Save slots (save.persistence) Save and load the whole game into manual slots, a quick save and an autosave, with a Load / Save screen. Readable offline: `gripforge docs save-persistence`, or the MCP tool `gripforge_docs` (topic `save-persistence`). ## What it is for Every slot holds a whole `gripforge.save/1` save of the game: the game state, the entities, the mission run and the state of every kit, with its date, play time, location, picture and game version. The model is the same on the web, Godot, Unity and Unreal. It is installed by default in every new game, with `ui.menu`, `input.remap` and `settings.graphics`. It requires `runtime.core` (the host's own save) and `game.engine`. Configure it freely; remove it only when asked. ## Turn it on ```text gripforge_gamekit_install { project_id: "gkp_…", id: "save.persistence" } gripforge_gamekit_configure { project_id: "gkp_…", id: "save.persistence", config: { "slots": 5, "autosaveSeconds": 120, "gameVersion": "0.3.0" } } ``` Only the keys that differ from the defaults need to be written. The full reference is `gripforge kit api save.persistence`. ## Settings and defaults | Key | Default | Meaning | | --- | --- | --- | | `slots` | 3 | manual slots (1 to 20) | | `quicksave` | `true` | a quick-save slot, saved with F5 and loaded with F9 (input actions `quicksave` and `quickload`, rebindable with `input.remap`) | | `autosaveSeconds` | 60 | interval autosave in seconds of play; 0 means none | | `autosaveOn` | `interval`, `mission`, `checkpoint`, `zone`, `scene` | what autosaves | | `autosaveTopics` | `[]` | other bus events that autosave (a boss defeated, a level reached) | | `autosaveMinGap` | 5 | seconds of play between two event autosaves | | `storage` | `local` | `local` or `cloud` | | `compatibility` | `lenient` | `lenient` loads saves of another kit set or older versions; `strict` refuses them | | `gameVersion` | `1.0.0` | version written in each save and shown on the Load screen | | `locationVar` | empty | variable holding the location shown on a save | | `thumbnail`, `thumbnailWidth` | `true`, 192 | a picture of the game with each save (16:9, 32 to 512 px) | ## Slots Manual slots are `slot1` to `slot`; `quick` is the quick save and `auto` the autosave. The service accepts `2`, `"2"` or `slot2`. Each save keeps the date (`savedAt`), play time, location (`setLocation(name)`, else `locationVar`, else the title of the current scene, else the last zone entered), the picture, the game version and a counter `seq`: the most recent save has the highest, and that is what Continue loads. - `save(slot?, label?)` (default `auto`) and `load(slot)` run at the end of the current tick when asked during one (a trigger, a mission event, the quick-save key), at once otherwise. - A load is refused (`save.failed`) for an empty or unreadable slot or a save of another project. A save made with another kit set loads with `migrated: true` unless `compatibility` is `strict`. - A save taken while a menu pauses the game is stored as playing, so a load never leaves the game paused without a menu. ## Autosave Into `auto`, on the triggers of `autosaveOn`: `interval` (every `autosaveSeconds`), `mission` (`mission.completed`), `checkpoint` (`checkpoint.reached`, `course.checkpoint`), `zone` (`zone.entered`), `scene` (after a scene change, never on the start scene nor on a scene rebuilt by a load), plus any topic of `autosaveTopics`. Event autosaves are at most one per `autosaveMinGap`. `autosave(false)` pauses autosave, `autosaveNow(reason)` forces one. ## Load / Save screen The kit is the options tab `saves` of `ui.menu`: one row per slot with picture, date, play time, location and version, and the actions Load, Save (manual slots) and Delete. Overwriting and deleting ask for confirmation. `ui.menu` opens it from its buttons `load` (title and pause) and `save` (pause); nothing can be saved on the title screen. A load from the screen closes the menu, and Continue on the title loads `latest()`. `setOption("mode", "load" | "save" | "manage")` picks the actions shown. ## Service, events, variables Service `save.persistence`: `slotIds()`, `slots()` and `info(slot)` (`{ slot, label, tick, elapsed, storage, kind, savedAt, playtime, location, thumbnail, gameVersion, seq, format }`), `latest()`, `has(slot)`, `save(slot?, label?)`, `load(slot)`, `remove(slot)`, `quicksave()`, `quickload()`, `autosave(on?)`, `autosaveNow(reason?)`, `storage()`, `exportSlot(slot)`, `importSlot(slot, json)`, `setLocation(name)`, `location()`, `setClock(ms, offsetMinutes)` (a fixed clock for replays and tests), `setThumbnailSource(fn)` (web), and the options tab (`optionsTab()`, `optionFields(lang)`, `setOption(id, value)`). Events: `save.saved { slot, label, tick, auto, kind, reason }`, `save.loaded { slot, tick, migrated }`, `save.removed { slot }`, `save.failed { slot, op, reason }`. Variables: `save.last`, `save.autosave`. Script action: `save_game { slot?, label? }` (default slot `auto`), for example `{ "save_game": "slot1" }`. A second capability, `save.replays`, is Rust only: validated immutable archives and separate viewing and preferences through `request(command)`. ## Storage and the picture `local` is the host's local store: the browser's local storage in a page (memory in headless runs), files under `user://gripforge_saves` on Godot, `Application.persistentDataPath` on Unity, save game slots on Unreal, or a store the application passes. `cloud` is the store the application passes as `cloud`; without one the kit warns once and saves locally (`storage()` says which is used). Keys are `gripforge.save//`. Records are `gripforge.slot/2`; older `gripforge.slot/1` records are read as they are and become `slot/2` at the next save in that slot. The picture is a capture of the renderer: the web play page installs the source, Godot reads the viewport, Unity renders the main camera, Unreal a scene capture. It is taken when a menu opens (the frame under it) and used by the saves made from that menu. Headless runs keep no picture. ## Hooks and modding - `save.write { slot, label, kind, auto, reason }`: asked before every save (`reason`: `manual`, `quick`, `menu`, `action`, or for an autosave `interval`, `mission`, `checkpoint`, `zone`, `scene`, `event`). A tap may change `label`. A cancel writes nothing, emits `save.failed` and makes `save()`, `quicksave()` and `autosaveNow()` answer false; a refused interval autosave is asked again one interval later. - `save.load { slot, label, kind, tick, migrated }`: asked for a load that passed the kit's own checks. A cancel loads nothing and the Load screen stays open. ```json { "id": "no_save_in_fight", "hook": "save.write", "if": [{ "var": "boss.active", "op": "==", "value": true }], "cancel": true, "reason": "in_fight" } ``` `remove(slot)` and `importSlot` are not intercepted (the player's own management of the slots). A mod may override `slots`, `quicksave`, `autosaveSeconds`, `autosaveOn`, `autosaveTopics` and `autosaveMinGap`. Not moddable: `storage`, `compatibility`, `gameVersion`, `locationVar`, `thumbnail`, `thumbnailWidth`. The hooks run on the web and Rust. ## Engines | Engine | Where the saves go | | --- | --- | | Web and native player | local storage of the page, or a store the application passes | | Godot | files under `user://gripforge_saves`, picture from the viewport | | Unity | `Application.persistentDataPath`, picture from the main camera | | Unreal | save game slots, picture from a scene capture of the player camera | ## When it does not do what you expect | Symptom | Cause and fix | | --- | --- | | Continue does nothing | no save exists yet; Continue loads `latest()` | | A load is refused | empty slot, another project, or `compatibility: "strict"` with another kit set; read `save.failed.reason` | | Autosave is rare | `autosaveMinGap` holds event autosaves; raise `autosaveSeconds` or list more `autosaveOn` triggers | | No picture on a save | headless run, or `thumbnail` is `false` | | Saving is refused in a fight | a `save.write` tap cancels it | | Cloud saves stay local | the application passed no `cloud` store; `storage()` shows `local` |