> ## 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.

# Manifest-Driven Design

> Data-driven content through TypeScript manifest files

## Overview

Hyperscape uses manifest-driven design where game content is defined in TypeScript data files rather than hardcoded in logic. This enables content creation without modifying game systems.

## Design Philosophy

### Separation of Concerns

| Layer         | Responsibility                    |
| ------------- | --------------------------------- |
| **Manifests** | Content definitions (what exists) |
| **Systems**   | Game logic (how things work)      |
| **Entities**  | Runtime instances                 |

### Benefits

* **Content creators** can add items, NPCs, areas without deep coding
* **Designers** iterate quickly on balance
* **Developers** focus on systems, not data
* **Modders** can extend content easily

## Asset Management

<Warning>
  **Breaking Change (Feb 2026)**: Game assets (3D models, textures, audio) and manifests are now sourced from the separate [HyperscapeAI/assets](https://github.com/HyperscapeAI/assets) repository. The `packages/server/world/assets/` directory is fully gitignored and populated exclusively by cloning the assets repo.
</Warning>

**Local Development:**

* Assets are auto-downloaded during `bun install` via `postinstall` hook
* Full clone with Git LFS (\~200MB) for complete asset access
* Located at `packages/server/world/assets/`

**CI/Production:**

* Assets cloned via `ensure-assets.mjs` script (shallow, no LFS)
* Manifests-only clone for server startup (models served from CDN)
* Prevents manifest divergence between repos

**Why This Change:**

* Manifests were being committed directly to hyperscape repo to fix CI, causing divergence with assets repo
* Assets directory is now the single source of truth for all game content
* Eliminates dual-maintenance burden and sync issues

## Manifest Files

All manifests are in `packages/server/world/assets/manifests/` (cloned from HyperscapeAI/assets):

| File                     | Content                                                      |
| ------------------------ | ------------------------------------------------------------ |
| `npcs.json`              | Mobs and NPCs                                                |
| `items/`                 | Equipment, resources, consumables (split by category)        |
| `banks-stores.json`      | Shop inventories                                             |
| `world-areas.json`       | Zones and regions                                            |
| `avatars.json`           | Character models                                             |
| `tier-requirements.json` | Equipment level requirements by tier                         |
| `skill-unlocks.json`     | What unlocks at each skill level                             |
| `gathering/`             | Resource gathering data (woodcutting, mining, fishing)       |
| `recipes/`               | Processing recipes (cooking, firemaking, smelting, smithing) |
| `stations.json`          | World station configurations (anvils, furnaces, ranges)      |
| `prayers.json`           | Prayer definitions with bonuses and drain rates              |
| `model-bounds.json`      | Auto-generated model footprints (build-time only)            |

## Manifest Structure

### NPCs (`npcs.json`)

NPC data is loaded from JSON manifests at runtime by DataManager:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
[
  {
    "id": "goblin",
    "name": "Goblin",
    "description": "A weak goblin creature",
    "category": "mob",
    "faction": "monster",
    "stats": {
      "level": 2,
      "health": 5,
      "attack": 1,
      "strength": 1,
      "defense": 1
    },
    "combat": {
      "attackable": true,
      "aggressive": true,
      "retaliates": true,
      "aggroRange": 4,
      "combatRange": 1,
      "attackSpeedTicks": 4,
      "respawnTicks": 35
    },
    "drops": {
      "defaultDrop": { "enabled": true, "itemId": "bones", "quantity": 1 },
      "common": [{ "itemId": "coins", "minQuantity": 5, "maxQuantity": 15, "chance": 1.0, "rarity": "common" }]
    },
    "appearance": { "modelPath": "asset://models/goblin/goblin.vrm", "scale": 0.75 }
  }
]
```

<Info>
  NPC definitions are in `world/assets/manifests/npcs.json`, not hardcoded in TypeScript.
</Info>

### Items Directory

Items are now organized into separate JSON files by category for better maintainability:

```
manifests/items/
├── weapons.json      # Combat weapons (swords, axes, bows)
├── 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
```

All item files are loaded atomically by DataManager - if any required file is missing, the system falls back to the legacy `items.json` format.

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
// From packages/shared/src/data/items.ts
/**
 * Item Database
 *
 * Items are loaded from world/assets/manifests/items/ directory by DataManager.
 * This file provides the empty Map that gets populated at runtime.
 *
 * To add new items:
 * 1. Add entries to appropriate file in world/assets/manifests/items/
 * 2. Generate 3D models in 3D Asset Forge (optional)
 * 3. Restart the server to reload manifests
 */

// Item Database - Populated by DataManager
export const ITEMS: Map<string, Item> = new Map();

// Helper functions
export function getItem(itemId: string): Item | null {
  return ITEMS.get(itemId) || null;
}
```

### Tools (tools.json)

Tools include a `tool` object specifying skill, priority, and optional bonus mechanics:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "id": "dragon_pickaxe",
  "name": "Dragon Pickaxe",
  "type": "tool",
  "tier": "dragon",
  "tool": {
    "skill": "mining",
    "priority": 2,
    "rollTicks": 3,
    "bonusTickChance": 0.167,
    "bonusRollTicks": 2
  },
  "equipSlot": "weapon",
  "weaponType": "PICKAXE",
  "attackType": "MELEE",
  "value": 50000,
  "weight": 2.2,
  "description": "A powerful pickaxe with a chance for bonus mining speed",
  "examine": "A pickaxe with a dragon metal head.",
  "tradeable": true,
  "rarity": "rare",
  "modelPath": "asset://models/pickaxe-dragon/pickaxe-dragon.glb"
}
```

#### Tool Properties

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
interface GatheringToolData {
  skill: "woodcutting" | "mining" | "fishing";
  priority: number;              // Lower = better (1 = best)
  levelRequired: number;         // Minimum skill level
  rollTicks?: number;            // Mining: ticks between roll attempts
  bonusTickChance?: number;      // Mining: chance for bonus speed roll
  bonusRollTicks?: number;       // Mining: tick count when bonus triggers
}
```

