--- description: Inventory system architecture - item management for individual Explorer loadouts and shared storage globs: src/managers/InventoryManager.js, src/types/Inventory.ts, src/ui/components/InventoryUI.js alwaysApply: false --- # **Inventory System Rule** The Inventory system operates in two distinct contexts: 1. **Run Context (The Expedition):** - **Unit Loadout:** Active gear affecting stats. Locked during combat (usually), editable during rest - **Run Stash (The Bag):** Temporary storage for loot found during the run. Infinite (or high) capacity - **Rule:** If the squad wipes, the _Run Stash_ is lost. Only equipped gear might be recovered (depending on difficulty settings) 2. **Hub Context (The Armory):** - **Master Stash:** Persistent storage for all unequipped items - **Management:** Players move items between the Master Stash and Unit Loadouts to prepare for the next run - **Extraction:** Upon successful run completion, _Run Stash_ contents are merged into _Master Stash_ ## **1. Visual Description (UI)** ### **A. Unit Loadout (The Paper Doll)** - **Visual:** A silhouette or 3D model of the character - **Slots:** - **Primary Hand:** Weapon (Sword, Staff, Wrench) - **Off-Hand:** Shield, Focus, Tool, or 2H (occupies both) - **Body:** Armor (Plate, Robes, Vest) - **Accessory/Relic:** Stat boosters or Passive enablers - **Belt (2 Slots):** Consumables (Potions, Grenades) usable in combat via Bonus Action - **Interaction:** Drag-and-drop from Stash to Slot. Invalid slots highlight Red. Valid slots highlight Green ### **B. The Stash (Grid View)** - **Visual:** A grid of item tiles on the right side of the screen - **Filters:** Tabs for [All] [Weapons] [Armor] [Utility] [Consumables] - **Sorting:** By Rarity (Color Coded border) or Tier - **Context Menu:** Right-click an item to "Equip to Active Unit" or "Salvage/Sell" ## **2. TypeScript Interfaces (Data Model)** ```typescript // src/types/Inventory.ts export type ItemType = | "WEAPON" | "ARMOR" | "RELIC" | "UTILITY" | "CONSUMABLE" | "MATERIAL"; export type Rarity = "COMMON" | "UNCOMMON" | "RARE" | "ANCIENT"; export type SlotType = "MAIN_HAND" | "OFF_HAND" | "BODY" | "ACCESSORY" | "BELT"; /** * A specific instance of an item. * Allows for RNG stats or durability in the future. */ export interface ItemInstance { uid: string; // Unique Instance ID (e.g. "ITEM_12345_A") defId: string; // Reference to static registry (e.g. "ITEM_RUSTY_BLADE") isNew: boolean; // For UI "New!" badges quantity: number; // For stackables (Potions/Materials) } /** * The inventory of a single character. */ export interface UnitLoadout { mainHand: ItemInstance | null; offHand: ItemInstance | null; body: ItemInstance | null; accessory: ItemInstance | null; belt: [ItemInstance | null, ItemInstance | null]; // Fixed 2 slots } /** * The shared storage (Run Bag or Hub Stash). */ export interface InventoryStorage { id: string; // "RUN_LOOT" or "HUB_VAULT" items: ItemInstance[]; // Unordered list currency: { aetherShards: number; ancientCores: number; }; } /** * Data payload for moving items. */ export interface TransferRequest { itemUid: string; sourceContainer: "STASH" | "UNIT_LOADOUT"; targetContainer: "STASH" | "UNIT_LOADOUT"; targetSlot?: SlotType; // If moving to Unit slotIndex?: number; // For Belt slots (0 or 1) unitId?: string; // Which unit owns the loadout } ``` ## **3. Logic & Rules** ### **A. Equipping Items** 1. **Validation:** - Check `Item.requirements` (Class Lock, Min Stats) against `Unit.baseStats` - Check Slot Compatibility (Can't put Armor in Weapon slot) - _Two-Handed Logic:_ If equipping a 2H weapon, unequip Off-Hand automatically 2. **Swapping:** - If target slot is occupied, move the existing item to Stash (or Swap if source was another slot) 3. **Stat Recalculation:** - Trigger `unit.recalculateStats()` immediately ### **B. Stacking** - **Equipment:** Non-stackable. Each Sword is a unique instance - **Consumables/Materials:** Stackable up to 99 - **Logic:** When adding a Potion to Stash, check if `defId` exists. If yes, `quantity++`. If no, create new entry ### **C. The Extraction (End of Run)** ```js function finalizeRun(runInventory, hubInventory) { // 1. Transfer Currency hubInventory.currency.shards += runInventory.currency.shards; // 2. Transfer Items for (let item of runInventory.items) { hubInventory.addItem(item); } // 3. Clear Run Inventory runInventory.clear(); } ``` ## **4. Conditions of Acceptance (CoA)** **CoA 1: Class Restrictions** - Attempting to equip a "Tinker Only" item on a "Vanguard" must fail - The UI should visually dim incompatible items when a unit is selected **CoA 2: Stat Updates** - Equipping a `+5 Attack` sword must immediately update the displayed Attack stat in the Character Sheet - Unequipping it must revert the stat **CoA 3: Belt Logic** - Using a Consumable in combat (via `ActionSystem`) must reduce its quantity - If quantity reaches 0, the item reference is removed from the Belt slot **CoA 4: Persistence** - Saving the game must serialize the `InventoryStorage` array correctly - Loading the game must restore specific item instances (not just generic definitions) ## **5. Integration Strategy (Wiring)** ### **A. Game Loop (Looting)** - **Trigger:** Player unit moves onto a tile with a Loot Chest / Item Drop - **Logic:** `GameLoop` detects collision -> calls `InventoryManager.runStash.addItem(foundItem)` - **Visual:** `VoxelManager` removes the chest model. UI shows "Item Acquired" toast ### **B. Character Sheet (Management)** - **Trigger:** Player opens Character Sheet -> Inventory Tab - **Logic:** The UI Component imports `InventoryManager` - **Display:** It renders `InventoryManager.runStash.items` (if in dungeon) or `hubStash.items` (if in hub) - **Action:** Dragging an item to a slot calls `InventoryManager.equipItem(activeUnit, itemUid, slot)` ### **C. Combat System (Consumables)** - **Trigger:** Player selects a "Potion" from the Combat HUD Action Bar - **Logic:** 1. Check `unit.equipment.belt` for the item 2. Execute Effect (Heal) 3. Call `InventoryManager.consumeItem(unit, slotIndex)` 4. Update Unit Inventory state ### **D. Persistence (Saving)** - **Trigger:** `GameStateManager.saveRun()` or `saveRoster()` - **Logic:** The `Explorer` class's `equipment` object and the `InventoryManager`'s `runStash` must be serialized to JSON - **Requirement:** Ensure `ItemInstance` objects are saved with their specific `uid` and `quantity`, not just `defId`