Native inventory values

Inventory.Open("village.items.json") loads an immutable catalog from the current project's items/ directory. Open once and create containers from that catalog; separately opened catalogs are separate rule domains and cannot exchange items. Invalid resource paths, malformed catalogs and invalid constructor arguments raise Lua errors. Empty catalogs are valid and can create empty containers.

local catalog = Inventory.Open("village.items.json")
local bag = catalog:Create(30)
local chest = catalog:Create(12)
local equipment = catalog:Create(4, {"subweapon", "head", "chest", "legs"})
local item = catalog:GetItem("herb") -- owned definition table, or nil
local accepted = bag:Add("herb", 8)
world_pickup_count = world_pickup_count - accepted.moved
local result = bag:Move(1, chest, 1) -- one-based slot indices

Containers are native userdata owned by Lua, independent of actors. Keep them in component instance state and use explicit snapshots for saves. Native operations run on the owner thread without invoking script callbacks. No inventory update loop or per-frame polling is required. Returned item/slot tables are independent copies; changing them does not change the catalog or container.

Catalog

Version 1 resources use { "version": 1, "items": [...] }. A definition requires id, display_name, kind, and max_stack. Optional fields are description, equipment_slot, health_restore, attack, defense, move_speed_bonus, icon, tool_power, places and use. Kinds:

Kind Rules
material No equipment, combat stats or healing.
food Positive health_restore; no equipment or combat stats.
weapon subweapon slot, max_stack 1, attack; no armor stats.
armor head/chest/legs slot, max_stack 1, defense/move_speed_bonus; no attack.
tool max_stack 1, tool_power >= 1 (mining/chopping strength), optional attack, no slot or subweapon.
placeable Stackable; places names what it places (a terrain or object the game interprets).
consumable Stackable; use names the effect it is used up for (for example summon:eye); may restore health.

icon is an optional image path relative to images/ for any item; tool_power, places and use belong only to their kind. Numeric ranges and strict parser rules are specified by the executable ItemCatalog validator. Unknown/duplicate fields and invalid combinations are rejected. Gameplay applies these values explicitly; the catalog has no implicit game effect.

IDs are case-sensitive ASCII [A-Za-z0-9][A-Za-z0-9_.-]{0,63}. Catalogs are bounded to 4096 items and 4 MiB. Native catalog allocations are not part of the Lua allocator quota; the agent supervisor's deadline is not an RSS sandbox.

Queries and mutations

Call Result
catalog:Create(capacity, filters?) New empty container, capacity 1..100. Filters omitted/empty or exactly one string per slot; empty string accepts any item.
catalog:GetItem(id) Definition copy or nil.
container:GetCapacity() Capacity.
container:GetRevision() Monotonic integer; increments once per changed transaction.
container:GetSlot(index) {item_id, count, instance_id}. Empty means "", 0, "".
container:Snapshot() Dense array of copied slots, including empties.
container:Add(id, count, instance_id?) Fill existing stacks then empty slots. Report actual acceptance.
container:Remove(index, count) Remove exact positive count; insufficient count fails without change.
container:Move(source, destination, target, count?, allowSwap?) Count omitted/0 means whole stack; allowSwap defaults true.
container:Split(source, destination, target, count) Move a positive proper fraction of a stack to an empty slot.
container:TransferAll(source, destination) Fill all destination stacks then empty slots; never swap; same container rejected.
Inventory.RestoreGroup(entries) Validate all candidate snapshots before committing any.

Mutations return {ok, code, moved, remaining, changed}. remaining is the unaccepted part of this operation's requested quantity, not necessarily the source slot's final count. A partial Add/Transfer succeeds with a nonzero remainder; no space fails with moved=0. Same-slot Move succeeds without changes. Identical-value swaps also leave revisions unchanged. Normal failure codes include invalid_argument, invalid_slot, invalid_count, unknown_item, invalid_instance, no_space, insufficient_items, slot_filter, stack_limit, duplicate_instance, capacity_mismatch, duplicate_model, incompatible_catalog, swap_not_allowed, invalid_split, destination_occupied, same_container and empty_source.

A Move merges if the target has matching stack space. Otherwise a whole move may swap, including a full matching stack. Both slot filters are checked. Targeted Move and TransferAll are separate operations; Unity's combined PlaceSmart (all matching stacks, then clicked-slot swap) is not a dedicated API in this slice. UI behavior must not claim that combined ordering unless it implements it.

Items with max_stack 1 require a nonempty instance_id in the same identifier syntax. Their quantity is exactly one. Stackable items require empty instance IDs. IDs survive moves, swaps and saves. Uniqueness is enforced within a container and across all containers participating in a transaction. The game owns allocation and world-wide uniqueness; this API is not a global item identity registry.

Atomic restoration

local result = Inventory.RestoreGroup({
    {inventory=bag, slots=loaded.bag},
    {inventory=chest, slots=loaded.chest},
    {inventory=equipment, slots=loaded.equipment}
})
if result.ok then
    -- Apply already-validated scalar game state; do not discover errors here.
end

Entries and slot arrays are dense raw tables. Every slot has exactly item_id, count, instance_id. Extra fields, missing fields, sparse/mixed keys, bad counts, unknown items, filters and duplicate instance IDs fail without mutations. Parsing does not run table __index, __len or __pairs. Snapshots must match container capacities. Validate health, pose, hotbar map and other game-owned fields before RestoreGroup. Hotbar maps reference inventory indices; they do not own copies of items. The native model does not provide Unity save compatibility.