> ## Documentation Index
> Fetch the complete documentation index at: https://hyperscape-ai-mintlify-docs-update.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Item Data Structure

> How items, equipment, and consumables are defined

# Item Data Structure

Items are defined in **JSON manifests** and loaded at runtime. Items are now organized into separate files by type for better maintainability.

<Info>
  Item data is managed in `packages/shared/src/data/items.ts` and loaded from `world/assets/manifests/items/` directory.
</Info>

## Data Loading

Items are **NOT hardcoded**. The `ITEMS` map is populated at runtime from multiple manifest files:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From items.ts
export const ITEMS: Map<string, Item> = new Map();

// Populated by DataManager from JSON files
DataManager.loadItems(); // Reads all files in world/assets/manifests/items/
```

## Manifest Organization

Items are split into separate files by category:

```
manifests/items/
├── weapons.json      # Combat weapons (swords, axes, etc.)
├── tools.json        # Skilling tools (hatchets, pickaxes, fishing rods)
├── resources.json    # Gathered materials (ores, logs, bars, raw fish)
├── food.json         # Cooked consumables
└── misc.json         # Currency, burnt food, junk items
```

### Directory-Based Loading

Items are organized by category in `packages/server/world/assets/manifests/items/`:

* `weapons.json` - Swords, axes, bows, etc.
* `tools.json` - Hatchets, pickaxes, fishing rods, hammer, tinderbox
* `resources.json` - Ores, bars, logs, raw fish
* `food.json` - Cooked food, raw food, burnt food
* `misc.json` - Coins, junk items, quest items

**Atomic Loading**: All 5 category files must exist or the system falls back to legacy `items.json`.

**Duplicate Detection**: The loader validates that no item ID appears in multiple category files.

***

## Item Schema

Each item has the following structure:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
interface Item {
  id: string;                    // Unique identifier (e.g., "bronze_sword")
  name: string;                  // Display name (e.g., "Bronze Sword")
  type: ItemType;                // Category
  tier?: string;                 // Equipment tier (bronze, steel, mithril, etc.)
  rarity: ItemRarity;            // Common, Uncommon, Rare, etc.
  modelPath?: string;            // Path to GLB model
  tier?: string;                 // Metal tier (bronze, iron, steel, etc.)

  // Inventory properties
  stackable: boolean;            // Can stack in inventory
  tradeable: boolean;            // Can be traded/sold
  value: number;                 // Base value in coins

  // Equipment properties (for weapons/armor)
  requirements?: ItemRequirement;
  bonuses?: ItemStats;           // Renamed from stats
  attackType?: AttackType;
  weaponType?: WeaponType;
  equipSlot?: EquipmentSlotName; // Where item equips
  attackSpeed?: number;          // In milliseconds
  range?: number;                // Attack range
  equipSlot?: string;            // Equipment slot (weapon, shield, head, body, etc.)

  // Tool properties (for gathering tools)
  tool?: {
    skill: "woodcutting" | "mining" | "fishing";
    priority: number;            // Higher = better tool
    rollTicks?: number;          // Mining: ticks between roll attempts
  };

  // OSRS-accurate inventory actions
  inventoryActions?: string[];   // Context menu actions (e.g., ["Eat", "Use", "Drop"])

  // Tool properties (for gathering tools)
  tool?: {
    skill: "woodcutting" | "mining" | "fishing";
    priority: number;            // Higher priority tools used first
    rollTicks?: number;          // Ticks between gather attempts
  };

  // Processing properties (embedded recipe data)
  cooking?: CookingRecipeData;
  firemaking?: FiremakingRecipeData;
  smelting?: SmeltingRecipeData;
  smithing?: SmithingRecipeData;

  // Noted item support
  isNoted?: boolean;
  baseItemId?: string;           // For noted items
  notedItemId?: string;          // For base items

  // Processing properties
  cooking?: CookingData;         // For raw food items
  firemaking?: FiremakingData;   // For logs
  smelting?: SmeltingData;       // For bars
  smithing?: SmithingData;       // For smithable items
  
  // Ground item display overrides
  modelScale?: number;           // Scale for 3D model when displayed as ground item (default: 0.3)
  groundOffset?: number;         // Ground item Y positioning (see below)
}
```

