Quick Design Spec: Inventory & Hauling
Type: New Small System Scope: The item-stack interface and the two carry/storage components every economy system builds against. Owns stacks, bag capacity, and carry↔storage transfer rules. Does NOT own item definitions (Crafting), harvest overflow behavior (Harvesting), or storage placement (Base Building). Date: 2026-08-26 Estimated Implementation: ~3 days (matches the sprint-1 time-box; TD advises a micro-spike if the storage-race implementation surfaces unknowns)
Overview
Every fight and every node feeds the bag on your back — this system is that bag, and the shared chest it empties into at home. A player carries a fixed-slot stack inventory (the CARRY component); the home island holds host-authoritative shared containers (SHARED-STORAGE components — one per storage chest Base Building places, BB Rule 12; any number may exist). Hauling is the loop beat between them: fill the bag on a docked land, get it home before the tide, bank it. Tension is opportunity cost only — there is no weight, no slowdown, no loss on death or departure.
Core Rules
- Two components, never one system (TD boundary, binding).
CarryInventorylives on the player object, owner-authoritative and client-predicted like movement.SharedStoragelives on a world container, host-authoritative like damage. They share the stack interface but never share authority; the first co-op storage-grab race forces this split, so it is built in from day one. - The stack interface (the contract GDDs 4–6 build against):
ItemStack = (itemId, count, instanceTag?); operationsCanAdd(itemId, count) → bool,Add,Remove,Count(itemId), plus capacity queries. Per-itemmaxStackis data-driven from item definitions (Crafting owns values; cloth-tier materials default below). No other system touches slots directly. **instanceTag(added 2026-08-27, cross-GDD review C2 — closes crafting OQ4):** an opaque value Inventory stores and carries verbatim through everyAdd/Remove/transfer and never interprets; Crafting keys itsInstanceMetadataon it (crafting Rule 4). A tagged stack never merges with any other stack (gear ismaxStack1 by definition, crafting AC-7); materials carry no tag. - Capacity is slots, never weight. The bag is
bagSlotsslots; a slot holds one stack. Full is full:CanAddreturns false and the calling system decides what that means (Harvesting owns blocked-harvest behavior). No encumbrance, no movement penalty, no micromanagement — rejected at concept level (Pillar 4). - Equipment is not bag space. Equipped weapon and armor occupy dedicated equipment slots outside the bag (hook signatures owned by the Combat GDD; definitions by Crafting). Swapping equipment never consumes or requires bag slots at MVP. **Amended 2026-09-11 (ADR-0010 §5, user decision): equipping moves the tagged stack out of the bag into the slot — so it frees a bag slot; a swap always works; a bare unequip into a full bag is an honest no-op (
BagFull). Prediction (Rule 7) applies to transfers only — harvest yields, crafts and placements are host-confirmed.** - No world-dropped items at MVP. Items exist only in a CARRY or a STORAGE component. No voluntary drop, no death drop (combat: keep-everything), no ground-item entities — harvest yields go directly to the bag or the harvest is blocked. This closes item-litter netcode scope; revisit only at Tier 1.
- Transfers are atomic host transactions. Deposit and withdraw between CARRY and STORAGE resolve on the host, in arrival order, all-or-nothing per stack: on any failure the items stay exactly where they were. **Duplication/loss invariant: no sequence of transfers, disconnects, or races may create or destroy items.**
- Race rule (the honest no-op). Two players grabbing the same stack: first host-arrival wins; the loser's withdraw fails as a visible, honest no-op — the UI shows the stack gone, no error theatrics (same feedback philosophy as combat's rejected-hit whiff). Client-side prediction applies only to the player's own CARRY mutations; STORAGE contents render host-confirmed state only.
- Cross-system invariants (restated, owned elsewhere): tide deposit leaves inventory untouched (tide Rule 9 / AC-23); death keeps everything (combat Rule 9 / AC-28); Downed does not affect the bag. Storage contents and carried items persist via Save (quick-spec ✅ 2026-08-26 —
save-persistence-2026-08-26.mdRule 2 lists both tables; this spec provides the serializable stack list). - UI flag 📌: bag + storage + transfer UI is the "chronically underestimated" risk named in the systems index. Run
/ux-designfor the inventory screens in Pre-Production; stories citedesign/ux/, not this spec.
Tuning Knobs
| Knob | Default | Range | Category | Rationale |
|---|---|---|---|---|
bagSlots | 10 | 8–16 | gate | Small enough that a deep push forces bank-or-push decisions ("bag three-quarters full of tin"); big enough to not nag |
maxStack (fiber / timber / sinew) | 20 | 10–50 | gate | Locked per-item by crafting F4 (2026-08-26) — Crafting owns the values, this row cites them; interacts with bagSlots for effective haul size |
storageSlots (home container) | 40 | 24–80 | gate | Must comfortably exceed a full bag so banking never blocks at MVP |
equipmentSlots | 2 (weapon, armor) | fixed at MVP | — | Matches Combat's two gear hooks; not a config knob until Tier 1 |
transferBatchWindow | 0 ms | 0–100 | feel | 0 = every click is its own host transaction; raise only if RPC volume becomes a measured problem |
All values live in assets/data/inventory.json, not hardcoded. Effective haul capacity = bagSlots × maxStack — retune together with Harvesting yield rates and tide window W (the bank-or-push decision only exists if a bag fills in comfortably less than one tide window).
Acceptance Criteria
- [ ] [UNIT]
CanAdd/Add/RemoverespectbagSlotsand per-itemmaxStack; adding to a full bag returns false and mutates nothing. - [ ] [UNIT] A stack's
instanceTagsurvives everyAdd/Remove/CARRY↔STORAGE transfer byte-identical, and two tagged stacks never merge even with equalitemId(crafting AC-37's inventory half; cross-GDD C2). - [ ] [UNIT] Transfer of a stack between CARRY and STORAGE is all-or-nothing: forced mid-transfer failure leaves total item count across both components unchanged (duplication/loss invariant).
- [ ] [INTEGRATION] Two clients withdraw the same last stack in the same window: exactly one succeeds; the loser sees an honest no-op, and total items are conserved.
- [ ] [INTEGRATION] A client's own bag updates within one frame of pickup/deposit input (client prediction on CARRY); STORAGE contents change only on host confirmation.
- [ ] [INTEGRATION] Tide deposit at
φ+Wand death/respawn both leave CARRY contents bit-identical (invariants restated from tide AC-23 / combat AC-28). - [ ] [EXPERIENTIAL] In playtest, a full bag reads as "time to decide: bank or push" — never as fiddly management (no weight UI, no sorting minigame at MVP).
- [ ] Regression: equipping/unequipping gear never changes bag contents or slot count.
Systems Index
Already tracked as MVP system #6 (Economy, quick-spec, sprint-1 exit criterion). On approval: status flips from "Not Started — HIGH RISK" to "Designed (quick-spec)" — unblocking design starts for Harvesting, Crafting, and Base Building.
Forward refs owned elsewhere: transfer RPC shape + STORAGE replication representation → netcode ADR; item definitions & stack values → Crafting GDD; blocked-harvest UX → Harvesting GDD; container placement → Base Building GDD; save format details → Save quick-spec.