**Mining Pickaxe Bonus Speed:**

Dragon and crystal pickaxes have a chance to mine faster:

| Pickaxe | Roll Ticks | Bonus Chance | Bonus Ticks | Avg Speed  |
| ------- | ---------- | ------------ | ----------- | ---------- |
| Bronze  | 8          | -            | -           | 8 ticks    |
| Rune    | 3          | -            | -           | 3 ticks    |
| Dragon  | 3          | 1/6 (0.167)  | 2           | 2.83 ticks |
| Crystal | 3          | 1/4 (0.25)   | 2           | 2.75 ticks |

The bonus roll is determined **server-side** to maintain determinism and prevent client/server desyncs.

<Info>
  Tools with `equipSlot: "weapon"` can be equipped and used for combat. The tier system automatically derives level requirements from `tier-requirements.json`.
</Info>

### Inventory Actions

Items can define explicit `inventoryActions` for OSRS-accurate context menus:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "id": "shrimp",
  "name": "Shrimp",
  "type": "consumable",
  "healAmount": 3,
  "inventoryActions": ["Eat", "Use", "Drop", "Examine"]
}
```

The first action becomes the left-click default. Supported actions:

| Action      | Use Case         | Example Items                  |
| ----------- | ---------------- | ------------------------------ |
| **Eat**     | Food items       | Shrimp, Lobster, Shark         |
| **Drink**   | Potions          | Strength potion, Attack potion |
| **Wield**   | Weapons, shields | Bronze sword, Wooden shield    |
| **Wear**    | Armor            | Bronze platebody, Leather body |
| **Bury**    | Bones            | Bones, Big bones, Dragon bones |
| **Use**     | Tools, misc      | Tinderbox, Hammer, Logs        |
| **Drop**    | Any item         | (always available)             |
| **Examine** | Any item         | (always available)             |

If `inventoryActions` is not specified, the system falls back to type-based detection using `item-helpers.ts`.

### Gathering Resources (`gathering/`)

**gathering/woodcutting.json** - Trees and log yields:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "trees": [
    {
      "id": "tree_normal",
      "name": "Tree",
      "type": "tree",
      "harvestSkill": "woodcutting",
      "toolRequired": "bronze_hatchet",
      "levelRequired": 1,
      "baseCycleTicks": 4,
      "depleteChance": 0.125,
      "respawnTicks": 80,
      "harvestYield": [
        { "itemId": "logs", "quantity": 1, "chance": 1.0, "xpAmount": 25 }
      ]
    }
  ]
}
```