### Tier-Based Requirements

Items with a `tier` property automatically derive level requirements from `tier-requirements.json`:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "id": "steel_sword",
  "name": "Steel Sword",
  "type": "weapon",
  "tier": "steel",
  "equipSlot": "weapon",
  "attackType": "MELEE"
  // requirements auto-derived: { attack: 5 }
}
```

The `TierDataProvider` maps tiers to skill requirements, eliminating redundant requirement definitions.

***

## Item Types

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
type ItemType =
  | "weapon"      // Swords, axes, bows → weapons.json
  | "armor"       // Helmets, bodies, legs → (future)
  | "tool"        // Hatchets, pickaxes, fishing rods → tools.json
  | "consumable"  // Food, potions → food.json
  | "resource"    // Logs, ore, fish, bars → resources.json
  | "currency"    // Coins → misc.json
  | "junk"        // Burnt food → misc.json
  | "misc";       // Other items → misc.json
```

## Tier-Based Equipment System

Items now use a **centralized tier system** defined in `tier-requirements.json`. Instead of hardcoding level requirements in each item, equipment references a tier:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "id": "bronze_sword",
  "name": "Bronze Sword",
  "type": "weapon",
  "tier": "bronze",  // References tier-requirements.json
  // ... other properties
}
```

The system automatically looks up requirements from `tier-requirements.json`:

### Melee Tiers

| Tier        | Attack | Defence |
| ----------- | ------ | ------- |
| bronze/iron | 1      | 1       |
| steel       | 5      | 5       |
| black       | 10     | 10      |
| mithril     | 20     | 20      |
| adamant     | 30     | 30      |
| rune        | 40     | 40      |
| dragon      | 60     | 60      |

### Tool Tiers

| Tier        | Attack | Woodcutting | Mining |
| ----------- | ------ | ----------- | ------ |
| bronze/iron | 1      | 1           | 1      |
| steel       | 5      | 6           | 6      |
| mithril     | 20     | 21          | 21     |
| adamant     | 30     | 31          | 31     |
| rune        | 40     | 41          | 41     |
| dragon      | 60     | 61          | 61     |

<Info>
  This centralized approach ensures OSRS-accurate requirements and makes it easy to add new tiers without modifying individual items.
</Info>

***

## Item Rarity

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
type ItemRarity =
  | "common"
  | "uncommon"
  | "rare"
  | "epic"
  | "legendary";
```

***

## Equipment Stats

For weapons and armor:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
interface ItemStats {
  attack?: number;       // Attack bonus
  strength?: number;     // Strength bonus (max hit)
  defense?: number;      // Defense bonus
  ranged?: number;       // Ranged attack bonus
  rangedStrength?: number; // Ranged damage bonus
  prayer?: number;       // Prayer bonus
}
```

***

## Skill Requirements

Items can require skill levels to use:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
interface ItemRequirement {
  attack?: number;
  strength?: number;
  defense?: number;
  ranged?: number;
  woodcutting?: number;
  mining?: number;
  fishing?: number;
}
```

***

## Weapon Types

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
type WeaponType =
  | "sword"
  | "scimitar"
  | "longsword"
  | "dagger"
  | "axe"
  | "mace"
  | "warhammer"
  | "2h"
  | "bow"
  | "crossbow"
  | "staff";

