Veduta

Saves

A save keeps what must outlive a play: progress, settings, high scores. It is a Lua table of numbers, strings, booleans and tables of them, stored under a name.

local state = {gold = 0, level = 1, party = {"mira"}, flags = {}}

function game.init()
  local saved = save.read("slot1")
  if saved then
    state = saved
  end
end

local function checkpoint()
  local ok, err = save.write("slot1", state)
  if not ok then
    message = "COULD NOT SAVE"   -- a full or missing card: the game goes on
    print(err)
  end
end
FunctionDoes
save.write(name, table)stores the table, replacing the save of that name; true, or nil and a message
save.read(name)a new table with what was saved, or nil when there is no such save
save.remove(name)deletes the save; true, or nil and a message
save.list()the names of the saves, sorted
save.best(key [, value [, "low"]])a record: see Records

Names follow the asset rule: 1 to 64 characters of a-z, 0-9, _ and - (slot1, settings, best_times).

Records

save.best keeps high scores and best times in one save, best, without reading and writing it yourself:

local function level_done(score, seconds)
  local best, new = save.best("score", score)              -- kept when higher
  local fastest, faster = save.best("time_1", seconds, "low") -- kept when lower
  if new then ui.toast("NEW HIGH SCORE") end
  return best, fastest
end

function game.draw()
  hud.text(4, 4, "BEST " .. (save.best("score") or 0))     -- nil before the first
end

With a value it stores it when it beats the record and returns the record and whether it is new; without one it returns the record, or nil.

Settings

settings is a table that saves itself: setting a key writes the save settings at once, and the next run reads it back.

function game.init()
  settings.defaults{volume = 5, shake = true, lang = "en"}   -- for keys never set
end

local function toggle_shake()
  settings.shake = not settings.shake
end

settings.reset() forgets every value (the defaults stay), settings.all() returns a copy of them all. The values follow the rules of a save. lang.set(code) keeps the language in settings.lang; see Game Toolkit for texts in several languages with T.

In tests, list best or settings in the scenario's "saves" to start with a record or an option already set.

What a save can hold

Functions, entities, coroutines, a table that contains itself, keys that are neither strings nor part of a list, nan and infinities are errors when writing: keep entity names or positions in the save, not entities.

A table read back is a new table, with its keys in sorted order.

Where saves live

Where the game runsSaves
The consoleon its SD card, saves/<game>/<name>.json
The simulator (F5)out/saves/<name>.json in the project
VEDUTA_SAVE_DIR setin that folder
Tests, simulate, fuzz, renderin memory only: see below

A save is a small JSON file you can read and edit. Writing goes to a temporary file first, synced to the card, and the previous save is kept until the new one is in place, so a console switched off in the middle of a save still has one of the two.

To start the simulator with no saves, delete out/saves. To back up the progress of a game on the console, copy the card's saves folder.

Saves in tests

A test run never reads or writes the saves of the simulator or the console, so it plays the same everywhere. A scenario lists the saves the run starts with; what the game writes stays in memory for that run, and each write or removal is a trace event a scenario can count:

{
  "veduta": "scenario/1",
  "scene": "title",
  "ticks": 40,
  "saves": {
    "slot1": { "gold": 120, "level": 3, "party": ["mira", "tobi"], "flags": { "bridge": true } }
  },
  "inputs": [
    { "tick": 1, "press": ["a"] },
    { "tick": 2, "release": ["a"] }
  ],
  "expect": [
    { "tick": 40, "entity": "hero", "path": "state.gold", "op": "==", "value": 120 },
    { "tick": 40, "trace": "save_write", "count_max": 0 }
  ]
}

Write one scenario that starts from an empty game and one per save that matters: a new game, a save in the middle of the story, a save from an older version of the game.

Changing what a save holds

A save written by an older version of your game may lack fields the new one expects. Keep a version number in it and fill in what is missing when reading:

local VERSION = 2

local function load_slot(name)
  local s = save.read(name)
  if not s then
    return nil
  end
  if (s.version or 1) < 2 then
    s.flags = s.flags or {}   -- added in version 2
  end
  s.version = VERSION
  return s
end

Veduta v2 (Lua games, release candidates). This wiki is built from wiki/ in the engine's repository, where its examples are tested, and published with every engine release on https://veduta.roomve.it/docs/.