# Equipment System
*Inventory, Equipment Slots, and Item-Granted Abilities*

## Implementation Approach

This feature adds a full equipment system: a limited-capacity inventory bag, five equipment slots (head, body, mainHand, offHand, neck), and items that grant abilities when equipped. Item definitions are static data files loaded on startup (same pattern as abilities and encounters). The existing `key`-based ability override mechanic means equipping a weapon naturally replaces the unarmed attack without special-casing.

**This feature wipes existing character data — no backward compatibility needed.**

### Core Principles
- Each phase integrates backend AND frontend together
- Project remains runnable after each phase
- Phases are digestible (~10-20 files each)
- Each phase includes specific testable outcomes a human can verify in-game

### Phase Summary

1. Item Schema, Data Files & Inventory Display
2. Inventory Management (drop items, capacity)
3. Equipment Slots, Equip/Unequip & Sprite Overrides
4. Item-Granted Abilities in Combat

### How to Use This Plan

Work one phase at a time. Complete all backend and frontend work within a phase before moving on. Check off task boxes as you finish them. Leave testing checkboxes for the human reviewer. Get approval before starting the next phase.

---

## Data Definitions

### ItemDef (static, loaded from JSON files at startup)

```typescript
interface ItemDef {
  id: string;              // unique across all items
  name: string;
  description: string;
  type: ItemType;          // 'weapon' | 'armor' | 'accessory'
  slot: EquipmentSlot;     // which equipment slot this item occupies
  icon: ItemIcon;          // sprite atlas reference for UI rendering
  grantedAbilities: string[]; // ability IDs added to player when equipped
  tags: string[];          // for future filtering/RAG use
  version: string;
  updated_at: string;
}

interface ItemIcon {
  atlas: string;   // Phaser atlas key, e.g. 'equipment-0'
  frame: string;   // frame filename within the atlas, e.g. 'Iron_Sword.png'
}

enum ItemType {
  WEAPON = 'weapon',
  ARMOR  = 'armor',
  ACCESSORY = 'accessory',
}

enum EquipmentSlot {
  HEAD      = 'head',
  BODY      = 'body',
  MAIN_HAND = 'mainHand',
  OFF_HAND  = 'offHand',
  NECK      = 'neck',
}
```

### InventoryItem (instance stored in MongoDB)

```typescript
// Stored in DB — minimal, just a reference
interface InventoryItem {
  instanceId: string;   // UUID v4
  defId: string;        // references ItemDef.id
}

// Sent to client in DTO — enriched with full def
interface InventoryItemDTO extends InventoryItem {
  def: ItemDef;
}
```

### EquipmentSlots (stored in DB, on the Character document)

```typescript
type EquipmentSlots = {
  head?:     InventoryItem;
  body?:     InventoryItem;
  mainHand?: InventoryItem;
  offHand?:  InventoryItem;
  neck?:     InventoryItem;
};
```

### Character Document Additions

```typescript
// Added to CharacterModel mongoose schema
inventory:  InventoryItem[]   // default []
equipment:  EquipmentSlots    // default {}
```

### Ability Resolution Logic (Phase 4)

The effective ability list is assembled at DTO time, not stored:

1. Start with `['player_unarmed']` (always present as fallback).
2. Collect all `grantedAbilities` from every equipped item.
3. Merge: for each granted ability, look up its `key`. If any existing ability in the list shares that `key`, replace it; otherwise append.

This means equipping an `iron_sword` that grants `player_melee_weapon` (key: `melee_attack`) automatically replaces `player_unarmed` (also key: `melee_attack`). Unequipping the sword reverts to `player_unarmed`.

---

## Phase 1: Item Schema, Data Files & Inventory Display

### Goal

Define the data model, load item definitions from disk, give a new character a starting item, and show a read-only inventory panel in the game UI.

### Backend Work