type AttackType = "melee" | "ranged" | "magic";
```

***

## Attack Speed

Weapons have different attack speeds:

| Weapon Type | Speed (ms) | Speed (ticks) |
| ----------- | ---------- | ------------- |
| Dagger      | 1800       | 3             |
| Scimitar    | 1800       | 3             |
| Sword       | 2400       | 4             |
| Longsword   | 2400       | 4             |
| Battleaxe   | 3000       | 5             |
| 2H Sword    | 3600       | 6             |
| Shortbow    | 1800       | 3             |
| Longbow     | 3000       | 5             |

***

## Helper Functions

### Item Type Detection

The `item-helpers.ts` module provides utilities for detecting item types:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From item-helpers.ts
export function isFood(item: Item | null): boolean {
  if (!item) return false;
  return (
    item.type === "consumable" &&
    typeof item.healAmount === "number" &&
    item.healAmount > 0 &&
    !item.id.includes("potion")
  );
}

export function isPotion(item: Item | null): boolean {
  if (!item) return false;
  return item.type === "consumable" && item.id.includes("potion");
}

export function isBone(item: Item | null): boolean {
  if (!item) return false;
  return item.id === "bones" || item.id.endsWith("_bones");
}

export function isWeapon(item: Item | null): boolean {
  if (!item) return false;
  return (
    item.equipSlot === "weapon" ||
    item.equipSlot === "2h" ||
    item.is2h === true ||
    item.weaponType != null
  );
}

export function isShield(item: Item | null): boolean {
  if (!item) return false;
  return item.equipSlot === "shield";
}

export function usesWield(item: Item | null): boolean {
  return isWeapon(item) || isShield(item);
}

export function usesWear(item: Item | null): boolean {
  if (!item) return false;
  if (!item.equipable && !item.equipSlot) return false;
  return !usesWield(item);
}

export function isNotedItem(item: Item | null): boolean {
  if (!item) return false;
  return item.isNoted === true || item.id.endsWith("_noted");
}
```

### Primary Action Detection

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// Get primary action for left-click
export function getPrimaryAction(
  item: Item | null,
  isNoted: boolean,
): PrimaryActionType {
  if (isNoted) return "use";

  // Check manifest first
  const manifestAction = getPrimaryActionFromManifest(item);
  if (manifestAction) return manifestAction;

  // Fallback to heuristic detection
  if (isFood(item)) return "eat";
  if (isPotion(item)) return "drink";
  if (isBone(item)) return "bury";
  if (usesWield(item)) return "wield";
  if (usesWear(item)) return "wear";

  return "use";
}
```

### Get Item by ID

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
export function getItem(itemId: string): Item | null {
  return ITEMS.get(itemId) || null;
}
```

### Get Items by Type

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
export function getItemsByType(type: ItemType): Item[] {
  return Array.from(ITEMS.values()).filter((item) => item.type === type);
}

// Convenience functions
export function getWeapons(): Item[] {
  return getItemsByType("weapon");
}

export function getArmor(): Item[] {
  return getItemsByType("armor");
}

export function getTools(): Item[] {
  return getItemsByType("tool");
}

export function getConsumables(): Item[] {
  return getItemsByType("consumable");
}

export function getResources(): Item[] {
  return getItemsByType("resource");
}
```

### Get Items by Skill Requirement

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
export function getItemsBySkill(skill: string): Item[] {
  return Array.from(ITEMS.values()).filter(
    (item) => item.requirements && item.requirements[skill as keyof ItemRequirement]
  );
}
```

### Get Items by Level Requirement

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
export function getItemsByLevel(level: number): Item[] {
  return Array.from(ITEMS.values()).filter((item) => {
    if (!item.requirements) return true;
    return Object.values(item.requirements).every((req) =>
      typeof req === "number" ? req <= level : true
    );
  });
}
```

***

## Shop Items

Items available in general stores:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
export const SHOP_ITEMS = [
  "bronze_hatchet",
  "fishing_rod",
  "tinderbox",
  "arrows",
];
```

***

## Noted Items

Items can have "noted" variants for efficient storage:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// Check if item can be noted
export function canBeNoted(itemId: string): boolean {
  const item = ITEMS.get(itemId);
  return item?.stackable === false && item?.notedItemId !== undefined;
}

// Get base item from noted item
export function getBaseItem(itemId: string): Item | null {
  const item = ITEMS.get(itemId);
  if (!item) return null;

  if (item.isNoted && item.baseItemId) {
    return ITEMS.get(item.baseItemId) || null;
  }
  return item;
}

// Get noted variant of item
export function getNotedItem(itemId: string): Item | null {
  const item = ITEMS.get(itemId);
  if (!item || item.isNoted) return null;

  if (item.notedItemId) {
    return ITEMS.get(item.notedItemId) || null;
  }
  return null;
}

// Check if item ID is a noted variant
export function isNotedItemId(itemId: string): boolean {
  return itemId.endsWith("_noted");
}