**gathering/mining.json** - Ore rocks with OSRS-accurate success rates:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "rocks": [
    {
      "id": "rock_copper",
      "name": "Copper rocks",
      "type": "mining_rock",
      "harvestSkill": "mining",
      "toolRequired": "bronze_pickaxe",
      "levelRequired": 1,
      "baseCycleTicks": 8,
      "depleteChance": 1.0,
      "respawnTicks": 200,
      "harvestYield": [
        { 
          "itemId": "copper_ore", 
          "quantity": 1, 
          "chance": 1.0, 
          "xpAmount": 17.5,
          "successRate": { "low": 100, "high": 256 }
        }
      ]
    },
    {
      "id": "rock_rune_essence",
      "name": "Rune essence",
      "type": "mining_rock",
      "harvestSkill": "mining",
      "toolRequired": "bronze_pickaxe",
      "levelRequired": 1,
      "baseCycleTicks": 8,
      "depleteChance": 0.0,
      "respawnTicks": 0,
      "harvestYield": [
        { 
          "itemId": "rune_essence", 
          "quantity": 1, 
          "chance": 1.0, 
          "xpAmount": 5.0
        }
      ]
    }
  ]
}
```

<Info>
  **Mining Depletion (Updated March 2026)**: Depletion is now manifest-driven via `depleteChance`:

  * `depleteChance: 1.0` — Always depletes on success (regular ores)
  * `depleteChance: 0.0` — Never depletes (rune essence rocks, OSRS-accurate)
  * `depleteChance: 0.125` — 1/8 chance to deplete (special rocks)

  Respawn is purely tick-based via `ResourceSystem.processRespawns()`. The legacy `setTimeout`-based respawn was removed in PR #1099.
</Info>

### Gathering Resources

Resource gathering data is split by skill for better organization:

```
manifests/gathering/
├── woodcutting.json  # Trees, logs, XP values, level requirements
├── mining.json       # Ore rocks, ores, XP values, level requirements
└── fishing.json      # Fishing spots, fish, XP values, level requirements
```

Each manifest defines the resources, their requirements, XP rewards, and drop tables.

### Processing Recipes

Processing recipes are organized by skill:

```
manifests/recipes/
├── cooking.json      # Raw → cooked food recipes
├── firemaking.json   # Log burning recipes
├── smelting.json     # Ore → bar recipes (at furnaces)
└── smithing.json     # Bar → equipment recipes (at anvils)
```

These manifests define inputs, outputs, level requirements, XP rewards, and tick-based timing.

### Station Configurations

World stations (anvils, furnaces, ranges, banks) are configured in `stations.json`:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "stations": [
    {
      "type": "anvil",
      "name": "Anvil",
      "model": "asset://models/anvil/anvil.glb",
      "modelScale": 0.5,
      "modelYOffset": 0.4,
      "examine": "An anvil. Used to make things out of metal."
    },
    {
      "type": "furnace",
      "name": "Furnace",
      "model": "asset://models/furnace/furnace.glb",
      "modelScale": 1.5,
      "modelYOffset": 1.0,
      "examine": "A very hot furnace."
    }
  ]
}
```

This allows 3D models and configurations to be updated without code changes. The `modelScale` and `modelYOffset` properties control the visual appearance of stations in the world.

### Stations (`stations.json`)

