Version: 1
All ItemVault persistence uses JSON. The top-level envelope carries a version
field so future shape changes can add upgrade steps.
Envelope
{
"version":1,
"inventory": {... }
}| Field | Type | Notes |
|---|---|---|
version |
int |
Schema version. Increment on any breaking change. |
inventory |
object |
The inventory shape (see below). |
Inventory
{
"id":"player_inventory",
"name":"Inventory",
"max_containers":12,
"containers": [... ]
}| Field | Type | Notes |
|---|---|---|
id |
string |
Stable identifier. |
name |
string |
Display name. |
max_containers |
int |
Optional operational container cap; omitted when the default is used. |
containers |
array |
Array of container objects. |
Container
Two modes — free-list and slot-based. The mode field determines which
shape is used.
Free-list mode
{
"type":"backpack",
"mode":"free",
"stacks": [... ]
}Slot-based mode
{
"type":"equipment",
"mode":"slot",
"type_config": {
"slot_based":true,
"slot_count":2,
"max_stacks":20,
"max_weight":0,
"slot_templates": [
{"allowed_category":"weapon","stackable_only":false,"max_override":1 },
{}
]
},
"slots": [... ]
}Each element in slots is a slot object. Empty slots serialize as {};
occupied slots serialize as { "stack": { ... } }.
"slots": [
{},
{"stack": {"id":"iron_ore","qty":5 } }
]| Field | Type | Notes |
|---|---|---|
type |
string |
The ContainerType.id this was created from. |
mode |
string |
"free" or "slot". |
type_config |
object |
Optional operational capacity and slot-constraint configuration. Older saves may omit it. |
stacks |
array |
Free-list mode: array of stack objects. |
slots |
array |
Slot-mode: array of slot objects ({} or { "stack": { ... } }). |
type_config.slot_templates is parallel to the concrete slot indices. Each
template may contain allowed_category, stackable_only, and
max_override; omitted values use their defaults. The saved configuration is
restored before stacks so subsequent additions enforce the same constraints.
Stack
{
"id":"iron_ore",
"qty":5
}Optional fields are omitted when absent (not written as null).
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string |
yes | ItemDefinition.id — the stable key. |
qty |
int |
yes | Stack quantity. |
iid |
string |
no | Retained item-instance identity. |
sid |
string |
no | Concrete stack-boundary identity. |
max |
int |
no | Max-stack override (0 or absent = use default). |
wt |
float |
no | Weight override (0 or absent = use default). |
resources |
object |
no | Numeric retained resources by stable resource id. |
Missing-id policy (locked)
When loading, if a stack's id does not resolve to an ItemDefinition via
ItemDatabase:
- Log a
push_warningwith the unresolved id and the container it came from. - Drop that stack — skip it, do not insert a placeholder.
- Continue loading the rest of the save. Do not abort.
This means old saves survive the removal of items gracefully — the missing stacks vanish with a warning rather than corrupting the load.
Version compatibility policy
InventorySave.from_dict() enforces strict version checking:
- Current version saves load normally.
- Missing
versionfield is rejected withpush_errorand returnsnull. - Older versions are rejected unless a migration callable was registered via
InventorySave.register_migration(old_version, callable). The callable receives the old dict and must return a dict with"version"set to the currentSCHEMA_VERSION. - Newer versions are always rejected with
push_errorand returnnull. We cannot know future schema shape; refusing prevents silent data loss.
This means old saves are not silently loaded through the current schema without
an explicit migration step. Callers should check the return value for null
and surface an error to the user.
Migration state for SCHEMA_VERSION = 1
Version 1 is the initial schema. No migrations are registered in InventorySave;
any save missing version or with version < 1 is rejected. When SCHEMA_VERSION
is incremented, the first migration should be registered for 1 and return a dict
with the new version.
Version-bump rule
Any change to the JSON shape (renamed keys, restructured nesting, removed
fields) must increment version and add an upgrade step that converts the
old shape to the new one. Adding optional fields with sensible defaults does not
require a version bump.