// Get base item ID from noted ID
export function getBaseItemId(itemId: string): string {
  if (isNotedItemId(itemId)) {
    return itemId.replace(/_noted$/, "");
  }
  return itemId;
}
```

***

## Example Item Definitions

### Weapon (weapons.json)

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "id": "bronze_sword",
  "name": "Bronze Sword",
  "type": "weapon",
  "tier": "bronze",
  "value": 100,
  "weight": 2,
  "equipSlot": "weapon",
  "weaponType": "SWORD",
  "attackType": "MELEE",
  "attackSpeed": 4,
  "attackRange": 1,
  "description": "A basic sword made of bronze",
  "examine": "A basic sword made of bronze",
  "tradeable": true,
  "rarity": "common",
  "modelPath": "asset://models/sword-bronze/sword-bronze.glb",
  "equippedModelPath": "asset://models/sword-steel/sword-steel-aligned.glb",
  "iconPath": "asset://models/sword-bronze/concept-art.png",
  "bonuses": {
    "attack": 4,
    "strength": 3,
    "defense": 0,
    "ranged": 0
  }
}
```

### Tool (tools.json)

Tools include a `tool` object specifying the skill and priority. The tier system automatically derives level requirements:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "id": "bronze_hatchet",
  "name": "Bronze Hatchet",
  "type": "tool",
  "tier": "bronze",
  "tool": {
    "skill": "woodcutting",
    "priority": 1
  },
  "value": 50,
  "weight": 1,
  "equipSlot": "weapon",
  "weaponType": "AXE",
  "attackType": "MELEE",
  "attackSpeed": 5,
  "attackRange": 1,
  "description": "A basic hatchet for chopping trees",
  "examine": "A basic hatchet for chopping trees",
  "tradeable": true,
  "rarity": "common",
  "modelPath": "asset://models/hatchet-bronze/hatchet-bronze.glb",
  "iconPath": "asset://models/hatchet-bronze/concept-art.png",
  "bonuses": {
    "attack": 4,
    "strength": 3,
    "defense": 0,
    "ranged": 0
  }
}
```

<Info>
  The `tier: "bronze"` automatically gives this tool `attack: 1` and `woodcutting: 1` requirements from `tier-requirements.json`. Tools with `equipSlot: "weapon"` can be equipped and used for combat.
</Info>

### Resource (resources.json)

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "id": "copper_ore",
  "name": "Copper Ore",
  "type": "resource",
  "stackable": false,
  "maxStackSize": 100,
  "value": 5,
  "weight": 2,
  "description": "Copper ore that can be smelted into a bronze bar",
  "examine": "Ore containing copper. Can be combined with tin to make bronze.",
  "tradeable": true,
  "rarity": "common",
  "modelPath": null,
  "iconPath": "asset://icons/ore-copper.png"
}
```

### Consumable (food.json)

Consumables can include `inventoryActions` to define OSRS-style context menu actions:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "id": "shrimp",
  "name": "Shrimp",
  "type": "consumable",
  "stackable": false,
  "value": 10,
  "weight": 0.2,
  "description": "Some nicely cooked shrimp",
  "examine": "Some nicely cooked shrimp.",
  "tradeable": true,
  "rarity": "common",
  "modelPath": null,
  "iconPath": "asset://icons/shrimp.png",
  "healAmount": 3,
  "inventoryActions": ["Eat", "Use", "Drop", "Examine"]
}
```

<Info>
  The first action in `inventoryActions` becomes the left-click default. If not specified, the system uses heuristic detection based on item properties (food → Eat, potions → Drink, etc.).
</Info>

### Context Menu Color Coding

Context menus use OSRS-accurate color coding for entity names:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From GameConstants.ts
export const CONTEXT_MENU_COLORS = {
  ITEM: "#ff9040",    // Orange for item names
  NPC: "#ffff00",     // Yellow for NPC names
  OBJECT: "#00ffff",  // Cyan for scenery/objects
  PLAYER: "#ffffff",  // White for player names
} as const;
```

**Examples**:

* "Eat **Shrimp**" (orange)
* "Attack **Goblin**" (yellow)
* "Mine **Copper rocks**" (cyan)
* "Trade **Shop keeper**" (yellow)