Defines crafting stations and interactive objects in the world:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "stations": [
    {
      "type": "anvil",
      "name": "Anvil",
      "model": "asset://models/anvil/anvil.glb",
      "modelScale": 0.5,
      "modelYOffset": 0.2,
      "examine": "An anvil for smithing metal bars into weapons and tools."
    },
    {
      "type": "furnace",
      "name": "Furnace",
      "model": "asset://models/furnace/furnace.glb",
      "modelScale": 1.5,
      "modelYOffset": 1.0,
      "examine": "A furnace for smelting ores into metal bars."
    }
  ]
}
```

Station types include:

* **Anvil** — Smith bars into equipment
* **Furnace** — Smelt ores into bars
* **Range** — Cook food with reduced burn chance
* **Bank** — Store items

## DataManager

Smithing, cooking, and other processing skills use recipe manifests:

**Smelting Recipe** (`recipes/smelting.json`):

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "recipes": [
    {
      "output": "bronze_bar",
      "inputs": [
        { "item": "copper_ore", "amount": 1 },
        { "item": "tin_ore", "amount": 1 }
      ],
      "level": 1,
      "xp": 6.25,
      "ticks": 4,
      "successRate": 1.0
    }
  ]
}
```

**Smithing Recipe** (`recipes/smithing.json`):

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "recipes": [
    {
      "output": "bronze_sword",
      "bar": "bronze_bar",
      "barsRequired": 1,
      "level": 4,
      "xp": 12.5,
      "ticks": 4,
      "category": "weapons"
    }
  ]
}
```

### Tier Requirements (`tier-requirements.json`)

Defines level requirements by equipment tier:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "melee": {
    "bronze": { "attack": 1, "defence": 1 },
    "iron": { "attack": 1, "defence": 1 },
    "steel": { "attack": 5, "defence": 5 },
    "mithril": { "attack": 20, "defence": 20 },
    "adamant": { "attack": 30, "defence": 30 },
    "rune": { "attack": 40, "defence": 40 }
  },
  "tools": {
    "bronze": { "attack": 1, "woodcutting": 1, "mining": 1 },
    "steel": { "attack": 5, "woodcutting": 6, "mining": 6 },
    "mithril": { "attack": 20, "woodcutting": 21, "mining": 21 }
  }
}
```

### Prayers (`prayers.json`)

Defines prayer bonuses, drain rates, and conflicts:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "prayers": [
    {
      "id": "thick_skin",
      "name": "Thick Skin",
      "description": "Increases Defense by 5%",
      "icon": "🛡️",
      "level": 1,
      "category": "defensive",
      "drainEffect": 3,
      "bonuses": {
        "defenseMultiplier": 1.05
      },
      "conflicts": ["rock_skin", "steel_skin"]
    },
    {
      "id": "burst_of_strength",
      "name": "Burst of Strength",
      "description": "Increases Strength by 5%",
      "icon": "💪",
      "level": 4,
      "category": "offensive",
      "drainEffect": 3,
      "bonuses": {
        "strengthMultiplier": 1.05
      },
      "conflicts": ["superhuman_strength"]
    }
  ]
}
```

**Prayer Fields:**

* `id` — Unique prayer ID (lowercase, underscores, max 64 chars)
* `name` — Display name
* `description` — Effect description for tooltip
* `icon` — Emoji icon for UI
* `level` — Required Prayer level (1-99)
* `category` — "offensive", "defensive", or "utility"
* `drainEffect` — Drain rate (higher = faster drain)
* `bonuses` — Combat stat multipliers (attackMultiplier, strengthMultiplier, defenseMultiplier)
* `conflicts` — Array of prayer IDs that conflict with this prayer

<Info>
  Prayer bonuses are multipliers applied to base stats. A value of 1.05 means +5%, 1.10 means +10%.
</Info>

### Station Configuration (`stations.json`)

Defines world stations with 3D models:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "stations": [
    {
      "type": "anvil",
      "name": "Anvil",
      "model": "asset://models/anvil/anvil.glb",
      "modelScale": 0.5,
      "modelYOffset": 0.4,
      "examine": "An anvil for smithing metal bars into weapons and tools."
    },
    {
      "type": "furnace",
      "name": "Furnace",
      "model": "asset://models/furnace/furnace.glb",
      "modelScale": 1.5,
      "modelYOffset": 1.0,
      "examine": "A furnace for smelting ores into metal bars."
    }
  ]
}
```