- [x] **New schema file** `packages/shared-utils/src/schema/item.schema.ts`
  - `ItemTypeSchema` (enum)
  - `EquipmentSlotSchema` (enum)
  - `ItemIconSchema`: `{ atlas: z.string(), frame: z.string() }`
  - `ItemDefSchema` (includes `icon: ItemIconSchema`)
  - `InventoryItemSchema` (stored)
  - `InventoryItemDTOSchema` (includes full `def`)
  - `EquipmentSlotsSchema`
  - Export DTOs: `ItemDefDTO`, `InventoryItemDTO`, `EquipmentSlotsDTO`
- [x] Export new schemas from `packages/shared-utils/src/index.ts`
- [x] Run `npm run build:schemas` to export JSON schemas
- [x] **Update `PlayerCharacterSchema`** in `character.schema.ts`
  - Add `inventory: z.array(InventoryItemDTOSchema).optional()`
  - Add `equipment: EquipmentSlotsDTOSchema.optional()`
  - (DTOs use the enriched `InventoryItemDTOSchema` with embedded `def`)
- [x] **Update `CharacterModel`** (`apps/server/src/model/CharacterModel.ts`)
  - Add `inventory: [{ instanceId: String, defId: String }]` subdocument array
  - Add `equipment: { head?, body?, mainHand?, offHand?, neck? }` subdocument (each field `{ instanceId, defId }`)
- [x] **Create `ItemDefService`** (`apps/server/src/service/item/itemDefService.ts`)
  - Loads all `apps/server/data/items/*.json` on startup (same pattern as `AbilitiesService`)
  - `getItemDef(id: string): ItemDef`
  - `getAllItemDefs(): ItemDef[]`
- [x] **Create starter item data file** `apps/server/data/items/starter-items.json`
  - Include at least: `iron_sword` (mainHand weapon, grants `player_melee_weapon`), `bamboo_hat` (head armor, no abilities), `scarf_blue` (neck accessory, no abilities)
  - Each item must have an `icon` field referencing the correct atlas and frame (see Item Icon Atlas Reference in Supporting Details)
- [x] **Add `startingInventory` to each archetype** in `apps/server/data/config/character-creation.json`
  - Each archetype gets an array of item `defId` strings, e.g.:
    - `combat`: `["iron_sword"]`
    - `magic`: `["scarf_blue"]`
    - `stealth`: `["hunters_bow"]`
- [x] **Update `CharacterCreationConfigSchema`** in shared-utils to include `startingInventory: z.array(z.string())` on each archetype entry
  - Note: implemented as server-only `CharacterArchetype` interface change; stored archetype name on character model
