All features, backlog, and decisions of this project will be documented here.
Version-sensitive patches are listed in docs/PATCHES.md.
The server owns the capacity map and every inventory change.
- Set the capacity of standard held bags by item-code pattern
(
BagCapacityOverrides) - Existing bags grow or shrink without hiding or deleting contents
- Placed chests, vessels, crates, and other block inventories. Those need storage-specific adapters; they don't share one safe capacity contract.
Stowage is required on the server and every client. The server reads
stowage.json. Clients receive the resulting bag attributes through
normal game synchronization.
Compatibility is the standard held-bag behavior, not a list of mod names.
A custom IHeldBag that ignores backpack.quantitySlots is reported and
left alone. Guessing at an unsupported storage type is worse than leaving
it.
Bag definitions change during AssetsFinalize, before the server sends
collectible attributes to clients.
A dormant oversized bag must keep reporting its persisted size. Showing
only the new size before migration would hide items even if the data still
existed. Equipped bags migrate on equip or at PlayerNowPlaying;
ground-stored and entity-attached bags migrate when opened.
PlayerNowPlaying is the first join event with a ready inventory. Passive
reconstruction has no authenticated player or safe drop position.
GetOrCreateSlots and GetQuantitySlots stay small universal patches.
Their job is to keep every persisted slot readable until server migration
finishes, because the asset layer cannot repair an existing item stack.
Passive slot reads only touch the current item stack. Actual migration
runs from verified server main-thread inventory and interaction paths.
On shrink: compact retained contents, route overflow into valid player storage (never back into the bag being reduced, and never into slots beyond another bag's pending capacity), drop any remainder, and only then save the smaller layout. If migration cannot finish, completed transfers stay saved and leftover overflow slots stay readable for a later interaction.
Detached ItemSlotBagContent wrappers must sit in an inventory that owns
those same instances, or ItemSlot.TryPutInto rejects their dirty
notification.
An entity-attached bag opens through packet 1001. The slot index lives in
the upper bits (packetid >> 11). Lower ids are slot clicks.
Rules are checked in file order. The first match wins, so a specific rule can sit above a broad fallback.
The server loads, cleans, and stores stowage.json during asset
finalization. Clients don't read a local copy. Restart the dedicated
server or reopen the singleplayer world after editing.
The full pattern syntax is in README.md.