# SaveHandle

> Talos 0.7 · Since 0.6

A small JSON store that outlives the run: a best score, an unlocked level, a chosen setting.

It survives everything `ctx.state` does not — {@link SessionHandle.restart}, {@link SessionHandle.loadScene}, and the process. One store per application, shared by every scene and behaviour of it, so pick keys as you would for any shared namespace (`"hoplane.best"`). **Where it lives.** `argus run <project>` and an exported game keep it in the user's data folder under the application's id (`%LOCALAPPDATA%\\Argus\\saves\\<application.id>\\save.json`, or `$XDG_DATA_HOME/argus/saves/…`), or where `--save-dir` says. A bare scene, a test world and the editor's Play have no file: the store works for the length of the run and is then gone, which keeps a playtest from inheriting yesterday's. Editor Play keeps saves in memory; `argus run` and exported games write to the per-app save folder. `critic.play` and `export.run` give the game a folder of their own, removed when it stops. **When it is written.** At the end of a tick in which something changed, to a temporary file that is then renamed over the old one, so a crash mid-write leaves the previous save intact. A write that fails is logged once and never throws: a full disk must not end a run. It is **not** part of a snapshot, by design: restoring a world to an earlier tick must not un-earn a high score. Anything a replay has to reproduce belongs in `ctx.state`. Limits, refused with a thrown `SaveError` whose message starts with the code: a key is 1–64 characters of `A-Z a-z 0-9 . _ -` (`save-bad-key`); the whole store is at most 256 KiB of JSON (`save-full`). Values are JSON — no functions, no handles, no `undefined`.

## Signature

```ts
export interface SaveHandle {
  /** The value saved under `key`, or null. */
  get(key: string): unknown
  /** Save a JSON-serialisable value under `key`. */
  set(key: string, value: unknown): void
  /** Forget `key`. True if there was something to forget. */
  delete(key: string): boolean
  /** Every saved key, sorted. */
  keys(): string[]
}
```

Import: `import type { SaveHandle } from "@argus/api"`

## Members

| Member | Signature | Description |
|---|---|---|
| `get` | `get(key: string): unknown` | The value saved under `key`, or null. |
| `set` | `set(key: string, value: unknown): void` | Save a JSON-serialisable value under `key`. |
| `delete` | `delete(key: string): boolean` | Forget `key`. True if there was something to forget. |
| `keys` | `keys(): string[]` | Every saved key, sorted. |

## Example

The tested fixture `fixtures/app/agent.ts`.

```ts
import type { Behavior } from "@argus/api"

// Sends a nav agent to `props.target` once, then reports arrival through ctx.state so the
// test can read it without polling the crowd directly.
export default {
  init(ctx) {
    ctx.state.arrived = false
    ctx.nav?.goTo(ctx.props.target as [number, number, number])
  },
  update(ctx) {
    if (ctx.nav?.arrived) ctx.state.arrived = true
  },
} satisfies Behavior
```
