Saves
Usage guide of save.persistence: slots, quick save and autosave, the Load / Save screen, storage, the save.write and save.load hooks, modding, engines.
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 #
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<slots>; 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?)(defaultauto) andload(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 withmigrated: trueunlesscompatibilityisstrict. - 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/<projectId>/<slot>. 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 autosaveinterval,mission,checkpoint,zone,scene,event). A tap may changelabel. A cancel writes nothing, emitssave.failedand makessave(),quicksave()andautosaveNow()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.
{ "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 |