### Currency (misc.json)

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "id": "coins",
  "name": "Coins",
  "type": "currency",
  "stackable": true,
  "maxStackSize": 2147483647,
  "value": 1,
  "weight": 0,
  "description": "The universal currency of Hyperia",
  "examine": "Gold coins used as currency throughout the realm",
  "tradeable": true,
  "rarity": "always",
  "modelPath": null,
  "iconPath": "asset://icons/coins.png"
}
```

***

## Adding New Items

<Steps>
  <Step title="Choose the right manifest file">
    * Weapons → `items/weapons.json`
    * Tools → `items/tools.json`
    * Resources (ores, logs, bars, raw fish) → `items/resources.json`
    * Food → `items/food.json`
    * Currency, junk → `items/misc.json`
  </Step>

  <Step title="Add entry with proper structure">
    Follow the examples above. For tiered equipment, specify the `tier` field instead of hardcoding requirements.
  </Step>

  <Step title="Create 3D Model (optional)">
    Generate model in 3D Asset Forge if needed
  </Step>

  <Step title="Restart Server">
    Server must restart to reload manifests
  </Step>
</Steps>

<Warning>
  **DO NOT** add item data directly to `items.ts`. Keep all content in JSON manifests for data-driven design.
</Warning>

## Tool Priority System

Tools use a priority system to determine which tool to use when multiple are available:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "tool": {
    "skill": "woodcutting",
    "priority": 1  // Higher = better tool
  }
}
```

For example:

* Bronze hatchet: priority 1
* Iron hatchet: priority 2
* Steel hatchet: priority 3
* Rune hatchet: priority 6

The system automatically selects the highest priority tool the player has equipped or in inventory.

## Inventory Actions System

Items can define explicit context menu actions using the `inventoryActions` array. This is the **OSRS-accurate approach** where actions are stored per-item in the manifest.

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "id": "bronze_sword",
  "inventoryActions": ["Wield", "Use", "Drop", "Examine"]
}
```

**Key Features:**

* First action becomes the **left-click default**
* Actions appear in context menu in the order specified
* "Cancel" is always added automatically as the last option
* Manifest-defined actions take priority over heuristic detection

### Common Action Patterns

| Item Type | Actions                               |
| --------- | ------------------------------------- |
| Food      | `["Eat", "Use", "Drop", "Examine"]`   |
| Potions   | `["Drink", "Use", "Drop", "Examine"]` |
| Weapons   | `["Wield", "Use", "Drop", "Examine"]` |
| Armor     | `["Wear", "Use", "Drop", "Examine"]`  |
| Bones     | `["Bury", "Use", "Drop", "Examine"]`  |
| Generic   | `["Use", "Drop", "Examine"]`          |

### Action Handlers

The following actions have built-in handlers in `InventoryActionDispatcher`:

* **Eat**: Sends `useItem` packet → server validates eat delay (3 ticks) → consumes food → heals player
* **Drink**: Sends `useItem` packet → server validates → applies potion effects
* **Wield**: Sends `equipItem` network message (weapons/shields)
* **Wear**: Sends `equipItem` network message (armor)
* **Bury**: Sends `buryBones` network message
* **Use**: Enters targeting mode for item-on-item/item-on-object interactions
* **Drop**: Calls `world.network.dropItem()`
* **Examine**: Shows examine text in chat and toast
* **Cancel**: Closes context menu (always added automatically)

### Heuristic Fallback

If `inventoryActions` is not specified, the system uses type detection helpers from `item-helpers.ts`:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From packages/shared/src/utils/item-helpers.ts
export function getPrimaryAction(item: Item | null, isNoted: boolean): PrimaryActionType {
  if (isNoted) return "use";
  
  const manifestAction = getPrimaryActionFromManifest(item);
  if (manifestAction) return manifestAction;
  
  // Fallback to heuristic detection
  if (isFood(item)) return "eat";
  if (isPotion(item)) return "drink";
  if (isBone(item)) return "bury";
  if (usesWield(item)) return "wield";
  if (usesWear(item)) return "wear";
  
  return "use";
}
```

**Detection Rules:**

* **Food**: `type: "consumable"` + `healAmount > 0` + not potion
* **Potions**: `type: "consumable"` + `id.includes("potion")`
* **Weapons**: `equipSlot: "weapon"` or `equipSlot: "2h"` or `weaponType` defined
* **Shields**: `equipSlot: "shield"`
* **Armor**: `equipable: true` + not weapon/shield
* **Bones**: `id === "bones"` or `id.endsWith("_bones")`
* **Noted Items**: Always use "use" action (cannot eat/equip noted items)

