| name | punk-save-editor |
| description | Save-file editor for the game PUNK (SvelteKit + Tauri), built on a reverse-engineered save format. Use whenever working in D:\Repositories\punk-save-editor — editing the app, the save codec, or digging back into the game's code/assets. |
PUNK Save Editor
A save-file editor for the game PUNK (Steam app 2707980), runnable as both a website and a Tauri 2
desktop app from one SvelteKit codebase. The binary save format was reverse-engineered first; the app
is built on that codec. Repo: D:\Repositories\punk-save-editor (runs with bun).
Owner is Saskia. Ambitions beyond the current curated + raw editors: a visual module-grid editor,
and eventually a map editor.
Read the atlas
Deep knowledge lives in versioned repo docs — read the one that matches your task instead of
rediscovering it:
- docs/save-format.md — on-disk bytes: LZF container, Odin binary
token stream, every save file, the
world struct dump, the entities/ship layout.
- docs/game-code.md — get back into the game itself: decompile
Punk.Main.dll (ilspycmd), extract ScriptableObjects (UnityPy), subsystem map (resources/tanks,
modules/grid, capacity math) with class citations.
- docs/editor-internals.md — how the app is wired: the
$state.raw rule, the save/game/editor layer split, generated-data pipelines, in-browser e2e.
- docs/migration.md — the game-update runbook:
bun run extract,
what every warning means, known blast radii.
- docs/design.md — the design system: the three game fonts and their
pixel grids, the palette tokens, the game pixel
--u, integer-scaled art, the shared punk-*
utilities, and the .crt-screen scroller traps. Read before any styling work.
Golden rules (violating these corrupts saves or crashes the game)
- Never wrap decoded Odin save trees in deep
$state. A deep proxy stores mutations in its own
signal storage and never writes them back, so the serializer saves stale data (and aliased paths get
independent proxies). Keep trees in $state.raw, mutate the raw objects directly via oninput
handlers, refresh the UI with the version counter. This was the "edits don't persist" bug.
- Unit/ship resource
Values must stay ≥ 0. A negative value crashes PUNK on load (hangs the
loading screen). Clamp all resource edits to ≥ 0; ship resources clamp to [0, max].
- Never commit decompiled game code — keep decomp in the scratchpad. Extracted game assets
(icons, lookup JSON under
src/lib/game/) ARE committed — Saskia reversed the old blanket rule
on 2026-07-17. The Python venv lives at /.venv (gitignored), not in the scratchpad.
write(parse(x)) is byte-identical on every save file — rely on it; a round-trip regression means
the codec broke.
Environment (not in the repo)
| |
|---|
| Game install | C:\data\apps\Steam\steamapps\common\PUNK Playtest (Unity 6000.3.4f1, Mono) |
| Game code | …\Punk_Data\Managed\Punk.Main.dll — decompile with ilspycmd |
| Assets | …\Punk_Data\*.assets, level*, globalgamemanagers — read with UnityPy |
| Saves | C:\Users\alya\AppData\LocalLow\DefaultCompany\Punk\saves\save001 |
| Unity log | C:\Users\alya\AppData\LocalLow\DefaultCompany\Punk\Player.log (load-crash traces) |
Commands
bun run dev
bun run check
bun run lint
bun run check:style
bun run build
bun run extract
bun run check:data
- Always run the Svelte MCP
svelte-autofixer on any component you write/edit, until clean — hard
rule. Prefer the Svelte MCP docs tools when touching Svelte 5 features.
- Read docs/design.md before any styling work.
src/lib/components is the most edited area in
the repo; the design system is what keeps it from forking, and bun run lint enforces the part of
it a checker can see.
- bun is the package manager, never npm/yarn.
- Verify save-affecting changes with the in-browser e2e (
window.__punkTestDir hook) — Node tests
can't catch proxy-layer bugs. See docs/editor-internals.md.
Map of the code
src/lib/save/ — the save files: platform.ts (which runtime, and the folder plumbing all three
share), io.ts (SaveDir), lzf.ts (codec), odin.ts (reader/writer),
slot.ts (slot IO), zip.ts + backup.ts + backup-folder.ts (whole-folder zip backups and
restore), tree.ts (generic Odin accessors), vault.ts (vault
views/mutations), rundata.ts (run views), ship.ts (entities grid walk — seed of the grid editor).
src/lib/game/ — static game knowledge: data.ts (assets, names, module info/effects),
module-stats.ts, rich-text.ts, pixel-icon.ts, and the generated *.json (including
ui-sounds.json, the game's own UI sounds).
src/lib/sound.svelte.ts — plays those sounds (sound.play('click')) and owns the switch that
governs them. Sound follows the primitive, never the panel — see docs/design.md.
src/lib/editor/ — state.svelte.ts (EditorState), backup.svelte.ts (BackupState: backups and
restores, as editor.backups), settings.svelte.ts (the backup folder and the ask-on-load flag,
one singleton because the footer outlives the editor), inputs.ts (raw-tree input handlers).
src/lib/components/ — the primitives (restyle these, not the panels): Section, Dialog,
Button, NumberInput/InlineNumber/CounterCell, TextInput/Select, CloseBadge,
ModuleStatLine/ModuleGroupHeading. Then panels/ (one per editor section),
ModuleList/ModulePicker/RichText/RawTree,
BackupPrompt/RestoreDialog/OptionsDialog (the backup surfaces) +
BackupFolderRow/ (the two pieces of them that appear twice),
(all four resource art sizes)// (the area-of-effect
diagram the game draws on power cores and boosters) + (picking that shape) +
(painting one — hand-painted fields must stay , see
docs/game-code.md; the saved library lives in ).