Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
The English summary is being prepared. Showing the original description.
Use when adding or changing save games, settings or progression storage - a versioned save format with a tested migration chain, atomic writes with backup and checksum, per-engine storage locations, safe serializers, and the round-trip/migration/corruption tests.
The English summary is being prepared. Showing the original description.
See how to installReview the instructions the agent will receive before you install.
A save file outlives the code that wrote it. Players carry saves across updates, crashes happen mid-write, and a lost 40-hour save is the bug players remember. Every save has a version, every older version has a tested migration, and a write never destroys the last good save.
Core principle: Versioned, migrated, written atomically, checked on load — and never deserialized with a format that can execute code.
{
"version": 3,
"savedAtUtc": "2026-10-08T12:00:00Z",
"player": { "levelId": "forest_02", "position": [12.5, 0, -4.0], "hp": 72 },
"inventory": [{ "itemId": "sword_iron", "count": 1 }],
"flags": ["met_blacksmith", "boss_forest_defeated"]
}
GameObject, Node or AActor references, no array indices into data tables — stable IDs only (data-driven-gameplay-design).store_var without objects) is fine when size matters — the version field stays first.type Migration = (save: any) => any;
const migrations: Record<number, Migration> = {
1: (s) => ({ ...s, version: 2, inventory: s.items.map((id: string) => ({ itemId: id, count: 1 })) }),
2: (s) => ({ ...s, version: 3, flags: Object.keys(s.questState ?? {}).filter((k) => s.questState[k] === "done") }),
};
export function upgrade(raw: any): SaveV3 {
if (typeof raw?.version !== "number") throw new SaveError("unversioned");
if (raw.version > CURRENT) throw new SaveError("from a newer game version"); // refuse, never guess
let s = raw;
while (s.version < CURRENT) s = migrations[s.version](s);
return SaveV3Schema.parse(s);
}
tests/fixtures/saves/v1.json, v2.json).save.json.tmp, flush to disk.save.json.bak.save.json (atomic on the same volume).On load: verify the checksum and parse; on failure fall back to .bak, tell the player, and never crash or silently start a new game over the old one.
Serialize on the main thread if the engine requires it, but do file I/O off it (or between frames at a checkpoint) — a save must not hitch gameplay. Autosave at checkpoints, not every frame.
| Engine | Location / API |
|---|---|
| Unity | Application.persistentDataPath + System.IO; PlayerPrefs only for tiny settings |
| Godot | user:// with FileAccess; ConfigFile for settings |
| Unreal | USaveGame subclass + UGameplayStatics::AsyncSaveGameToSlot / LoadGameFromSlot; keep a version UPROPERTY in it |
| Web | IndexedDB for saves (async, larger; e.g. idb-keyval), localStorage only for small settings — it is synchronous, ~5 MB and can throw in private mode or when full |
| Bevy / Rust | serde to the platform data dir (directories crate) |
| pygame / MonoGame | the OS user data directory, never the install folder |
Consoles and some stores impose their own save APIs and size limits — follow the platform layer the repository already has.
BinaryFormatter: obsolete and insecure; do not use it in Unity, MonoGame or Godot C#.bytes_to_var_with_objects / str_to_var on untrusted data, and loading a user-supplied .tres/.res, can instantiate scripts — use JSON, ConfigFile, or FileAccess.get_var(false) (objects disallowed).pickle for pygame saves: a shared or downloaded save becomes code execution — use JSON.vN.json → upgrade → matches the expected current-version object..bak and reports; no exception escapes.BinaryFormatter, pickle, or object-enabled Godot variant decoding in a save path.No reviews yet. Tell others what you thought.
No login needed. Share how it worked for you, ask a question, or note where it did not run.
No comments yet. Be the first to write one.
Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
The English summary is being prepared. Showing the original description.
Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping
The English summary is being prepared. Showing the original description.
Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal
The English summary is being prepared. Showing the original description.
Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task
The English summary is being prepared. Showing the original description.
How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.
The English summary is being prepared. Showing the original description.
Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here
The English summary is being prepared. Showing the original description.