- [x] **Update `gameService.createNewGame()`** to read the selected archetype's `startingInventory` from the config and add each item to the character's inventory (generate a UUID `instanceId` per item, push to `inventory` array)
- [x] **Update `gameService.gameModelToDTO()`**
  - Populate `character.inventory` by mapping each stored `InventoryItem` to `InventoryItemDTO` (look up `ItemDef` via `ItemDefService`, embed as `def`)
  - Populate `character.equipment` similarly (enrich each slot's item)
- [x] Run `npm run build:shared`

### Frontend Work

- [x] **Create `inventoryStore.ts`** (`apps/client/src/ui/stores/inventoryStore.ts`)
  - State: `inventory: InventoryItemDTO[]`, `equipment: EquipmentSlotsDTO`
  - Actions: `setInventory()`, `setEquipment()`
  - Hydrate from GameState on game load (in existing game init flow)
- [x] **Preload equipment atlases** in the client's asset loading phase (BootScene or equivalent)
  - `equipment-0`: atlas key `equipment-0`, JSON at `assets/images/equipment-0.json`, image at `assets/images/equipment-0.png`
  - `equipment-1`: atlas key `equipment-1`, JSON at `assets/images/equipment-1.json`, image at `assets/images/equipment-1.png`
- [x] **Create `ItemIcon.tsx`** (`apps/client/src/ui/components/inventory/ItemIcon.tsx`)
  - Renders a React `<img>` using a canvas or Phaser texture frame extracted to a data URL, OR renders a positioned `<div>` with the atlas image as a CSS background-position sprite
  - Accepts `{ atlas, frame, size }` props
- [x] **Create `InventoryPanel.tsx`** (`apps/client/src/ui/components/inventory/InventoryPanel.tsx`)
  - Grid showing inventory items — each cell displays the item icon (via `ItemIcon`) with name below
  - Item tooltip on hover: name + description
  - Capacity display: "3 / 20"
  - Read-only for now (no actions)
- [x] **Add inventory button to game HUD** (small bag icon or button) that opens/closes the panel
- [x] Wire GameState `character.inventory` + `character.equipment` into the inventory store on load

### Deliverable

After a new game starts, the player can open an inventory panel and see their archetype-specific starting item(s). A Combat character sees an Iron Sword; a Stealth character sees a Hunter's Bow; a Magic character sees a Blue Scarf. Hovering any item shows its name and description. The capacity indicator reflects the correct count.

### Testing

- [x] Start a new game as **Combat**. An inventory button is visible in the game HUD.
- [x] Click the inventory button. The panel opens showing the Iron Sword with its sword icon.
- [x] Hover the Iron Sword. A tooltip shows its name and description.
- [x] The capacity indicator reads "1 / 20".
- [x] Close and reopen the panel. Items persist.
- [x] Start a new game as **Stealth**. The panel opens showing the Hunter's Bow instead.
- [x] Start a new game as **Magic**. The panel opens showing the Blue Scarf instead.

---

## Phase 2: Inventory Management

### Goal

Allow players to drop items from their inventory. Enforce the inventory capacity limit so the bag cannot exceed 20 items.

### Backend Work

- [x] **Create `InventoryService`** (`apps/server/src/service/item/inventoryService.ts`)
  - `dropItem(gameId: string, instanceId: string): Promise<InventoryDropResponseDTO>`
    - Validates item is in inventory (not equipped)
    - Removes item from character's inventory array
    - Returns updated inventory
  - `INVENTORY_CAPACITY = 20` constant
  - `canAddItem(character)` helper — checks `inventory.length < INVENTORY_CAPACITY`
- [x] **New schema** `InventoryDropResponseSchema` in `item.schema.ts`
  - `{ inventory: InventoryItemDTOSchema[] }`
- [x] **New API route** `apps/server/src/api/game/inventory.ts`
  - `POST /game/inventory/drop` → body `{ gameId, instanceId: string }`
  - Delegates to `InventoryService.dropItem()`
- [x] Register the new router in the server's main router (auto-registered via scanApiFolder)

### Frontend Work

- [x] **Add drop button** to each inventory item in `InventoryPanel.tsx`
  - Shows on hover or as a small ✕ button
  - Confirmation: click once to select, second click or confirm dialog to drop
- [x] **Add `dropItem()` action** to `inventoryStore.ts`
  - Calls `POST /game/inventory/drop`
  - Updates inventory in store on success
- [x] Show error toast if drop fails (item not found)

### Deliverable

The player can drop items from their inventory. The inventory count decreases. The item is permanently gone.

### Testing

- [x] Open the inventory panel. Hover over the Iron Sword. A drop/remove button appears.
- [x] Click drop. The item disappears from the inventory.
- [x] The capacity indicator updates (e.g., "0 / 20").
- [x] Reload the page. The dropped item is still gone.

---

## Phase 3: Equipment Slots & Equip/Unequip

### Goal

Add an equipment panel showing the five slots (head, body, mainHand, offHand, neck). Players can equip items from their inventory into compatible slots and unequip them back to their inventory. Equipping items that define sprite overrides immediately updates the character's in-game sprite.

### Sprite Override Background

The sprite system already has full support for equipment overrides via `EquipmentSpriteOverridesDTO` (defined in `character.schema.ts`) and `buildSpriteForCharacter(character, equipmentOverrides?)` (in `characterAppearanceService.ts`). Each equipped item can declare a `spriteOverrides` field that maps sprite layer keys to new layer selections (or `null` to hide a layer) and adds animation overrides.

When equip/unequip occurs, the server:
1. Collects `spriteOverrides` from all currently equipped items after the change.
2. Merges them into a single `EquipmentSpriteOverridesDTO` (last-writer-wins per layer key).
3. Calls `buildSpriteForCharacter(character, mergedOverrides)` to produce a new sprite ZIP and hash.
4. Returns the new `spriteHash` in the equip/unequip response.

The client reloads the player sprite in Phaser whenever `spriteHash` changes.

### Backend Work

- [x] **Add `spriteOverrides` to `IItemDef`** (`packages/shared-utils/src/entity/types.ts`)
  - `spriteOverrides?: EquipmentSpriteOverridesDTO`
- [x] **Add `spriteOverrides` to `ItemDefSchema`** (`packages/shared-utils/src/schema/item.schema.ts`)
  - `spriteOverrides: EquipmentSpriteOverridesSchema.optional()`
  - `EquipmentSpriteOverridesSchema` moved to `characterDef.schema.ts` (re-exported from `character.schema.ts`) to avoid circular dep between `item.schema.ts` and `character.schema.ts`
- [x] **Add to `InventoryService`**:
  - `equipItem(character, instanceId, slot)` — validates, swaps displaced item back to bag if needed, capacity-checks, saves, builds sprite overrides + spriteHash
  - `unequipItem(character, slot)` — capacity-checks, moves to bag, saves, builds sprite overrides + spriteHash
  - `buildEquipmentSpriteOverrides(character)` helper — merges spriteOverrides from all equipped items
- [x] **New schemas** in `item.schema.ts`:
  - `EquipRequestSchema`: `{ gameId, instanceId, slot }`
  - `UnequipRequestSchema`: `{ gameId, slot }`
  - `EquipResponseSchema`: `{ inventory, equipment, spriteHash? }`
- [x] **New API routes** in `inventory.ts`:
  - `POST /game/inventory/equip` (function `postEquip`)
  - `POST /game/inventory/unequip` (function `postUnequip`)
- [x] Run `npm run build:shared`

### Frontend Work

- [x] **Create `EquipmentPanel.tsx`** (`apps/client/src/ui/components/inventory/EquipmentPanel.tsx`)
  - Five labeled slot boxes: Head, Body, Main Hand, Off Hand, Neck
  - Each occupied slot renders the item icon (via `ItemIcon`) and item name; empty slots show placeholder
  - Clicking an occupied slot unequips the item (sends unequip request)
- [x] **Integrate into `InventoryPanel.tsx`**
  - Equipment panel above inventory grid (stacked layout)
- [x] **Click-to-equip from inventory**
  - Clicking an item in the inventory grid equips it to its compatible slot (slot determined by `def.slot`)
- [x] **Add `equipItem()` / `unequipItem()` actions** to `inventoryStore.ts`
  - Call the respective API endpoints
  - Update `inventory` and `equipment` in store on success
- [x] **Reload player sprite in Phaser on spriteHash change**
  - `applySpriteHash()` in inventoryStore calls `MapScene.reloadPlayerSprite(hash)` via `phaserGame.scene.getScene('MapScene')`
  - `MapScene.reloadPlayerSprite(newHash)` removes old texture, destroys old sprite, re-loads ZIP at new URL, recreates sprite at same position
- [x] Show error if equip fails (e.g., "Inventory full — unequip something first")

### Deliverable

The player opens the inventory panel and sees both their bag items and equipment slots. They can click the Iron Sword to equip it in the Main Hand slot. They can click the equipped sword to unequip it back to the bag. If an equipped item declares sprite overrides, the in-game character sprite updates immediately without a page reload.

### Testing

- [x] Open the inventory panel. An equipment section is visible with 5 labeled slots, all "Empty".
- [x] Click the Iron Sword in the inventory. It moves into the "Main Hand" slot. The inventory grid is now empty.
- [x] Click the Iron Sword in the Main Hand slot. It moves back to the inventory grid.
- [x] Start a new game with a bamboo hat in inventory. Clicking it equips to "Head", not "Main Hand". The Head slot renders the bamboo hat icon.
- [x] Fill the inventory to 20 items (via cheats or manual DB edit). Attempt to unequip. Get an error message about inventory being full.
- [x] Add `spriteOverrides` to an item def (e.g., iron_helmet overrides the `head` layer). Equip it. The character's sprite on the map updates immediately to show the helmet layer. Unequip it — sprite reverts.

---

## Phase 4: Item-Granted Abilities in Combat

### Goal

Equipped items dynamically modify the player's ability list. Equipping a sword replaces the unarmed attack. Unequipping it reverts. The combat ability bar reflects the current equipment at all times.

### Backend Work

- [x] **Create `getEffectiveAbilities(character)`** helper (in `InventoryService` or a shared utility)
  - Implements the resolution logic from the Data Definitions section above
  - Replaces the current hardcoded `['player_unarmed']` in `gameService.gameModelToDTO()`
- [x] **Update `gameService.gameModelToDTO()`**
  - Call `getEffectiveAbilities(character)` instead of hardcoded list
- [x] **Update `EquipResponseSchema` / `UnequipResponseSchema`** to include `abilities: string[]` in the response
  - So the client can refresh the ability list immediately after equip/unequip without a full game state reload
- [x] **Confirm `iron_sword` in `starter-items.json` grants `player_melee_weapon`**
  - `player_melee_weapon` has key `melee_attack`, which overrides `player_unarmed` (also key `melee_attack`) per the resolution logic
- [x] Add a test item: `hunters_bow` in `starter-items.json` — offHand weapon, grants `player_bow` (key: `range_attack`), icon: `{ atlas: 'equipment-0', frame: 'Bow_Green.png' }`

### Frontend Work

- [x] **Update `inventoryStore`** — add `abilities: string[]` to state, synced on equip/unequip responses
- [x] **Ensure combat ability bar** re-reads from game state abilities on each combat load
  - Currently abilities come from GameState — no special changes if GameState is refreshed on equip
  - If abilities come from a separate store, ensure it's updated when inventory store refreshes
- [x] **Item tooltips** in `InventoryPanel` should list granted abilities
  - e.g., "Grants: Melee Attack (2–4 dmg)"

### Deliverable

Equipping the Iron Sword before entering combat shows "Melee Attack" with 2–4 base damage instead of the 0-damage unarmed version. Unequipping the sword before combat reverts to the unarmed attack.

### Testing

- [x] Start a new game. Open inventory, do NOT equip the Iron Sword. Enter combat. The ability bar shows "Melee Attack" (unarmed, 0 extra dmg).
- [x] Exit combat. Open inventory, equip the Iron Sword. Enter combat. The ability bar shows "Melee Attack" with higher damage.
- [x] Use the sword ability in combat. It deals more damage than the unarmed version.
- [x] Unequip the sword between combats. Ability bar reverts to unarmed.
- [x] Equip the Hunter's Bow (offHand). A "Range Attack" ability appears in combat.

---

## Supporting Details

### File Structure

```
packages/shared-utils/src/schema/
  item.schema.ts                  ← NEW: all item/inventory/equipment schemas

apps/server/data/items/
  starter-items.json              ← NEW: iron_sword, bamboo_hat, scarf_blue, hunters_bow

apps/server/data/config/
  character-creation.json         ← UPDATED: add startingInventory to each archetype

apps/server/src/service/item/
  itemDefService.ts               ← NEW: loads JSON defs on startup
  inventoryService.ts             ← NEW: drop, equip, unequip logic

apps/server/src/api/game/
  inventory.ts                    ← NEW: /inventory/drop, /equipment/equip, /equipment/unequip

apps/client/public/assets/images/
  equipment-0.json / equipment-0.png  ← EXISTING: weapon/armor icon atlas
  equipment-1.json / equipment-1.png  ← EXISTING: armor/accessory icon atlas

apps/client/src/ui/stores/
  inventoryStore.ts               ← NEW: inventory + equipment state

apps/client/src/ui/components/inventory/
  InventoryPanel.tsx              ← NEW: combined inventory + equipment UI
  EquipmentPanel.tsx              ← NEW: five slot boxes
  ItemIcon.tsx                    ← NEW: renders atlas-based item icon
```

### API Endpoint Summary

| Method | Path | Body | Response |
|--------|------|------|----------|
| POST | `/game/:gameId/inventory/drop` | `{ instanceId }` | `{ inventory }` |
| POST | `/game/:gameId/equipment/equip` | `{ instanceId, slot }` | `{ inventory, equipment, spriteHash?, abilities }` |
| POST | `/game/:gameId/equipment/unequip` | `{ slot }` | `{ inventory, equipment, spriteHash?, abilities }` |

### Inventory Capacity

`INVENTORY_CAPACITY = 20` — declared as a named constant in `inventoryService.ts`. This covers the bag only; equipped items are not counted against the bag limit.

### Slot Compatibility

Each `ItemDef` declares exactly one `slot`. The equip endpoint validates that the requested slot matches `def.slot` and rejects mismatches with a 400 error. Two-handed weapons can be modeled later as a mainHand item that also clears offHand on equip (out of scope for this plan).

### Item Icon Atlas Reference

Two Phaser atlases live at `apps/client/public/assets/images/equipment-{0,1}.{json,png}`. Load them with atlas keys `equipment-0` and `equipment-1`. Below are the available frames by category — use these when setting `icon.atlas` and `icon.frame` in item JSON files.

**`equipment-0`** — weapons, hats, scarves
| Frame | Category |
|---|---|
| `Iron_Sword.png`, `Copper_Sword.png`, `Silver_Sword.png`, `Gold_Sword.png`, `Platinum_Sword.png` | Sword (mainHand) |
| `Weapon_BoneSword.png`, `Weapon_DiamonSword.png`, `Weapon_Scimitar.png`, `Weapon_Rapier.png`, `Weapon_CurvedDagger.png` | Sword/Dagger variants (mainHand) |
| `Iron_Axe.png`, `Copper_Axe.png`, `Silver_Axe.png`, `Gold_Axe.png`, `Platinum_Axe.png` | Axe (mainHand) |
| `Iron_Hammer.png`, `Copper_Hammer.png`, `Silver_Hammer.png`, `Gold_Hammer.png`, `Platinum_Hammer.png` | Hammer (mainHand) |
| `Bow_Blue.png`, `Bow_Green.png`, `Bow_Red.png`, `Bow_WHite.png`, `Bow_Yellow.png` | Bow (offHand) |
| `Copper_Shield.png`, `Gold_Shield.png` | Shield (offHand) |
| `Bamboo_Hat.png` | Hat (head) |
| `Beanie_Blue.png` | Beanie (head) |
| `Scarf_Blue.png` | Scarf (neck) |
| `Potion_Blue.png`, `Potion_Red.png` | Potion |
| `Sack.png`, `LeatherBag_Beige.png`, `LeatherBag_Blue.png`, `LeatherBag_Brown.png`, `LeatherBag_Red.png`, `LeatherBag_Yellow.png` | Bag |
| `Copper_Key.png`, `Gold_Key.png`, `Iron_Key.png`, `Platinum_Key.png` | Key |
| `DarkWood_Crate.png`, `LightWood_Crate.png`, `Wood_Crate.png` | Crate |

**`equipment-1`** — helmets, breastplates, shields, scarves, necklaces, beanies
| Frame | Category |
|---|---|
| `Iron_Helmet.png`, `Copper_Helmet.png`, `Silver_Helmet.png`, `Gold_Helmet.png`, `Platinum_Helmet.png` | Helmet (head) |
| `Iron_Breastplate.png`, `Copper_Breastplate.png`, `Silver_Breastplate.png`, `Gold_Breastplate.png`, `Platinum_Breastplate.png` | Breastplate (body) |
| `Iron_Shield.png`, `Silver_Shield.png`, `Platinum_Shield.png` | Shield (offHand) |
| `Scarf_Green.png`, `Scarf_Purple.png`, `Scarf_Red.png`, `Scarf_Yellow.png` | Scarf (neck) |
| `Amathyst_Neckace.png`, `Diamond_Necklace.png`, `Emrald_Necklace.png`, `LapisLazulie_Necklace.png`, `Ruby_Necklace.png` | Necklace (neck) |
| `Beanie_Blue.png`, `Beanie_Green.png`, `Beanie_Purple.png`, `Beanie_Red.png`, `Beanie_Yellow.png` | Beanie (head) |
| `Silver_Key.png` | Key |
| `Copper_Pickaxe.png`, `Iron_Pickaxe.png`, `Silver_Pickaxe.png`, `Gold_PickAxe.png`, `Platinum_PickAxe.png` | Pickaxe |
| `DarkWood_Chest.png`, `LightWood_Chest.png`, `Wood_Chest.png` | Chest |
| `Map.png` | Map |

### Starter Item Examples

```json
[
  {
    "id": "iron_sword",
    "name": "Iron Sword",
    "description": "A reliable short sword with a worn iron blade.",
    "type": "weapon",
    "slot": "mainHand",
    "icon": { "atlas": "equipment-0", "frame": "Iron_Sword.png" },
    "grantedAbilities": ["player_melee_weapon"],
    "tags": ["weapon", "melee", "iron"],
    "version": "1.0",
    "updated_at": "2026-03-08"
  },
  {
    "id": "bamboo_hat",
    "name": "Bamboo Hat",
    "description": "A wide-brimmed hat woven from bamboo. Keeps the sun off.",
    "type": "armor",
    "slot": "head",
    "icon": { "atlas": "equipment-0", "frame": "Bamboo_Hat.png" },
    "grantedAbilities": [],
    "tags": ["armor", "head"],
    "version": "1.0",
    "updated_at": "2026-03-08"
  },
  {
    "id": "scarf_blue",
    "name": "Blue Scarf",
    "description": "A worn woolen scarf. Smells faintly of campfire smoke.",
    "type": "accessory",
    "slot": "neck",
    "icon": { "atlas": "equipment-0", "frame": "Scarf_Blue.png" },
    "grantedAbilities": [],
    "tags": ["accessory", "neck"],
    "version": "1.0",
    "updated_at": "2026-03-08"
  },
  {
    "id": "hunters_bow",
    "name": "Hunter's Bow",
    "description": "A simple recurve bow suited for short-range hunting.",
    "type": "weapon",
    "slot": "offHand",
    "icon": { "atlas": "equipment-0", "frame": "Bow_Green.png" },
    "grantedAbilities": ["player_bow"],
    "tags": ["weapon", "ranged", "bow"],
    "version": "1.0",
    "updated_at": "2026-03-08"
  },
  {
    "id": "iron_helmet",
    "name": "Iron Helmet",
    "description": "A dented iron helmet. Still better than bare skull.",
    "type": "armor",
    "slot": "head",
    "icon": { "atlas": "equipment-1", "frame": "Iron_Helmet.png" },
    "grantedAbilities": [],
    "tags": ["armor", "head", "iron"],
    "version": "1.0",
    "updated_at": "2026-03-08"
  },
  {
    "id": "iron_breastplate",
    "name": "Iron Breastplate",
    "description": "Heavy iron chest armour. Slows you down but stops arrows.",
    "type": "armor",
    "slot": "body",
    "icon": { "atlas": "equipment-1", "frame": "Iron_Breastplate.png" },
    "grantedAbilities": [],
    "tags": ["armor", "body", "iron"],
    "version": "1.0",
    "updated_at": "2026-03-08"
  },
  {
    "id": "iron_shield",
    "name": "Iron Shield",
    "description": "A round iron shield with a battered rim.",
    "type": "armor",
    "slot": "offHand",
    "icon": { "atlas": "equipment-1", "frame": "Iron_Shield.png" },
    "grantedAbilities": [],
    "tags": ["armor", "offHand", "iron"],
    "version": "1.0",
    "updated_at": "2026-03-08"
  }
]
```
