saving

A system for saving a player's progress across reloads, crashes, and game updates. It includes versioned changes to saved data, so an older save can be opened by newer game code.

In plain words
What is it for?
Use it to build saves and autosaves, load progress, migrate old save formats, handle corrupted data, and provide reset or start-over behavior.
Why use it?
It prevents progress from silently disappearing when save data is damaged or its format changes. Failed saves become a fresh game with a report instead of an unhandled error.

Skill for Claude CodeCodex

Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

agentmods
npx agentmods add skills/plausibleventures/lattice/saving
Any agent
npx skills add plausibleventures/lattice --skill saving
Clone the repo
git clone --depth 1 https://github.com/plausibleventures/lattice

Made for: Claude Code, Codex.

Per session 70 Skills are progressive disclosure: only the name and description are preloaded; the body loads when the skill is used.
When invoked 3,245 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 0 findings. Scan, not verified.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5 $0.00070 $0.03245
Opus 5 $0.00035 $0.01622
Sonnet 5 $0.00014 $0.00649
Haiku 4.5 $0.00007 $0.00325

Measured 2d ago against content hash 45bd4e3cabd8, method: parsed. Prices are Anthropic first-party input rates as of 2026-08-30, from the pricing page.

Security

Grade A, and why

saving scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 2d ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Nothing flagged

None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.

skills/saving/SKILL.md · 283 lines

How it starts

The opening of the file, as written. The whole thing — 283 lines — stays where its author put it; the contents beside it link to each section on GitHub.

Saving

Three ideas, and every failure mode in this skill follows from one of them.

  • The chain is the version. createStore reads the current version off the migration chain's head, so declaring version 7 while shipping a chain that ends at 6 is not a bug you can write down.
  • Every failure is a value. open() never throws, for any content whatsoever. Seven ways a save fails to become a state, and every one degrades to a fresh game with a report.
  • A save stores causes, not consequences. Persist the player's brand hue, never the #rrggbb it derives to.

The alternative to a chain — parsed.version === SAVE_VERSION with a fallback to newGame() — is not a migration policy. It is a delete. The game this kit was extracted from said so in its own source: "a bump is not a migration — it is a deletion of every player's campus."


A save, its chain, and its autosave

import { asEpochMillis } from '@latticekit/core';
import { browserStorage, createStore, migrations, scheduleFrom } from '@latticekit/persist';
import type { Recognize } from '@latticekit/persist';
import type { Loop } from '@latticekit/loop';

interface V1 { readonly version: 1; readonly coins: number }
interface V2 { readonly version: 2; readonly wallet: { readonly coin: number }; readonly hue: number }

// A recognizer returns the value TYPED, or throws naming the field. Never a boolean —
// a boolean has already discarded the value that was wrong, so it cannot name it.
const isV1: Recognize<V1> = (value) => {
  const coins = (value as { coins?: unknown }).coins;
  if (typeof coins !== 'number' || !Number.isFinite(coins)) {
    throw new RangeError(`save.v1.coins: expected a finite number, got ${String(coins)}`);
  }
  return { version: 1, coins };
};

const isV2: Recognize<V2> = (value) => {
  const v = value as { wallet?: { coin?: unknown }; hue?: unknown };
  const coin = v.wallet?.coin;
  // Number.isFinite is the load-side guard that matters: JSON turns Infinity and NaN into
  // `null`, under a valid checksum, and they come back as NaN on the next tick.
  if (typeof coin !== 'number' || !Number.isFinite(coin)) {
    throw new RangeError(`save.v2.wallet.coin: expected a finite number, got ${String(coin)}`);
  }
  const hue = typeof v.hue === 'number' && Number.isFinite(v.hue) ? v.hue : 28;
  return { version: 2, wallet: { coin }, hue };
};

const chain = migrations(1, isV1)
  .step(2, 'one coin counter became a wallet', (v1) => ({
    version: 2 as const, wallet: { coin: v1.coins }, hue: 28,
  }), isV2)
  .seal();

export function openSave(loop: Loop) {
  const store = createStore({
    key: 'lighthouse:save',
    chain,
    adapter: browserStorage(),
    fresh: (): V2 => ({ version: 2, wallet: { coin: 0 }, hue: 28 }),
    // REQUIRED, and it has no default on purpose. See below.
    now: () => asEpochMillis(Date.now()),
  });

  const opened = store.open();     // never throws, whatever is in storage
  let live: V2 = opened.state;

  const auto = store.autosave(() => live, { schedule: scheduleFrom(loop.real) });
  return { store, opened, auto, set: (next: V2) => { live = next; } };
}

Read the full file on GitHub · 283 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 2d ago First seen · 283 lines · 70 tokens per session scan A 45bd4e3cabd8

Subscribe to this mod's changes

saving is a skill published in the GitHub repository plausibleventures/lattice (35 stars, last pushed 10d ago), licensed MIT. It adds 70 tokens to every session and 3,245 once invoked, about $0.0003 per session on Opus 5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-30.

Related

Other skills, from other repositories

assets-get-data

Get asset data from the asset file in the Unity project — every serializable field and property. Supports token-saving path-scoped reads via paths or viewQuery. Use 'assets-find' to find the asset first.

IvanMurzak/Unity-MCP · 50 tokens

gameobject-component-destroy

Destroy one or more Components from a target GameObject. Missing (null) components are skipped — they cannot be destroyed. Use 'gameobject-find' and 'gameobject-component-get' to identify the components first.

IvanMurzak/Unity-MCP · 49 tokens

gameobject-set-parent

Reparent a batch of GameObjects under a new parent in the currently opened Prefab or active Scene. Per-item failures are reported in the returned status string instead of aborting the batch. Use 'gameobject-find' to locate the GameObjects first.

IvanMurzak/Unity-MCP · 56 tokens

assets-prefab-save

Save the currently opened prefab edit stage back to its prefab asset without exiting the stage. Pair with 'assets-prefab-open' to enter the edit mode first.

IvanMurzak/Unity-MCP · 37 tokens

console-get-logs

Retrieve Unity Editor logs from the MCP plugin's LogCollector, optionally filtered by log type or time window. Useful for debugging and monitoring Editor activity.

IvanMurzak/Unity-MCP · 35 tokens

package-remove

Uninstall a UPM package from the Unity project. Modifies manifest.json and may trigger a domain reload — the final result is delivered after the reload via the request's requestId. Built-in packages and packages that are dependencies of others cannot be removed. Use 'package-list' to list installed packages first.

IvanMurzak/Unity-MCP · 68 tokens