<Info>
  Manifest-defined `inventoryActions` take priority over heuristic detection. This allows custom actions for special items.
</Info>

***

## Equipment Slots

Items equip to specific slots:

| Slot     | Item Types                      |
| -------- | ------------------------------- |
| `weapon` | Swords, axes, bows, staffs      |
| `shield` | Shields, defenders              |
| `helmet` | Helmets, hats, hoods            |
| `body`   | Platebodies, chainbodies, robes |
| `legs`   | Platelegs, chainlegs, skirts    |
| `boots`  | Boots                           |
| `gloves` | Gloves, bracers                 |
| `cape`   | Capes, cloaks                   |
| `ring`   | Rings                           |
| `amulet` | Amulets, necklaces              |
| `ammo`   | Arrows, bolts, runes            |

***

***

## Ground Item Display

Items dropped on the ground can be configured for visual appearance using `modelScale` and `groundOffset` properties.

### Model Scale

Controls the size of the 3D model when displayed as a ground item:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "id": "logs",
  "modelScale": 0.4,  // 40% of original model size
  // Default: 0.3 (30% scale)
}
```

### Ground Offset

Controls vertical positioning and animation behavior:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
/** Ground item Y positioning:
 *  - When <= 0: model bottom is bbox-snapped to terrain with this as offset (0 = flush)
 *  - When > 0: item floats at this height with bobbing animation
 *  - When undefined: defaults to 0.5 (standard floating ground item)
 */
groundOffset?: number;
```

**Examples:**

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// Grounded item (sits on terrain, no animation)
{
  "id": "logs",
  "groundOffset": 0,  // Model bottom flush with terrain
  "modelScale": 0.4
}

// Grounded with slight offset
{
  "id": "fire",
  "groundOffset": -0.05,  // Model bottom 0.05 units below terrain
  "modelScale": 0.35
}

// Floating item (default behavior)
{
  "id": "coins",
  "groundOffset": 0.5,  // Floats 0.5 units above terrain with bobbing
  "modelScale": 0.3
}

// Floating at custom height
{
  "id": "rare_item",
  "groundOffset": 0.8,  // Floats higher than normal
  "modelScale": 0.35
}
```

### Bounding Box Snapping

When `groundOffset <= 0`, the system automatically calculates the model's bounding box and positions it flush with the terrain:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From ItemEntity.ts
// Grounded items: compute bounding box and snap bottom to terrain level
const gOffset = this.config.groundOffset;
if (gOffset !== undefined && gOffset <= 0) {
  const bbox = new THREE.Box3().setFromObject(this.mesh);
  // 0.2 matches the yOffset passed to groundToTerrain() in GroundItemSystem
  const GROUND_ITEM_TERRAIN_OFFSET = 0.2;
  this.mesh.position.y = -bbox.min.y - GROUND_ITEM_TERRAIN_OFFSET + gOffset;
}
```

**Benefits:**

* Items sit naturally on terrain regardless of model pivot point
* No manual Y-offset tuning required per item
* Works correctly on slopes and uneven terrain
* Fire models and other objects use the same system

**Animation Behavior:**

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// Grounded items (groundOffset <= 0): no animation
if (offset !== undefined && offset <= 0) {
  // Y position set by createMesh bbox snap, no animation
} else {
  // Regular items: float and spin
  const floatHeight = offset ?? 0.5;
  this.mesh.position.y = floatHeight + Math.sin(time * 2) * 0.1;
  this.mesh.rotation.y += deltaTime * 0.5;
}
```

<Info>
  Grounded items (`groundOffset <= 0`) are static and do not float or spin. This is ideal for fires, logs, and other objects that should sit naturally on the ground.
</Info>

***

## Related Documentation

* [NPC Data Structure](/wiki/data/npcs)
* [Combat System](/wiki/game-systems/combat)
* [Skills & Requirements](/wiki/game-systems/skills)
* [Loot System](/wiki/game-systems/loot) (for ground item mechanics)