## Data Providers

The manifest system uses specialized data providers for efficient lookups:

| Provider                 | Purpose                                         | Manifest Source          |
| ------------------------ | ----------------------------------------------- | ------------------------ |
| `ProcessingDataProvider` | Cooking, firemaking, smelting, smithing recipes | `recipes/*.json`         |
| `TierDataProvider`       | Equipment level requirements by tier            | `tier-requirements.json` |
| `StationDataProvider`    | Station models and configurations               | `stations.json`          |
| `PrayerDataProvider`     | Prayer definitions, bonuses, conflicts          | `prayers.json`           |
| `DataManager`            | Central loader for all manifests                | All manifests            |

### DataManager

The `DataManager` class in `packages/shared/src/data/DataManager.ts` loads all manifests from JSON files and populates runtime data structures:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
import { dataManager } from '@hyperscape/shared';

// Initialize (loads all manifests)
await dataManager.initialize();

// Access loaded data via global maps
import { ITEMS, ALL_NPCS } from '@hyperscape/shared';

const item = ITEMS.get("bronze_sword");
const npc = ALL_NPCS.get("goblin");
```

### Manifest Loading

DataManager supports two loading modes:

1. **Filesystem** (server-side): Loads from `packages/server/world/assets/manifests/`
2. **CDN** (client-side): Fetches from `http://localhost:8080/assets/manifests/`

The loading is **atomic** for directory-based manifests - all required files must exist or it falls back to legacy single-file format.

## Adding Content

### Step 1: Choose the Right Manifest

Determine which manifest file to edit based on content type:

* **Items**: `manifests/items/weapons.json`, `tools.json`, `resources.json`, `food.json`, or `misc.json`
* **NPCs/Mobs**: `manifests/npcs.json`
* **Gathering Resources**: `manifests/gathering/woodcutting.json`, `mining.json`, or `fishing.json`
* **Processing Recipes**: `manifests/recipes/cooking.json`, `firemaking.json`, `smelting.json`, or `smithing.json`
* **Stations**: `manifests/stations.json`
* **World Areas**: `manifests/world-areas.json`

### Step 2: Edit Manifest

Add your content following the existing structure. Use tier-based requirements for equipment:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "id": "mithril_sword",
  "name": "Mithril Sword",
  "type": "weapon",
  "tier": "mithril",  // Automatically gets attack: 20 requirement
  "weaponType": "SWORD",
  "attackSpeed": 4
}
```

### Step 3: Restart Server

Manifests are loaded at server startup. Restart to apply changes:

```bash theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
bun run dev
```

### Step 4: Verify

Check the game to ensure content appears correctly. Use the Skills panel to verify level requirements.

## Validation

Manifests are JSON files validated at runtime by DataManager:

* **Schema validation**: Invalid fields logged as warnings
* **Duplicate detection**: Duplicate item IDs across files cause errors
* **Reference checking**: Invalid itemId/npcId references caught at runtime
* **Atomic loading**: Items directory loads all files or falls back to legacy format

<Warning>
  Invalid JSON syntax will cause server startup to fail. Use a JSON validator before committing changes.
</Warning>

## Data Providers

The manifest system uses specialized data providers for efficient lookups:

| Provider                 | Purpose                                         | Manifest Source          |
| ------------------------ | ----------------------------------------------- | ------------------------ |
| `ProcessingDataProvider` | Cooking, firemaking, smelting, smithing recipes | `recipes/*.json`         |
| `TierDataProvider`       | Equipment level requirements by tier            | `tier-requirements.json` |
| `StationDataProvider`    | Station models and configurations               | `stations.json`          |
| `DataManager`            | Central loader for all manifests                | All manifests            |

These providers build optimized lookup tables at startup for fast runtime queries.

### PrayerDataProvider Usage

Access prayer definitions at runtime:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
import { prayerDataProvider } from '@hyperscape/shared';

// Get prayer definition
const prayer = prayerDataProvider.getPrayer("thick_skin");
// Returns: { id, name, description, icon, level, category, drainEffect, bonuses, conflicts }

// Get all prayers available at player's level
const available = prayerDataProvider.getAvailablePrayers(prayerLevel);

// Check for conflicts
const conflicts = prayerDataProvider.getConflictsWithActive("rock_skin", activePrayers);

// Validate activation
const canActivate = prayerDataProvider.canActivatePrayer(
  "burst_of_strength",
  prayerLevel,
  currentPoints,
  activePrayers
);
```

**Prayer Loading:**

* Loaded by `DataManager` at startup
* Validates prayer ID format, bonuses, and conflicts
* Builds optimized lookup tables by level and category
* Provides type-safe access methods

### StationDataProvider Usage

Access station configurations at runtime:

```typescript theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
import { stationDataProvider } from '@hyperscape/shared';

// Get full station data
const anvilData = stationDataProvider.getStationData("anvil");
// Returns: { type, name, model, modelScale, modelYOffset, examine }

// Get specific properties
const modelPath = stationDataProvider.getModelPath("furnace");
const scale = stationDataProvider.getModelScale("anvil");
const yOffset = stationDataProvider.getModelYOffset("furnace");

// Station entities use this for model loading
// AnvilEntity and FurnaceEntity automatically load models from manifest
// Falls back to placeholder geometry if model loading fails
```

**Station Model Loading**:

* Models loaded via `ModelCache` with transform baking
* `modelYOffset` raises model so base sits on ground
* Graceful fallback to blue box placeholder if model fails
* Shadows and raycasting layers configured automatically

### Prayers (`prayers.json`)

The `prayers.json` manifest defines OSRS-accurate prayer abilities with stat bonuses and drain mechanics:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "$schema": "./prayers.schema.json",
  "_comment": "OSRS-accurate prayer definitions. drainEffect: higher = faster drain.",
  "prayers": [
    {
      "id": "thick_skin",
      "name": "Thick Skin",
      "description": "Increases your Defence by 5%",
      "icon": "prayer_thick_skin",
      "level": 1,
      "category": "defensive",
      "drainEffect": 1,
      "bonuses": {
        "defenseMultiplier": 1.05
      },
      "conflicts": ["rock_skin", "steel_skin", "chivalry", "piety"]
    }
  ]
}
```

**Properties**:

* `id` — Unique prayer identifier
* `name` — Display name shown in prayer book
* `description` — Effect description for tooltip
* `icon` — Icon asset path for prayer book UI
* `level` — Prayer level required to unlock
* `category` — Prayer type: `offensive`, `defensive`, or `utility`
* `drainEffect` — Drain rate (higher = faster drain)
* `bonuses` — Stat multipliers applied when active
  * `attackMultiplier` — Attack bonus (e.g., 1.05 = +5%)
  * `strengthMultiplier` — Strength bonus
  * `defenseMultiplier` — Defense bonus
* `conflicts` — Array of prayer IDs that cannot be active simultaneously

**Categories**:

* **Offensive** — Attack and strength bonuses (Burst of Strength, Clarity of Thought)
* **Defensive** — Defense bonuses (Thick Skin, Rock Skin, Steel Skin)
* **Utility** — Special effects (future: Protect from Melee, Rapid Heal)

**Conflict System**:
Prayers in the same category typically conflict. Activating a prayer automatically deactivates conflicting prayers (OSRS-accurate behavior).

<Info>
  Prayer drain rates follow OSRS formulas. The `drainEffect` value determines how quickly prayer points deplete while the prayer is active.
</Info>

### Model Bounds (`model-bounds.json`)

The `model-bounds.json` manifest contains pre-calculated bounding box data for all 3D models in the game. This data is used for spatial calculations, collision detection, and tile-based placement:

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "generatedAt": "2026-01-15T11:00:57.005Z",
  "tileSize": 1,
  "models": [
    {
      "id": "anvil",
      "assetPath": "asset://models/anvil/anvil.glb",
      "bounds": {
        "min": { "x": -1.0048660039901733, "y": -0.6355410218238831, "z": -0.5779970288276672 },
        "max": { "x": 1.007843017578125, "y": 0.6261950135231018, "z": 0.5753570199012756 }
      },
      "dimensions": {
        "x": 2.0127090215682983,
        "y": 1.2617360353469849,
        "z": 1.1533540487289429
      },
      "footprint": {
        "width": 2,
        "depth": 1
      }
    }
  ]
}
```

**Properties**:

* `bounds` — Minimum and maximum coordinates of the model's bounding box
* `dimensions` — Calculated width (x), height (y), and depth (z) of the model
* `footprint` — Tile-based footprint for placement (width × depth in tiles)
* `generatedAt` — Timestamp of when bounds were calculated
* `tileSize` — Base tile size used for footprint calculations (typically 1.0)

**Use Cases**:

* **Placement Validation** — Ensure entities fit within available space
* **Collision Detection** — Fast AABB checks for physics and interactions
* **Tile Occupancy** — Calculate which tiles an entity occupies
* **Spatial Queries** — Optimize raycasting and proximity checks

The bounds are automatically generated from the actual 3D model geometry and updated when models change.

## Best Practices

1. **Use descriptive IDs**: `bronze_sword` not `sword1`
2. **Follow naming conventions**: snake\_case for IDs
3. **Organize by category**: Use the directory structure (items/, recipes/, gathering/)
4. **Test after changes**: Verify in-game before committing
5. **Keep data flat**: Avoid deep nesting in manifest structures
6. **Use tier system**: Leverage TierDataProvider for equipment requirements instead of hardcoding
7. **Validate JSON**: Use a JSON validator before committing to catch syntax errors

## Manifest Loading Order

DataManager loads manifests in this order:

1. **Tier requirements** (`tier-requirements.json`) - Needed for item normalization
2. **Model bounds** (`model-bounds.json`) - Needed for station footprint calculation
3. **Items** (`items/` directory or `items.json` fallback)
4. **NPCs** (`npcs.json`)
5. **Gathering resources** (`gathering/*.json`)
6. **Recipe manifests** (`recipes/*.json`)
7. **Skill unlocks** (`skill-unlocks.json`)
8. **Prayers** (`prayers.json`)
9. **Stations** (`stations.json`) - Uses model bounds for footprints
10. **World areas** (`world-areas.json`)
11. **Stores** (`stores.json`)

This order ensures dependencies are loaded before dependent data (e.g., model bounds before stations).

## Build-Time Manifests

### Model Bounds Extraction

The `model-bounds.json` manifest is **auto-generated** during build:

```bash theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
# Runs automatically via Turbo (cached)
bun run extract-bounds
```

**Process:**

1. Scans `world/assets/models/**/*.glb` files
2. Parses glTF position accessor min/max values
3. Calculates bounding boxes and footprints at scale 1.0
4. Writes to `world/assets/manifests/model-bounds.json`

**Output Format:**

```json theme={"theme":{"light":"github-light","dark":"tokyo-night"}}
{
  "generatedAt": "2026-01-15T11:25:00.000Z",
  "tileSize": 1.0,
  "models": [
    {
      "id": "furnace",
      "assetPath": "asset://models/furnace/furnace.glb",
      "bounds": {
        "min": { "x": -0.755, "y": 0.0, "z": -0.725 },
        "max": { "x": 0.755, "y": 2.1, "z": 0.725 }
      },
      "dimensions": { "x": 1.51, "y": 2.1, "z": 1.45 },
      "footprint": { "width": 2, "depth": 1 }
    }
  ]
}
```

**Runtime Usage:**

* `StationDataProvider` loads this manifest at startup
* Combines model bounds × `modelScale` from `stations.json`
* Calculates final collision footprint for each station type
* No manual footprint configuration needed

<Warning>
  Do not edit `model-bounds.json` manually. It is regenerated on every build when GLB files change.
</Warning>
