playlist-data-engine 1.7.3 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +14 -0
  2. package/bin/cli.cjs +85 -0
  3. package/dist/gateway-CDMPqFEH.js +1320 -0
  4. package/dist/gateway-DKa45Uz6.cjs +6 -0
  5. package/dist/gateway.d.ts +1 -0
  6. package/dist/gateway.d.ts.map +1 -1
  7. package/dist/gateway.js +1 -1
  8. package/dist/gateway.mjs +22 -19
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/playlist-data-engine.js +4 -4
  12. package/dist/playlist-data-engine.mjs +30 -27
  13. package/dist/utils/engineDocs.d.ts +33 -0
  14. package/dist/utils/engineDocs.d.ts.map +1 -0
  15. package/docs/DATA_ENGINE_REFERENCE.md +6660 -0
  16. package/docs/USAGE_IN_OTHER_PROJECTS.md +587 -0
  17. package/docs/features/AUDIO_ANALYSIS.md +610 -0
  18. package/docs/features/BEAT_DETECTION.md +5250 -0
  19. package/docs/features/COMBAT_SYSTEM.md +1632 -0
  20. package/docs/features/CONTENT_PACKS.md +464 -0
  21. package/docs/features/CUSTOM_CONTENT.md +603 -0
  22. package/docs/features/ENEMY_GENERATION.md +1711 -0
  23. package/docs/features/EQUIPMENT_SYSTEM.md +2279 -0
  24. package/docs/features/EXTENSIBILITY_GUIDE.md +1106 -0
  25. package/docs/features/GATEWAY_RESOLUTION.md +725 -0
  26. package/docs/features/IRL_SENSORS.md +360 -0
  27. package/docs/features/PLAYLIST_PARSING.md +446 -0
  28. package/docs/features/PREREQUISITES.md +571 -0
  29. package/docs/features/ROLLS_AND_SEEDS.md +687 -0
  30. package/docs/features/XP_AND_STATS.md +1221 -0
  31. package/llms.txt +33 -0
  32. package/package.json +9 -2
  33. package/skills/playlist-data-engine/SKILL.md +69 -0
  34. package/dist/gateway-C_p9Ku3O.js +0 -1211
  35. package/dist/gateway-Ceg-5xug.cjs +0 -1
@@ -0,0 +1,2279 @@
1
+ # Equipment System Documentation
2
+
3
+ Complete reference for the Playlist Data Engine's Advanced Equipment System.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Overview](#overview)
8
+ 2. [Enhanced Equipment](#enhanced-equipment)
9
+ 3. [Equipment Properties](#equipment-properties)
10
+ 4. [Equipment Effects](#equipment-effects)
11
+ 5. [Equipment Modification](#equipment-modification)
12
+ 6. [Spawn Weights](#spawn-weights)
13
+ 7. [Enchantment Library](#enchantment-library)
14
+ 8. [Magic Item System](#magic-item-system)
15
+ 9. [Custom Equipment](#custom-equipment)
16
+ 10. [Box Equipment Type](#box-equipment-type)
17
+ 11. [API Reference](#api-reference)
18
+ 12. [Examples](#examples)
19
+
20
+ ---
21
+
22
+ ## Quick Start
23
+
24
+ Get up and running with the equipment system in 5 minutes.
25
+
26
+ ### 1. Register Custom Equipment
27
+
28
+ ```typescript
29
+ import { ExtensionManager } from 'playlist-data-engine';
30
+
31
+ const flamingSword = {
32
+ name: 'Flaming Sword',
33
+ type: 'weapon',
34
+ rarity: 'rare',
35
+ weight: 3,
36
+ damage: { dice: '1d8', damageType: 'slashing' },
37
+ icon: '/icons/weapons/flaming-sword.png',
38
+ properties: [{
39
+ type: 'damage_bonus',
40
+ target: 'fire',
41
+ value: '1d6',
42
+ description: '+1d6 fire damage'
43
+ }]
44
+ };
45
+
46
+ const manager = ExtensionManager.getInstance();
47
+ manager.register('equipment', [flamingSword]);
48
+ ```
49
+
50
+ ### 2. Spawn Equipment
51
+
52
+ ```typescript
53
+ import { EquipmentSpawnHelper, SeededRNG } from 'playlist-data-engine';
54
+
55
+ const rng = new SeededRNG('loot_seed');
56
+
57
+ // Spawn by name
58
+ const item = EquipmentSpawnHelper.spawnFromList(['Flaming Sword']);
59
+
60
+ // Spawn by rarity
61
+ const rareItems = EquipmentSpawnHelper.spawnByRarity('rare', 3, rng);
62
+
63
+ // Spawn random (respects spawn weights)
64
+ const loot = EquipmentSpawnHelper.spawnRandom(5, rng, { excludeZeroWeight: true });
65
+
66
+ // Add to character
67
+ character = EquipmentSpawnHelper.addToCharacter(character, loot, false);
68
+ ```
69
+
70
+ ### 3. Apply Equipment Effects
71
+
72
+ ```typescript
73
+ import { EquipmentEffectApplier } from 'playlist-data-engine';
74
+
75
+ // Equip item and apply effects
76
+ const result = EquipmentEffectApplier.equipItem(character, equipment);
77
+
78
+ // Check if equip succeeded (e.g., stat requirements met)
79
+ if (!result.applied) {
80
+ // Item was not equipped — requirements not satisfied
81
+ console.log('Equip failed:', result.reason);
82
+ }
83
+
84
+ // Unequip and remove effects
85
+ EquipmentEffectApplier.unequipItem(character, 'Flaming Sword');
86
+ ```
87
+
88
+ **Next Steps**: See [Enhanced Equipment](#enhanced-equipment) for the main equipment interface, [Equipment Properties](#equipment-properties) for all property types, or [API Reference](#api-reference) for complete class documentation.
89
+
90
+ ---
91
+
92
+ ## Overview
93
+
94
+ The Advanced Equipment System transforms the basic equipment database into a comprehensive item system supporting:
95
+
96
+ - **Equipment Properties**: Stat bonuses, skill proficiencies, ability unlocks, passive modifiers, special properties, damage bonuses, spell grants
97
+ - **Equipment-Granted Features**: Items can provide unique features or reference existing registry features
98
+ - **D&D 5e Standard Stats**: All existing equipment populated with default damage dice, AC, and properties
99
+ - **Custom Equipment Support**: ExtensionManager integration for custom equipment with full property support
100
+ - **Runtime Equipment Modification**: Template-based items (Flaming Sword) + per-instance enchanting/upgrading
101
+ - **Helper Functions**: Batch equipment spawning utilities
102
+
103
+ ### Design Principles
104
+
105
+ - **Backward Compatible**: Existing characters and equipment continue to work
106
+ - **Weight-Based Spawning**: Features have spawn weights (0 = never random, still available to game logic)
107
+ - **Template + Instance**: Support both equipment templates AND per-item unique modifications
108
+ - **D&D 5e Aligned**: Default equipment uses standard 5e stats
109
+ - **Feature-Aligned**: Follows existing FeatureEffect
110
+ - **Data Structure Focus**: Provides structures, not full gameplay systems
111
+
112
+ ### System Architecture
113
+
114
+ ```
115
+ ExtensionManager
116
+ | equipment (default + custom items)
117
+ | equipment.templates (template definitions)
118
+ |
119
+ v
120
+ EquipmentValidator
121
+ | Validates all equipment data
122
+ |
123
+ v
124
+ EquipmentEffectApplier
125
+ | Applies/removes equipment effects
126
+ |
127
+ v
128
+ CharacterSheet
129
+ | equipment_effects[] (tracks active effects)
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Enhanced Equipment
135
+
136
+ The `EnhancedEquipment` interface extends the base equipment with advanced capabilities.
137
+
138
+ ### EnhancedEquipment Interface
139
+
140
+ **Location:** [src/core/types/Equipment.ts](../src/core/types/Equipment.ts)
141
+
142
+ Primary equipment type with advanced properties support. Extends base equipment with properties, granted features/skills/spells, and spawn weights.
143
+
144
+ | Property | Type | Description |
145
+ |----------|------|-------------|
146
+ | `properties` | `EquipmentProperty[]` | Advanced properties (stat bonuses, damage, etc.) |
147
+ | `grantsFeatures` | Array<string \| EquipmentMiniFeature> | Features granted when equipped |
148
+ | `grantsSkills` | Array<{skillId, level}> | Skills granted when equipped |
149
+ | `grantsSpells` | Array<{spellId, level?, uses?, recharge?}> | Spells granted when equipped |
150
+ | `spawnWeight` | number | Spawn weight (0 = game-only) |
151
+ | `source` | `'default' \| 'custom'` | Source tracking |
152
+ | `tags` | string[] | Search/filter tags |
153
+ | `icon` | string | Optional icon URL for small UI display |
154
+ | `image` | string | Optional image URL for larger display |
155
+
156
+ For an example of the `EnhancedEquipment` interface, see [Example 1: Comprehensive Custom Item](#example-1-comprehensive-custom-item).
157
+
158
+ ---
159
+
160
+ ## Equipment Properties
161
+
162
+ Equipment properties define how items affect gameplay. Each property has a type, target, value, optional condition, and optional description.
163
+
164
+ ### EquipmentPropertyType
165
+
166
+ **Location:** [src/core/types/Equipment.ts](../src/core/types/Equipment.ts)
167
+
168
+ Equipment properties define how items affect gameplay. Each property has a type, target, value, optional condition, and optional description.
169
+
170
+ **For complete reference**, see [DATA_ENGINE_REFERENCE.md - EquipmentPropertyType](../DATA_ENGINE_REFERENCE.md#equipmentpropertytype).
171
+
172
+ | Type | Description |
173
+ |------|-------------|
174
+ | `stat_bonus` | Increases ability scores (STR, DEX, CON, INT, WIS, CHA) |
175
+ | `skill_proficiency` | Grants skill proficiency or expertise |
176
+ | `ability_unlock` | Unlocks special abilities (darkvision, flight, etc.) |
177
+ | `passive_modifier` | Modifies passive values. For AC, supports formula strings (e.g., `"11 + DEX"`, `"14 + min(DEX, 2)"`, `"16"`) which are evaluated by `EquipmentEffectApplier.evaluateACFormula()`. Numeric values are treated as additive bonuses (shields, rings, etc.) |
178
+ | `special_property` | Game-specific properties (finesse, versatile, etc.) |
179
+ | `damage_bonus` | Adds extra damage (fire, cold, lightning, etc.) |
180
+ | `stat_requirement` | Minimum stat required to use item |
181
+
182
+ ### EquipmentCondition
183
+
184
+ **Location:** [src/core/types/Equipment.ts](../src/core/types/Equipment.ts)
185
+
186
+ **For complete reference**, see [DATA_ENGINE_REFERENCE.md - EquipmentCondition](../DATA_ENGINE_REFERENCE.md#equipmentcondition).
187
+
188
+ | Type | Value Format | Description |
189
+ |------|--------------|-------------|
190
+ | `vs_creature_type` | string | Property applies vs specific creature |
191
+ | `at_time_of_day` | day/night/dawn/dusk | Property applies at specific time |
192
+ | `wielder_race` | string | Property applies only to specific race |
193
+ | `wielder_class` | string | Property applies only to specific class |
194
+ | `while_equipped` | boolean | Always true when equipped (default) |
195
+ | `on_hit` | boolean | Triggers when weapon hits |
196
+ | `on_damage_taken` | boolean | Triggers when wearer takes damage |
197
+ | `custom` | value + description | Game-defined condition |
198
+
199
+ ### EquipmentProperty
200
+
201
+ **Location:** [src/core/types/Equipment.ts](../src/core/types/Equipment.ts)
202
+
203
+ Defines how equipment affects gameplay.
204
+
205
+ | Property | Type | Description |
206
+ |----------|------|-------------|
207
+ | `type` | `EquipmentPropertyType` | Property type (stat_bonus, skill_proficiency, etc.) |
208
+ | `target` | string | What the property affects (ability, skill, etc.) |
209
+ | `value` | number \| string \| boolean | Effect value |
210
+ | `condition` | `EquipmentCondition` | Optional condition for when property applies |
211
+ | `stackable` | boolean | Whether effects stack (default: true) |
212
+
213
+ For property examples, see [Examples - Property Examples](#examples).
214
+
215
+ ### EquipmentMiniFeature
216
+
217
+ **Location:** [src/core/types/Equipment.ts](../src/core/types/Equipment.ts)
218
+
219
+ Equipment-specific features defined inline. Used in `EnhancedEquipment.grantsFeatures`.
220
+
221
+ | Property | Type | Description |
222
+ |----------|------|-------------|
223
+ | `id` | string | Unique feature ID |
224
+ | `name` | string | Feature name |
225
+ | `description` | string | Feature description |
226
+ | `effects` | `EquipmentProperty[]` | What this feature does |
227
+ | `source` | `'equipment_inline'` | Marks as equipment-specific |
228
+
229
+ For inline feature examples, see [Examples - Inline Mini-Features](#examples).
230
+
231
+ ### Equipment-Granted Features
232
+
233
+ Equipment can grant features in two ways:
234
+ - **Registry Feature References**: String references to features in the FeatureQuery. For examples, see [Examples - Registry Feature References](#examples).
235
+ - **Inline Mini-Features**: Equipment-specific features defined inline (see `EquipmentMiniFeature` table above).
236
+
237
+ ### Equipment-Granted Skills
238
+
239
+ Equipment can grant skill proficiencies or expertise. The `grantsSkills` property on `EnhancedEquipment` accepts an array of `{skillId, level}` objects.
240
+
241
+ **Skill Proficiency Hierarchy:** When equipment grants a skill proficiency:
242
+ - `none` < `proficient` < `expertise`
243
+ - Equipment always upgrades to at least the granted level
244
+ - Expertise overrides any lower level
245
+ - Multiple sources are tracked separately
246
+
247
+ For skill granting examples, see [Example 1: Comprehensive Custom Item](#example-1-comprehensive-custom-item) - Cloak of the Elder demonstrates `grantsSkills` with proficiency and expertise levels.
248
+
249
+ ---
250
+
251
+ ## Equipment Effects
252
+
253
+ Equipment effects are applied when items are equipped and removed when unequipped. The `EquipmentEffectApplier` class handles all effect management.
254
+
255
+ ### Stacking Behavior
256
+
257
+ **All equipment effects stack by default.** Multiple items with the same effect will combine:
258
+ - Two +1 STR items = +2 STR total
259
+ - Two +1 AC items = +2 AC total
260
+ - Stackable can be set to false for non-stacking effects
261
+
262
+ **Armor AC Formulas:** When a `passive_modifier` with `target: 'ac'` has a string value, it is treated as a full AC formula rather than an additive bonus. The formula replaces the character's unarmored AC entirely. Supported patterns:
263
+ - `"16"` — Flat AC (heavy armor, no DEX contribution)
264
+ - `"11 + DEX"` — Base + full DEX modifier (light armor)
265
+ - `"14 + min(DEX, 2)"` — Base + capped DEX modifier (medium armor)
266
+
267
+ Numeric AC values (e.g., `value: 2`) are additive bonuses that stack on top of armor, such as from shields or magic items.
268
+
269
+ ### Effect Application Flow
270
+
271
+ ```
272
+ Equip Item
273
+ |
274
+ v
275
+ EquipmentEffectApplier.equipItem()
276
+ |
277
+ +--> Check stat requirements (e.g., STR 13 for plate armor)
278
+ | |
279
+ | +--> Failed? Return { applied: false } — item not applied
280
+ |
281
+ +--> Apply properties (stat bonuses, AC formulas, skills, etc.)
282
+ +--> Apply granted features
283
+ +--> Apply granted skills
284
+ +--> Apply granted spells
285
+ |
286
+ v
287
+ Store in character.equipment_effects[]
288
+ ```
289
+
290
+ ### Effect Removal Flow
291
+
292
+ ```
293
+ Unequip Item
294
+ |
295
+ v
296
+ EquipmentEffectApplier.unequipItem()
297
+ |
298
+ +--> Remove properties (reverse stat changes, etc.)
299
+ +--> Remove granted features
300
+ +--> Remove granted skills
301
+ +--> Remove granted spells
302
+ |
303
+ v
304
+ Remove from character.equipment_effects[]
305
+ ```
306
+
307
+ ### Character Equipment Effects Structure
308
+
309
+ **Location:** [CharacterSheet.equipment_effects](../src/core/types/Character.ts)
310
+
311
+ Equipment effects are tracked separately on the character for proper removal when unequipping.
312
+
313
+ | Property | Type | Description |
314
+ |----------|------|-------------|
315
+ | source | string | Equipment name |
316
+ | instanceId | string | Per-instance tracking ID |
317
+ | effects | EquipmentProperty[] | Properties from this item |
318
+ | features | EquipmentFeature[] | Features granted by this item |
319
+ | skills | EquipmentSkill[] | Skills granted by this item |
320
+ | spells | Array<{spellId, level?, uses?, recharge?}> | Spells granted by this item |
321
+
322
+ For examples of equipment effects in use, see [Example 2: Enchanting Equipment](#example-2-enchanting-equipment) or [Example 8: Progressive Enchantment Through Gameplay](#example-8-progressive-enchantment-through-gameplay).
323
+
324
+ ---
325
+
326
+ ## Equipment Modification
327
+
328
+ **Location:** [src/core/equipment/EquipmentModifier.ts](../src/core/equipment/EquipmentModifier.ts)
329
+
330
+ The `EquipmentModifier` class handles runtime equipment modifications including enchanting, cursing, and upgrading.
331
+
332
+ ### Modification Types
333
+
334
+ | Type | Description | Source |
335
+ |------|-------------|--------|
336
+ | Enchantment | Adds positive properties | `enchantment` |
337
+ | Curse | Adds negative properties | `curse` |
338
+ | Upgrade | Improves existing properties | `upgrade` |
339
+ | Template | Applies template definition | `template` |
340
+
341
+ ### EquipmentModification Interface
342
+
343
+ **Location:** [src/core/types/Equipment.ts](../src/core/types/Equipment.ts)
344
+
345
+ Runtime modification for enchanting, cursing, or upgrading equipment.
346
+
347
+ | Property | Type | Description |
348
+ |----------|------|-------------|
349
+ | `id` | string | Unique modification ID |
350
+ | `name` | string | Display name |
351
+ | `properties` | `EquipmentProperty[]` | Properties added by modification |
352
+ | `addsFeatures` | Array<string \| EquipmentMiniFeature> | Features granted |
353
+ | `addsSkills` | Array<{skillId, level}> | Skills granted |
354
+ | `addsSpells` | Array<{spellId, level?, uses?, recharge?}> | Spells granted |
355
+ | `source` | string | Source type ('enchantment', 'curse', 'upgrade', 'template') |
356
+
357
+ **For complete method reference**, see [DATA_ENGINE_REFERENCE.md - EquipmentModifier](../DATA_ENGINE_REFERENCE.md#equipmentmodifier) or [API Reference - EquipmentModifier](#api-reference).
358
+
359
+ ### Templates vs Instances
360
+
361
+ The system supports both template-based and per-instance modifications.
362
+
363
+ **Template-based modifications** are predefined in the ExtensionManager and can be applied to any equipment. Templates define a set of properties that get added to base equipment.
364
+
365
+ **Per-instance modifications** are unique enchantments, curses, or upgrades applied to specific item instances. Each item tracks its own modifications separately.
366
+
367
+ For usage examples, see:
368
+ - [Example 4: Template-Based Items](#example-4-template-based-items) - Template registration and application
369
+ - [Examples - Enchantment Library](#examples) - `enchant`, `curse`
370
+ - [Example 2: Enchanting Equipment](#example-2-enchanting-equipment) - `enchant`
371
+ - [Example 8: Progressive Enchantment](#example-8-progressive-enchantment-through-gameplay) - `enchant`, `removeModification`
372
+ - [Example 9: Removing Debuffs from Cursed Items](#example-9-removing-debuffs-from-cursed-items) - `disenchant`, `liftCurse`, `removeModification`
373
+
374
+ ---
375
+
376
+ ## Spawn Weights
377
+
378
+ Spawn weights control item generation in random loot.
379
+
380
+ ### Weight System
381
+
382
+ - **Weight > 0**: Item can spawn randomly (higher = more common)
383
+ - **Weight = 0**: Item never spawns randomly, but can be used by game logic
384
+ - **Default weight**: 1.0 for most items
385
+
386
+ ### Rarity Levels
387
+
388
+ Equipment rarity controls spawn frequency and typical power level.
389
+
390
+ | Rarity | Spawn Weight (Typical) | Description |
391
+ |--------|----------------------|-------------|
392
+ | common | 1.0 | Standard items, spawn frequently |
393
+ | uncommon | 0.5 | Slightly magical, spawn occasionally |
394
+ | rare | 0.2 | Powerful magic items, spawn rarely |
395
+ | very_rare | 0.1 | Very powerful, spawn very rarely |
396
+ | legendary | 0.0 | Unique artifacts, never spawn randomly (game-only) |
397
+
398
+ **Note:** The spawnWeight property on equipment can override these defaults. Items with `spawnWeight: 0` will never appear in random loot but can still be used by game logic.
399
+
400
+ For spawning examples, see [Examples - Spawn Weights](#examples).
401
+
402
+ ---
403
+
404
+ ## Enchantment Library
405
+
406
+ The Enchantment Library provides a comprehensive collection of predefined enchantments and curses that can be applied to equipment at runtime. All enchantments are `EquipmentModification` objects designed to work with `EquipmentModifier`.
407
+
408
+ **For complete API documentation with all enchantment tables**, see [DATA_ENGINE_REFERENCE.md - Enchantment Library](../DATA_ENGINE_REFERENCE.md#enchantment-library).
409
+
410
+ **Collections:** `WEAPON_ENCHANTMENTS`, `ARMOR_ENCHANTMENTS`, `RESISTANCE_ENCHANTMENTS`, `CURSES`, `ALL_ENCHANTMENTS`
411
+
412
+ **Stat Boost Functions:** `createStrengthEnchantment`, `createDexterityEnchantment`, `createConstitutionEnchantment`, `createIntelligenceEnchantment`, `createWisdomEnchantment`, `createCharismaEnchantment` (each takes `bonus: 1 | 2 | 3 | 4`)
413
+
414
+ **Query Functions:** `getEnchantment`, `getCurse`, `getAllEnchantments`, `getAllCurses`, `getEnchantmentsByType`
415
+
416
+ For usage examples, see [Examples - Enchantment Library](#examples).
417
+
418
+ ---
419
+
420
+ ## Magic Item System
421
+
422
+ The Magic Item Examples library provides 38 pre-built magic items that demonstrate all capabilities of the Advanced Equipment System. These include weapons, armor, wondrous items, cursed items, conditional items, and template-based items. They serve as reference implementations and test fixtures.
423
+
424
+ **For complete API documentation with all item tables**, see [DATA_ENGINE_REFERENCE.md - Magic Items and Equipment Templates](../DATA_ENGINE_REFERENCE.md#magic-items-and-equipment-templates).
425
+
426
+ **Collections:** `MAGIC_ITEMS` (34 items: weapons, armor, stat bonuses, skills, movement, defense, vision, spells, curses, conditional, template-based), `ITEM_CREATION_TEMPLATES` (9 templates)
427
+
428
+ **Query Functions:** `getMagicItem`, `getMagicItemsByType`, `getMagicItemsByRarity`, `getCursedItems`, `getItemsWithProperty`, `applyTemplate`
429
+
430
+ For usage examples, see [Examples - Magic Item Examples](#examples).
431
+
432
+ ---
433
+
434
+ ## Custom Equipment
435
+
436
+ Custom equipment is registered through the ExtensionManager. For examples, see [Examples - Custom Equipment](#examples).
437
+
438
+ ### Validation
439
+
440
+ All custom equipment is automatically validated using `EquipmentValidator.validateEquipment()`. See [API Reference - EquipmentValidator](#api-reference) for validation methods.
441
+
442
+ ---
443
+
444
+ ## Box Equipment Type
445
+
446
+ The `'box'` equipment type represents containers that hold other items. Boxes support both guaranteed containers (backpacks, adventure packs) and probability-based loot boxes. Boxes are stored in the character's `items[]` inventory and are not automatically opened.
447
+
448
+ ### Design Principles
449
+
450
+ - **Unopened by Default**: Boxes are added to inventory unopened; your game code decides when to open them
451
+ - **Deterministic**: Uses `SeededRNG` so the same seed always produces the same items
452
+ - **Nested Support**: Opening a box that contains another box adds the inner box to inventory unopened
453
+ - **Graceful Failure**: Items referenced in pools but not found in the registry are silently skipped with a warning
454
+
455
+ ### Box Interfaces
456
+
457
+ **Location:** [src/core/types/Equipment.ts](../src/core/types/Equipment.ts)
458
+
459
+ #### BoxDropPool
460
+
461
+ A single entry in a drop pool — either an item or a gold reward.
462
+
463
+ | Property | Type | Required | Description |
464
+ |----------|------|----------|-------------|
465
+ | `weight` | number | Yes | Probability weight (higher = more likely; pool weights should sum to 100) |
466
+ | `itemName` | string | No | Item name to spawn (must exist in equipment registry) |
467
+ | `quantity` | number | No | How many copies to add (default: 1); e.g., `quantity: 10` for 10 torches |
468
+ | `gold` | number | No | Gold to award instead of an item (mutually exclusive with `itemName`) |
469
+
470
+ #### BoxDrop
471
+
472
+ Represents one "draw" from a pool — one item (or gold) selected from the array.
473
+
474
+ | Property | Type | Description |
475
+ |----------|------|-------------|
476
+ | `pool` | `BoxDropPool[]` | Pool of possible outcomes; one entry is selected per drop |
477
+
478
+ #### BoxContents
479
+
480
+ The full configuration for what a box contains.
481
+
482
+ | Property | Type | Required | Description |
483
+ |----------|------|----------|-------------|
484
+ | `drops` | `BoxDrop[]` | Yes | Each entry represents one draw; the box generates one result per drop |
485
+ | `consumeOnOpen` | boolean | No | Whether the box is removed from inventory after opening (default: `true`) |
486
+ | `openRequirements` | `BoxOpenRequirement[]` | No | Optional requirements that must be satisfied to open the box |
487
+
488
+ #### BoxOpenRequirement
489
+
490
+ A single requirement that must be met to open a box. Represents an item (and quantity) that must be consumed from inventory.
491
+
492
+ | Property | Type | Required | Description |
493
+ |----------|------|----------|-------------|
494
+ | `itemName` | string | Yes | Item name that must be consumed (must exist in character inventory) |
495
+ | `quantity` | number | No | Quantity of item required (default: 1) |
496
+
497
+ #### BoxOpenError
498
+
499
+ Error returned when box cannot be opened due to unmet requirements.
500
+
501
+ | Property | Type | Description |
502
+ |----------|------|-------------|
503
+ | `code` | `'MISSING_ITEM' \| 'INSUFFICIENT_QUANTITY' \| 'NO_BOX_CONTENTS'` | Error code for programmatic handling |
504
+ | `message` | string | Human-readable error message |
505
+ | `requirement` | `BoxOpenRequirement` | The requirement that was not met (if applicable) |
506
+
507
+ #### BoxOpenResult
508
+
509
+ The result returned by `BoxOpener.openBox()`.
510
+
511
+ | Property | Type | Description |
512
+ |----------|------|-------------|
513
+ | `success` | boolean | Whether the box was successfully opened |
514
+ | `items` | `Equipment[]` | All items generated from the box (empty if not opened) |
515
+ | `gold` | number | Total gold generated from gold drops |
516
+ | `consumeBox` | boolean | Whether the box should be removed from inventory |
517
+ | `error` | `BoxOpenError` | Error if box could not be opened (optional) |
518
+ | `consumedItems` | `{ name: string; quantity: number }[]` | Items consumed to open the box (optional) |
519
+
520
+ ### BoxOpener Class
521
+
522
+ **Location:** [src/core/equipment/BoxOpener.ts](../src/core/equipment/BoxOpener.ts)
523
+
524
+ Static utility class for opening box-type equipment.
525
+
526
+ | Method | Signature | Description |
527
+ |--------|-----------|-------------|
528
+ | `openBox` | `(box: Equipment, rng: SeededRNG, inventory?: EnhancedInventoryItem[]): BoxOpenResult` | Open a box and generate all its contents; checks requirements if inventory provided |
529
+ | `isBox` | `(equipment: Equipment): boolean` | Check if equipment is a valid (openable) box |
530
+ | `checkRequirements` | `(box: Equipment, inventory: EnhancedInventoryItem[]): BoxOpenError \| null` | Check if box requirements are met; returns null if satisfied |
531
+ | `canOpen` | `(box: Equipment, inventory: EnhancedInventoryItem[]): boolean` | Simple boolean check if box can be opened |
532
+ | `getRequirementsDescription` | `(box: Equipment): string \| null` | Human-readable description of requirements |
533
+ | `previewContents` | `(box: Equipment): { possibleItems, possibleGold, totalDrops, openRequirements? }` | List all possible outcomes without opening |
534
+
535
+ #### openBox
536
+
537
+ Iterates over every `BoxDrop` in `boxContents.drops`, selects one `BoxDropPool` entry using weighted random selection, and returns the aggregated result. If `inventory` is provided and the box has `openRequirements`, requirements are checked before opening.
538
+
539
+ ```typescript
540
+ import { BoxOpener, SeededRNG } from 'playlist-data-engine';
541
+
542
+ const rng = new SeededRNG('my-seed');
543
+ const result = BoxOpener.openBox(explorersPack, rng);
544
+
545
+ console.log(result.success); // boolean — whether the box was opened
546
+ console.log(result.items); // Equipment[] — all generated items
547
+ console.log(result.gold); // number — total gold from gold drops
548
+ console.log(result.consumeBox); // boolean — whether to remove from inventory
549
+ ```
550
+
551
+ #### isBox
552
+
553
+ ```typescript
554
+ if (BoxOpener.isBox(item)) {
555
+ // item.type === 'box' && item.boxContents !== undefined
556
+ }
557
+ ```
558
+
559
+ #### previewContents
560
+
561
+ Returns all *possible* outcomes without consuming RNG state. Useful for tooltips or shop previews. Also returns `openRequirements` if the box has any.
562
+
563
+ ```typescript
564
+ const preview = BoxOpener.previewContents(goblinChest);
565
+ // preview.possibleItems → ['Shortsword', 'Leather Armor', "Thieves' Tools"]
566
+ // preview.possibleGold → { min: 0, max: 50 }
567
+ // preview.totalDrops → 1
568
+ // preview.openRequirements → [{ itemName: 'Iron Key' }] (if present)
569
+ ```
570
+
571
+ #### checkRequirements
572
+
573
+ Check if all box requirements are met by the given inventory. Returns `null` if all requirements are satisfied, or a `BoxOpenError` if not.
574
+
575
+ ```typescript
576
+ const inventory = [{ name: 'Iron Key', quantity: 1, equipped: false }];
577
+ const error = BoxOpener.checkRequirements(lockedChest, inventory);
578
+
579
+ if (error) {
580
+ console.log(error.code); // 'MISSING_ITEM' or 'INSUFFICIENT_QUANTITY'
581
+ console.log(error.message); // Human-readable error
582
+ }
583
+ ```
584
+
585
+ #### canOpen
586
+
587
+ Simple boolean check useful for UI purposes (showing lock icons, enabling/disabling buttons).
588
+
589
+ ```typescript
590
+ const inventory = [{ name: 'Iron Key', quantity: 1, equipped: false }];
591
+
592
+ if (BoxOpener.canOpen(lockedChest, inventory)) {
593
+ console.log('You can open this chest!');
594
+ }
595
+ ```
596
+
597
+ #### getRequirementsDescription
598
+
599
+ Returns a human-readable string describing what items are needed to open the box, useful for tooltips.
600
+
601
+ ```typescript
602
+ const desc = BoxOpener.getRequirementsDescription(lockedChest);
603
+ // "Requires: Iron Key"
604
+
605
+ const multiDesc = BoxOpener.getRequirementsDescription(royalTreasuryBox);
606
+ // "Requires: Golden Key, 200 Gold Coins"
607
+ ```
608
+
609
+ ### Guaranteed Containers
610
+
611
+ Guaranteed containers have pools with exactly one entry (weight 100). Every item listed always drops. Adventure packs like Explorer's Pack are guaranteed containers.
612
+
613
+ ```typescript
614
+ const explorersPack = {
615
+ name: "Explorer's Pack",
616
+ type: 'box',
617
+ rarity: 'common',
618
+ weight: 59,
619
+ icon: '/icons/containers/backpack.png',
620
+ boxContents: {
621
+ drops: [
622
+ { pool: [{ weight: 100, itemName: 'Backpack' }] },
623
+ { pool: [{ weight: 100, itemName: 'Bedroll' }] },
624
+ { pool: [{ weight: 100, itemName: 'Mess Kit' }] },
625
+ { pool: [{ weight: 100, itemName: 'Tinderbox' }] },
626
+ { pool: [{ weight: 100, itemName: 'Waterskin' }] },
627
+ { pool: [{ weight: 100, itemName: 'Rope' }] },
628
+ { pool: [{ weight: 100, itemName: 'Torch', quantity: 10 }] },
629
+ { pool: [{ weight: 100, itemName: 'Rations', quantity: 10 }] },
630
+ ]
631
+ },
632
+ tags: ['gear', 'pack', 'general'],
633
+ description: 'A backpack containing wilderness exploration gear.'
634
+ };
635
+
636
+ // Opens to: Backpack, Bedroll, Mess Kit, Tinderbox, Waterskin, Rope, 10×Torch, 10×Rations
637
+ // Total: 26 items
638
+ ```
639
+
640
+ ### Loot Boxes (Probability-Based)
641
+
642
+ Loot boxes have pools with multiple entries. One entry is chosen per drop based on weights.
643
+
644
+ ```typescript
645
+ const goblinChest = {
646
+ name: 'Goblin Treasure Chest',
647
+ type: 'box',
648
+ rarity: 'uncommon',
649
+ weight: 5,
650
+ icon: '/icons/containers/chest.png',
651
+ boxContents: {
652
+ drops: [{
653
+ pool: [
654
+ { weight: 40, itemName: 'Shortsword' },
655
+ { weight: 30, itemName: 'Leather Armor' },
656
+ { weight: 20, itemName: "Thieves' Tools" },
657
+ { weight: 10, gold: 50 }
658
+ ]
659
+ }]
660
+ },
661
+ tags: ['loot', 'treasure', 'goblin'],
662
+ spawnWeight: 0.3,
663
+ description: 'A small chest containing goblin treasure.'
664
+ };
665
+
666
+ // Opens to one of: Shortsword (40%), Leather Armor (30%), Thieves' Tools (20%), or 50 gold (10%)
667
+ ```
668
+
669
+ ### Mixed Boxes (Guaranteed + Random)
670
+
671
+ Combine guaranteed drops with random pools in the same box.
672
+
673
+ ```typescript
674
+ const dragonHoard = {
675
+ name: 'Dragon Hoard Chest',
676
+ type: 'box',
677
+ rarity: 'rare',
678
+ weight: 10,
679
+ icon: '/icons/containers/treasure-chest.png',
680
+ image: '/images/equipment/dragon-hoard.png',
681
+ boxContents: {
682
+ drops: [
683
+ { pool: [{ weight: 100, gold: 500 }] }, // Always 500 gold
684
+ { pool: [{ weight: 100, itemName: 'Potion of Healing' }] }, // Always a potion
685
+ {
686
+ pool: [ // One random weapon/armor
687
+ { weight: 35, itemName: 'Longsword +1' },
688
+ { weight: 35, itemName: 'Chain Mail +1' },
689
+ { weight: 20, itemName: 'Ring of Protection' },
690
+ { weight: 10, itemName: 'Dragon Slayer Sword' }
691
+ ]
692
+ }
693
+ ]
694
+ },
695
+ tags: ['loot', 'treasure', 'dragon', 'boss'],
696
+ spawnWeight: 0.05,
697
+ description: "A chest from a dragon's hoard."
698
+ };
699
+ ```
700
+
701
+ ### Quantity Parameter
702
+
703
+ Use `quantity` on a pool entry to add multiple copies of the same item in one drop.
704
+
705
+ ```typescript
706
+ const archersBox = {
707
+ name: "Archer's Supply Box",
708
+ type: 'box',
709
+ rarity: 'common',
710
+ weight: 2,
711
+ boxContents: {
712
+ drops: [
713
+ { pool: [{ weight: 100, itemName: 'Arrow', quantity: 20 }] }, // 20 arrows
714
+ { pool: [{ weight: 100, itemName: 'Bowstring', quantity: 3 }] }, // 3 bowstrings
715
+ ]
716
+ },
717
+ description: 'A box of archery supplies.'
718
+ };
719
+ // Opens to: 20×Arrow, 3×Bowstring (23 items total)
720
+ ```
721
+
722
+ ### Nested Box Behavior
723
+
724
+ When a drop resolves to another box, that box is added to inventory **unopened**. The inner box is never recursively opened. This allows treasure caches that contain other containers.
725
+
726
+ ```typescript
727
+ const treasureCache = {
728
+ name: 'Treasure Cache',
729
+ type: 'box',
730
+ rarity: 'uncommon',
731
+ weight: 8,
732
+ icon: '/icons/containers/cache.png',
733
+ boxContents: {
734
+ drops: [
735
+ { pool: [{ weight: 100, itemName: 'Goblin Treasure Chest' }] }, // adds chest unopened
736
+ { pool: [{ weight: 100, gold: 100 }] }
737
+ ]
738
+ },
739
+ description: 'A hidden cache containing a treasure chest and gold.'
740
+ };
741
+ // Opens to: Goblin Treasure Chest (unopened, in items[]) + 100 gold
742
+ ```
743
+
744
+ ### Opening Boxes on Characters
745
+
746
+ Use `EquipmentSpawnHelper.openBoxForCharacter()` to open a box from a character's inventory. It removes the box (if `consumeOnOpen` is true) and adds all contents automatically.
747
+
748
+ ```typescript
749
+ import { EquipmentSpawnHelper, SeededRNG } from 'playlist-data-engine';
750
+
751
+ const rng = new SeededRNG('open-pack-seed');
752
+ const outcome = EquipmentSpawnHelper.openBoxForCharacter(
753
+ character,
754
+ "Explorer's Pack",
755
+ rng
756
+ );
757
+
758
+ if (outcome) {
759
+ character = outcome.character; // Updated character with contents added
760
+ console.log(outcome.result.items.length); // Number of items added
761
+ console.log(outcome.result.gold); // Gold awarded
762
+ }
763
+ ```
764
+
765
+ Returns `null` if the box is not found in the character's inventory.
766
+
767
+ ### Non-Consuming Boxes
768
+
769
+ Set `consumeOnOpen: false` to keep the box in inventory after opening. Useful for containers like a Component Pouch that players open repeatedly.
770
+
771
+ ```typescript
772
+ const componentPouch = {
773
+ name: 'Component Pouch',
774
+ type: 'box',
775
+ rarity: 'common',
776
+ weight: 2,
777
+ icon: '/icons/items/pouch.png',
778
+ boxContents: {
779
+ drops: [], // Empty — no automatic contents
780
+ consumeOnOpen: false // Stays in inventory
781
+ },
782
+ tags: ['gear', 'magic', 'spellcasting'],
783
+ description: 'A pouch for holding spell components.'
784
+ };
785
+ ```
786
+
787
+ ### Opening Requirements
788
+
789
+ Boxes can have optional **opening requirements** that must be satisfied before they can be opened. Requirements are items that get consumed from the character's inventory when the box is successfully opened.
790
+
791
+ #### Basic Item Requirement
792
+
793
+ A locked chest requiring a single key:
794
+
795
+ ```typescript
796
+ const lockedChest = {
797
+ name: 'Locked Chest',
798
+ type: 'box',
799
+ rarity: 'uncommon',
800
+ weight: 10,
801
+ icon: '/icons/containers/locked-chest.png',
802
+ boxContents: {
803
+ openRequirements: [
804
+ { itemName: 'Iron Key' } // Consumes 1 Iron Key when opened
805
+ ],
806
+ drops: [
807
+ { pool: [{ weight: 100, gold: 50 }] },
808
+ { pool: [
809
+ { weight: 50, itemName: 'Shortsword' },
810
+ { weight: 30, itemName: 'Leather Armor' },
811
+ { weight: 20, itemName: 'Medical Supply', quantity: 3 }
812
+ ]}
813
+ ]
814
+ },
815
+ description: 'A sturdy locked chest. Requires an Iron Key to open.'
816
+ };
817
+ ```
818
+
819
+ #### Gold Coin Requirement
820
+
821
+ Gold requirements use `"Gold Coin"` as the item name with a quantity:
822
+
823
+ ```typescript
824
+ const gildedStrongbox = {
825
+ name: 'Gilded Strongbox',
826
+ type: 'box',
827
+ rarity: 'rare',
828
+ weight: 15,
829
+ boxContents: {
830
+ openRequirements: [
831
+ { itemName: 'Gold Coin', quantity: 100 } // Consumes 100 Gold Coins
832
+ ],
833
+ drops: [
834
+ { pool: [{ weight: 100, gold: 250 }] },
835
+ { pool: [
836
+ { weight: 40, itemName: 'Longsword' },
837
+ { weight: 30, itemName: 'Chain Mail' },
838
+ { weight: 20, itemName: 'Scale Mail' }
839
+ ]}
840
+ ]
841
+ },
842
+ description: 'A gilded strongbox with a magical lock. Consumes 100 Gold Coins to unlock.'
843
+ };
844
+ ```
845
+
846
+ #### Quantity-Based Requirement
847
+
848
+ Some boxes require multiple of the same item:
849
+
850
+ ```typescript
851
+ const thievesCache = {
852
+ name: "Thieves' Cache",
853
+ type: 'box',
854
+ rarity: 'uncommon',
855
+ weight: 5,
856
+ boxContents: {
857
+ openRequirements: [
858
+ { itemName: 'Lockpick', quantity: 3 } // Consumes 3 Lockpicks
859
+ ],
860
+ drops: [
861
+ { pool: [{ weight: 100, gold: 75 }] },
862
+ { pool: [
863
+ { weight: 50, itemName: "Thieves' Tools" },
864
+ { weight: 30, itemName: 'Dagger' },
865
+ { weight: 20, itemName: 'Disguise Kit' }
866
+ ]}
867
+ ]
868
+ },
869
+ description: 'A hidden cache with a complex lock. Requires 3 lockpicks to crack.'
870
+ };
871
+ ```
872
+
873
+ #### Multiple Requirements
874
+
875
+ Boxes can require multiple different items simultaneously. ALL requirements must be satisfied:
876
+
877
+ ```typescript
878
+ const royalTreasuryBox = {
879
+ name: 'Royal Treasury Box',
880
+ type: 'box',
881
+ rarity: 'very_rare',
882
+ weight: 20,
883
+ boxContents: {
884
+ openRequirements: [
885
+ { itemName: 'Golden Key' }, // Need 1 Golden Key
886
+ { itemName: 'Gold Coin', quantity: 200 } // AND 200 Gold Coins
887
+ ],
888
+ drops: [
889
+ { pool: [{ weight: 100, gold: 1000 }] },
890
+ { pool: [
891
+ { weight: 30, itemName: 'Plate Armor' },
892
+ { weight: 25, itemName: 'Chain Mail' },
893
+ { weight: 25, itemName: 'Greataxe' },
894
+ { weight: 20, itemName: 'Longsword' }
895
+ ]}
896
+ ]
897
+ },
898
+ description: 'A royal treasury box sealed with powerful magic. Requires a Golden Key and 200 Gold Coins.'
899
+ };
900
+ ```
901
+
902
+ #### Checking Requirements Before Opening
903
+
904
+ Use `BoxOpener.checkRequirements()` to validate if a character can open a box:
905
+
906
+ ```typescript
907
+ import { BoxOpener, SeededRNG } from 'playlist-data-engine';
908
+
909
+ const inventory = [
910
+ { name: 'Iron Key', quantity: 1, equipped: false },
911
+ { name: 'Gold Coin', quantity: 150, equipped: false }
912
+ ];
913
+
914
+ // Check if requirements are met
915
+ const error = BoxOpener.checkRequirements(lockedChest, inventory);
916
+
917
+ if (error) {
918
+ console.log(`Cannot open: ${error.message}`);
919
+ // error.code → 'MISSING_ITEM' or 'INSUFFICIENT_QUANTITY'
920
+ // error.requirement → The specific unmet requirement
921
+ }
922
+ ```
923
+
924
+ #### Opening with Requirements
925
+
926
+ When opening a box with `BoxOpener.openBox()`, provide the inventory to enable requirement checking:
927
+
928
+ ```typescript
929
+ const rng = new SeededRNG('loot-seed');
930
+
931
+ // With inventory - requirements are checked
932
+ const result = BoxOpener.openBox(lockedChest, rng, inventory);
933
+
934
+ if (result.success) {
935
+ console.log('Box opened!');
936
+ console.log('Items received:', result.items);
937
+ console.log('Items consumed:', result.consumedItems);
938
+ // consumedItems → [{ name: 'Iron Key', quantity: 1 }]
939
+ } else {
940
+ console.log('Failed to open:', result.error?.message);
941
+ }
942
+
943
+ // Without inventory - requirements are skipped (backward compatible)
944
+ const legacyResult = BoxOpener.openBox(lockedChest, rng);
945
+ // Always succeeds regardless of requirements
946
+ ```
947
+
948
+ #### Character Integration with EquipmentSpawnHelper
949
+
950
+ When using `EquipmentSpawnHelper.openBoxForCharacter()`, requirements are automatically checked and items consumed from the character's inventory:
951
+
952
+ ```typescript
953
+ import { EquipmentSpawnHelper, SeededRNG } from 'playlist-data-engine';
954
+
955
+ const rng = new SeededRNG('character-loot');
956
+ const outcome = EquipmentSpawnHelper.openBoxForCharacter(character, 'Locked Chest', rng);
957
+
958
+ if (outcome) {
959
+ character = outcome.character; // Updated with consumed requirements and new items
960
+ console.log('Items:', outcome.result.items);
961
+ console.log('Gold:', outcome.result.gold);
962
+ console.log('Consumed:', outcome.result.consumedItems);
963
+ } else {
964
+ console.log('Box not found in inventory');
965
+ }
966
+
967
+ // If requirements not met, outcome.result.success will be false
968
+ // and outcome.result.error will contain the failure reason
969
+ ```
970
+
971
+ #### Error Handling
972
+
973
+ When a box cannot be opened, the result includes an error object:
974
+
975
+ ```typescript
976
+ interface BoxOpenError {
977
+ code: 'MISSING_ITEM' | 'INSUFFICIENT_QUANTITY' | 'NO_BOX_CONTENTS';
978
+ message: string;
979
+ requirement?: BoxOpenRequirement;
980
+ }
981
+
982
+ // Example error handling
983
+ const result = BoxOpener.openBox(box, rng, inventory);
984
+
985
+ if (!result.success && result.error) {
986
+ switch (result.error.code) {
987
+ case 'MISSING_ITEM':
988
+ console.log(`You don't have any ${result.error.requirement?.itemName}`);
989
+ break;
990
+ case 'INSUFFICIENT_QUANTITY':
991
+ console.log(`Not enough ${result.error.requirement?.itemName}`);
992
+ break;
993
+ case 'NO_BOX_CONTENTS':
994
+ console.log('This box is empty');
995
+ break;
996
+ }
997
+ }
998
+ ```
999
+
1000
+ #### UI Helper Methods
1001
+
1002
+ BoxOpener provides convenience methods for UI integration:
1003
+
1004
+ ```typescript
1005
+ // Boolean check for enabling/disabling open button
1006
+ const canOpen = BoxOpener.canOpen(box, character.equipment.items);
1007
+
1008
+ // Human-readable requirement description for tooltips
1009
+ const desc = BoxOpener.getRequirementsDescription(box);
1010
+ // "Requires: Iron Key"
1011
+ // "Requires: Golden Key, 200 Gold Coins"
1012
+ // null (if no requirements)
1013
+
1014
+ // Preview contents with requirements included
1015
+ const preview = BoxOpener.previewContents(box);
1016
+ // preview.openRequirements → [{ itemName: 'Iron Key' }] or undefined
1017
+ ```
1018
+
1019
+ ### Registering Custom Box Items
1020
+
1021
+ Register box items through `ExtensionManager` like any other equipment. Boxes with `spawnWeight > 0` can appear in random loot.
1022
+
1023
+ ```typescript
1024
+ import { ExtensionManager } from 'playlist-data-engine';
1025
+
1026
+ const manager = ExtensionManager.getInstance();
1027
+ manager.register('equipment', [goblinChest, dragonHoard, treasureCache]);
1028
+ ```
1029
+
1030
+ ---
1031
+
1032
+ ## API Reference
1033
+
1034
+ **For complete API documentation with all method signatures**, see [DATA_ENGINE_REFERENCE.md - Equipment System](../DATA_ENGINE_REFERENCE.md#equipment-system).
1035
+
1036
+ ### Quick Class Reference
1037
+
1038
+ | Class | Location | Description |
1039
+ |-------|----------|-------------|
1040
+ | `EquipmentEffectApplier` | [src/core/equipment/EquipmentEffectApplier.ts](../src/core/equipment/EquipmentEffectApplier.ts) | Apply/remove equipment effects when equipping/unequipping |
1041
+ | `EquipmentValidator` | [src/core/equipment/EquipmentValidator.ts](../src/core/equipment/EquipmentValidator.ts) | Validate equipment data structures |
1042
+ | `EquipmentModifier` | [src/core/equipment/EquipmentModifier.ts](../src/core/equipment/EquipmentModifier.ts) | Enchant, curse, upgrade, and modify equipment |
1043
+ | `EquipmentSpawnHelper` | [src/core/equipment/EquipmentSpawnHelper.ts](../src/core/equipment/EquipmentSpawnHelper.ts) | Batch spawn equipment by rarity, tags, or templates |
1044
+ | `EquipmentGenerator` | [src/core/generation/EquipmentGenerator.ts](../src/core/generation/EquipmentGenerator.ts) | Manage inventory and starting gear |
1045
+ | `BoxOpener` | [src/core/equipment/BoxOpener.ts](../src/core/equipment/BoxOpener.ts) | Open box-type items and generate their contents |
1046
+
1047
+ ### EquipmentEffectApplier
1048
+
1049
+ **Location:** [src/core/equipment/EquipmentEffectApplier.ts](../src/core/equipment/EquipmentEffectApplier.ts)
1050
+
1051
+ **For complete method reference**, see [DATA_ENGINE_REFERENCE.md - EquipmentEffectApplier](../DATA_ENGINE_REFERENCE.md#equipmenteffectapplier).
1052
+
1053
+ **Key Methods:**
1054
+ - `equipItem(character, equipment, instanceId?)` - Apply all effects from equipping an item. Returns `EffectApplicationResult` with `applied: boolean` (false if stat requirements not met)
1055
+ - `evaluateACFormula(formula, dexMod)` - Evaluate an armor AC formula string (e.g., `"11 + DEX"`, `"14 + min(DEX, 2)"`, `"16"`)
1056
+ - `unequipItem(character, equipmentName, instanceId?)` - Remove all effects from unequipping an item
1057
+ - `reapplyEquipmentEffects(character)` - Re-apply all equipment effects (for updates/level-ups)
1058
+ - `getActiveEffects(character)` - Get all active equipment effects
1059
+
1060
+ ### EquipmentValidator
1061
+
1062
+ **Location:** [src/core/equipment/EquipmentValidator.ts](../src/core/equipment/EquipmentValidator.ts)
1063
+
1064
+ **For complete method reference**, see [DATA_ENGINE_REFERENCE.md - EquipmentValidator](../DATA_ENGINE_REFERENCE.md#equipmentvalidator).
1065
+
1066
+ **Key Methods:**
1067
+ - `validateEquipment(equipment)` - Validate complete equipment object
1068
+ - `validateProperty(property)` - Validate single equipment property
1069
+ - `validateCondition(condition)` - Validate equipment condition
1070
+ - `validateModification(modification)` - Validate equipment modification
1071
+ - `validateEquipmentFeatureReference(featureId)` - Check if feature ID exists in FeatureQuery
1072
+ - `validateEquipmentSkillReference(skillId)` - Check if skill ID exists in SkillQuery
1073
+
1074
+ ### EquipmentModifier
1075
+
1076
+ **Location:** [src/core/equipment/EquipmentModifier.ts](../src/core/equipment/EquipmentModifier.ts)
1077
+
1078
+ **For complete method reference**, see [DATA_ENGINE_REFERENCE.md - EquipmentModifier](../DATA_ENGINE_REFERENCE.md#equipmentmodifier).
1079
+
1080
+ **Modification Operations:**
1081
+ - `enchant(equipment, itemName, enchantment, character?)` - Enchant equipment with new properties
1082
+ - `curse(equipment, itemName, curse, character?)` - Curse equipment with negative effects
1083
+ - `upgrade(equipment, itemName, upgrade, character?)` - Upgrade equipment (improve properties)
1084
+ - `applyTemplate(equipment, itemName, templateId, character?)` - Apply a template modification
1085
+ - `removeModification(equipment, itemName, modificationId, character?)` - Remove a specific modification
1086
+ - `disenchant(equipment, itemName, character?)` - Remove enchantments, keep curses
1087
+ - `liftCurse(equipment, itemName, character?)` - Remove curses, keep enchantments
1088
+
1089
+ **Query Methods:**
1090
+ - `getCombinedEffects(equipment, itemName, instanceId?)` - Get all active effects (base + mods)
1091
+ - `isEnchanted(equipment, itemName)` - Check if item has any enchantments
1092
+ - `isCursed(equipment, itemName)` - Check if item has any curses
1093
+ - `getItemSummary(equipment, itemName)` - Get comprehensive item summary
1094
+
1095
+ ### EquipmentSpawnHelper
1096
+
1097
+ **Location:** [src/core/equipment/EquipmentSpawnHelper.ts](../src/core/equipment/EquipmentSpawnHelper.ts)
1098
+
1099
+ **For complete method reference**, see [DATA_ENGINE_REFERENCE.md - EquipmentSpawnHelper](../DATA_ENGINE_REFERENCE.md#equipmentspawnhelper).
1100
+
1101
+ **Key Methods:**
1102
+ - `spawnFromList(itemNames, rng?)` - Spawn items from list of names
1103
+ - `spawnByRarity(rarity, count, rng?)` - Spawn items by rarity level
1104
+ - `spawnByTags(tags, count, rng?, options?)` - Spawn items matching tags
1105
+ - `spawnRandom(count, rng, options?)` - Spawn random equipment (respects weights)
1106
+ - `spawnTreasureHoard(cr, rng)` - Spawn treasure hoard by CR
1107
+ - `addToCharacter(character, items, equip?)` - Add spawned items to character
1108
+ - `openBoxForCharacter(character, boxName, rng)` - Open a box, remove from inventory, add contents
1109
+
1110
+ ### BoxOpener
1111
+
1112
+ **Location:** [src/core/equipment/BoxOpener.ts](../src/core/equipment/BoxOpener.ts)
1113
+
1114
+ Static class for opening box-type equipment. See [Box Equipment Type](#box-equipment-type) for full documentation.
1115
+
1116
+ **Key Methods:**
1117
+ - `openBox(box, rng)` - Open a box and return `BoxOpenResult` (items + gold + consumeBox flag)
1118
+ - `isBox(equipment)` - Return `true` if `equipment.type === 'box'` and `boxContents` is set
1119
+ - `previewContents(box)` - Return all possible items and gold range without consuming RNG state
1120
+
1121
+ ### FeatureQuery (Equipment-Related)
1122
+
1123
+ **Location:** [src/core/features/FeatureQuery.ts](../src/core/features/FeatureQuery.ts)
1124
+
1125
+ **For complete method reference**, see [DATA_ENGINE_REFERENCE.md - FeatureQuery](../DATA_ENGINE_REFERENCE.md#featurequery).
1126
+
1127
+ **Key Methods:**
1128
+ - `getEquipmentFeatures(equipmentName)` - Get features grantable by equipment
1129
+ - `isValidEquipmentFeature(featureId)` - Check if feature ID exists (spawnWeight: 0 is valid)
1130
+ - `registerEquipmentFeature(feature)` - Register feature for equipment use (adds 'equipment' tag)
1131
+
1132
+ ---
1133
+
1134
+ ## Examples
1135
+
1136
+ ### Property Examples
1137
+
1138
+ ```typescript
1139
+ // Stat Bonus
1140
+ {
1141
+ type: 'stat_bonus',
1142
+ target: 'STR',
1143
+ value: 2,
1144
+ description: '+2 Strength'
1145
+ }
1146
+
1147
+ // Skill Proficiency
1148
+ {
1149
+ type: 'skill_proficiency',
1150
+ target: 'stealth',
1151
+ value: 'expertise',
1152
+ description: 'Stealth expertise'
1153
+ }
1154
+
1155
+ // Conditional Damage
1156
+ {
1157
+ type: 'damage_bonus',
1158
+ target: 'fire',
1159
+ value: '2d6',
1160
+ description: '+2d6 fire damage',
1161
+ condition: { type: 'vs_creature_type', value: 'troll' }
1162
+ }
1163
+
1164
+ // Passive Modifier (additive — shield, ring of protection, etc.)
1165
+ {
1166
+ type: 'passive_modifier',
1167
+ target: 'ac',
1168
+ value: 2,
1169
+ description: '+2 AC',
1170
+ stackable: true
1171
+ }
1172
+
1173
+ // Passive Modifier (armor AC formula — replaces unarmored AC)
1174
+ // Light armor: full DEX
1175
+ { type: 'passive_modifier', target: 'ac', value: '11 + DEX', description: 'Base AC: 11 + DEX' }
1176
+ // Medium armor: capped DEX
1177
+ { type: 'passive_modifier', target: 'ac', value: '14 + min(DEX, 2)', description: 'Base AC: 14 + DEX (max 2)' }
1178
+ // Heavy armor: no DEX
1179
+ { type: 'passive_modifier', target: 'ac', value: '16', description: 'Fixed AC: 16' }
1180
+ ```
1181
+
1182
+ ### Registry Feature References
1183
+
1184
+ String references to features in the FeatureQuery:
1185
+
1186
+ ```typescript
1187
+ {
1188
+ name: 'Ring of Free Action',
1189
+ type: 'item',
1190
+ rarity: 'rare',
1191
+ weight: 0.1,
1192
+ grantsFeatures: ['freedom_of_movement'],
1193
+ spawnWeight: 0.2,
1194
+ source: 'custom',
1195
+ tags: ['magic', 'ring', 'movement']
1196
+ }
1197
+ ```
1198
+
1199
+ ### Inline Mini-Features
1200
+
1201
+ Equipment-specific features defined inline:
1202
+
1203
+ ```typescript
1204
+ {
1205
+ name: 'Boots of Speed',
1206
+ type: 'item',
1207
+ rarity: 'rare',
1208
+ weight: 1,
1209
+ grantsFeatures: [
1210
+ {
1211
+ id: 'boots_of_speed_haste',
1212
+ name: 'Haste',
1213
+ description: 'While wearing these boots, you can use a bonus action to click them together. On your turn, you can increase your speed by 20 feet until the end of your turn.',
1214
+ effects: [
1215
+ {
1216
+ type: 'passive_modifier',
1217
+ target: 'speed',
1218
+ value: 20,
1219
+ description: '+20 speed'
1220
+ }
1221
+ ],
1222
+ source: 'equipment_inline'
1223
+ }
1224
+ ],
1225
+ spawnWeight: 0.15,
1226
+ source: 'custom',
1227
+ tags: ['magic', 'boots', 'speed']
1228
+ }
1229
+ ```
1230
+
1231
+ ### Templates vs Instances
1232
+
1233
+ The system supports both template-based and per-instance modifications. **Template-based modifications** are predefined in the ExtensionManager and can be applied to any equipment. **Per-instance modifications** are unique enchantments, curses, or upgrades applied to specific item instances. Each item tracks its own modifications separately.
1234
+
1235
+ For template registration and application code examples, see [Example 4: Template-Based Items](#example-4-template-based-items).
1236
+
1237
+ #### Per-Instance Modifications
1238
+
1239
+ Each item can have unique modifications:
1240
+
1241
+ ```typescript
1242
+ // Enchant a specific sword instance
1243
+ const swordInstance = {
1244
+ name: 'Longsword',
1245
+ instanceId: 'sword_12345',
1246
+ modifications: []
1247
+ };
1248
+
1249
+ const enchantment: EquipmentModification = {
1250
+ id: 'enchant_001',
1251
+ name: '+1 Longsword',
1252
+ properties: [
1253
+ { type: 'passive_modifier', target: 'attack_roll', value: 1 }
1254
+ ],
1255
+ appliedAt: new Date().toISOString(),
1256
+ source: 'enchantment'
1257
+ };
1258
+
1259
+ EquipmentModifier.enchant(equipment, 'Longsword', enchantment, character);
1260
+ ```
1261
+
1262
+ #### Combined Effects
1263
+
1264
+ Final effects are the combination of:
1265
+ 1. Base equipment properties
1266
+ 2. Template properties
1267
+ 3. Per-instance modifications
1268
+
1269
+ ```typescript
1270
+ // Get all effects from an item
1271
+ const allEffects = EquipmentModifier.getCombinedEffects(
1272
+ equipment,
1273
+ 'Longsword',
1274
+ 'sword_12345'
1275
+ );
1276
+ ```
1277
+
1278
+ ### Spawning with Weights
1279
+
1280
+ ```typescript
1281
+ // Spawn random items (respects weights)
1282
+ const items = EquipmentSpawnHelper.spawnRandom(
1283
+ 3,
1284
+ rng,
1285
+ { excludeZeroWeight: true }
1286
+ );
1287
+
1288
+ // Spawn by rarity
1289
+ const rareItems = EquipmentSpawnHelper.spawnByRarity('rare', 2, rng);
1290
+ ```
1291
+
1292
+ ### Enchantment Library
1293
+
1294
+ #### Using Predefined Enchantments
1295
+
1296
+ ```typescript
1297
+ import { EquipmentModifier, WEAPON_ENCHANTMENTS, ARMOR_ENCHANTMENTS, RESISTANCE_ENCHANTMENTS } from 'playlist-data-engine';
1298
+
1299
+ // Apply a +1 enhancement to a weapon
1300
+ const plusOne = WEAPON_ENCHANTMENTS.plusOne;
1301
+ character.equipment = EquipmentModifier.enchant(
1302
+ character.equipment,
1303
+ 'Longsword',
1304
+ plusOne,
1305
+ character
1306
+ );
1307
+
1308
+ // Add elemental damage
1309
+ const flaming = WEAPON_ENCHANTMENTS.flaming; // +1d6 fire damage
1310
+ character.equipment = EquipmentModifier.enchant(
1311
+ character.equipment,
1312
+ 'Longsword',
1313
+ flaming,
1314
+ character
1315
+ );
1316
+
1317
+ // Improve armor
1318
+ character.equipment = EquipmentModifier.enchant(
1319
+ character.equipment,
1320
+ 'Plate Armor',
1321
+ ARMOR_ENCHANTMENTS.plusTwo, // +2 AC
1322
+ character
1323
+ );
1324
+
1325
+ // Add resistance
1326
+ character.equipment = EquipmentModifier.enchant(
1327
+ character.equipment,
1328
+ 'Cloak of Protection',
1329
+ RESISTANCE_ENCHANTMENTS.fire, // Fire resistance
1330
+ character
1331
+ );
1332
+ ```
1333
+
1334
+ #### Creating Stat-Boosting Enchantments
1335
+
1336
+ The `create*Enchantment` functions create stat bonuses with configurable levels (1-4):
1337
+
1338
+ ```typescript
1339
+ import {
1340
+ createStrengthEnchantment,
1341
+ createDexterityEnchantment,
1342
+ createConstitutionEnchantment,
1343
+ createIntelligenceEnchantment,
1344
+ createWisdomEnchantment,
1345
+ createCharismaEnchantment
1346
+ } from 'playlist-data-engine';
1347
+
1348
+ // Create +2 Strength belt
1349
+ const beltOfStrength = createStrengthEnchantment(2); // Bonus: 1-4
1350
+ character.equipment = EquipmentModifier.enchant(
1351
+ character.equipment,
1352
+ 'Belt of Giant Strength',
1353
+ beltOfStrength,
1354
+ character
1355
+ );
1356
+
1357
+ // Create +4 Intelligence circlet
1358
+ const circletOfIntellect = createIntelligenceEnchantment(4);
1359
+ character.equipment = EquipmentModifier.enchant(
1360
+ character.equipment,
1361
+ 'Circlet of Intellect',
1362
+ circletOfIntellect,
1363
+ character
1364
+ );
1365
+ ```
1366
+
1367
+ #### Applying Curses
1368
+
1369
+ ```typescript
1370
+ import { EquipmentModifier, CURSES } from 'playlist-data-engine';
1371
+
1372
+ // Apply a cursed item
1373
+ const cursedItem = EquipmentModifier.curse(
1374
+ character.equipment,
1375
+ 'Ring of Weakness',
1376
+ CURSES.weakness, // -4 Strength
1377
+ character
1378
+ );
1379
+
1380
+ // Apply attunement lock (cannot remove without remove curse)
1381
+ const lockedItem = EquipmentModifier.curse(
1382
+ character.equipment,
1383
+ 'Cursed Helmet',
1384
+ CURSES.attunement,
1385
+ character
1386
+ );
1387
+ ```
1388
+
1389
+ #### Combo Enchantments
1390
+
1391
+ Special multi-effect enchantments for powerful items:
1392
+
1393
+ ```typescript
1394
+ import { ALL_ENCHANTMENTS } from 'playlist-data-engine';
1395
+
1396
+ // Holy Avenger: +3 enhancement, radiant damage vs fiends/undead, +5 saves vs spells
1397
+ const holyAvenger = ALL_ENCHANTMENTS.holyAvenger;
1398
+ character.equipment = EquipmentModifier.enchant(
1399
+ character.equipment,
1400
+ 'Holy Avenger',
1401
+ holyAvenger,
1402
+ character
1403
+ );
1404
+
1405
+ // Dragon Slayer: +2 enhancement, extra damage vs dragons, fire resistance
1406
+ const dragonSlayer = ALL_ENCHANTMENTS.dragonSlayer;
1407
+ character.equipment = EquipmentModifier.enchant(
1408
+ character.equipment,
1409
+ 'Dragon Slayer Sword',
1410
+ dragonSlayer,
1411
+ character
1412
+ );
1413
+ ```
1414
+
1415
+ #### Querying Enchantments
1416
+
1417
+ ```typescript
1418
+ import { getEnchantment, getCurse, getAllEnchantments, getAllCurses, getEnchantmentsByType } from 'playlist-data-engine';
1419
+
1420
+ // Get specific enchantment by ID
1421
+ const ench = getEnchantment('flaming');
1422
+ if (ench) {
1423
+ console.log(ench.name); // 'Flaming'
1424
+ }
1425
+
1426
+ // Get all curses
1427
+ const allCurses = getAllCurses();
1428
+ console.log(`Available curses: ${allCurses.length}`); // 17 curses
1429
+
1430
+ // Get enchantments by type
1431
+ const weaponEnchants = getEnchantmentsByType('weapon');
1432
+ console.log(`Weapon enchantments: ${weaponEnchants.length}`); // 16 enchantments
1433
+ ```
1434
+
1435
+ ### Magic Item Examples
1436
+
1437
+ #### Getting Magic Items by Name
1438
+
1439
+ ```typescript
1440
+ import { getMagicItem } from 'playlist-data-engine';
1441
+
1442
+ // Get a specific magic item
1443
+ const vorpalSword = getMagicItem('Vorpal Sword');
1444
+ if (vorpalSword) {
1445
+ console.log(vorpalSword.properties);
1446
+ // Output: Array of equipment properties for this legendary item
1447
+ console.log(vorpalSword.spawnWeight); // 0 (never spawns randomly, game-only)
1448
+ }
1449
+ ```
1450
+
1451
+ #### Querying Magic Items
1452
+
1453
+ ```typescript
1454
+ import {
1455
+ getMagicItemsByType,
1456
+ getMagicItemsByRarity,
1457
+ getCursedItems,
1458
+ getItemsWithProperty
1459
+ } from 'playlist-data-engine';
1460
+
1461
+ // Get all weapons
1462
+ const weapons = getMagicItemsByType('weapon');
1463
+ console.log(`Magic weapons: ${weapons.length}`); // 4 weapons
1464
+
1465
+ // Get all rare items
1466
+ const rareItems = getMagicItemsByRarity('rare');
1467
+ console.log(`Rare items: ${rareItems.length}`); // ~15 rare items
1468
+
1469
+ // Get cursed items
1470
+ const cursedItems = getCursedItems();
1471
+ cursedItems.forEach(item => {
1472
+ console.log(`Cursed: ${item.name}`);
1473
+ // Output: -1 Cursed Sword, Belt of Strength Drain, Helmet of Opposite Alignment
1474
+ });
1475
+
1476
+ // Get all items with a specific property
1477
+ const statBonusItems = getItemsWithProperty('stat_bonus');
1478
+ console.log(`Items with stat bonuses: ${statBonusItems.length}`);
1479
+ ```
1480
+
1481
+ > **Note:** For template application examples, see [Example 4: Template-Based Items](#example-4-template-based-items).
1482
+
1483
+ #### Registering Magic Items with ExtensionManager
1484
+
1485
+ Magic item examples can be registered as custom equipment for procedural generation:
1486
+
1487
+ ```typescript
1488
+ import { ExtensionManager, MAGIC_ITEMS } from 'playlist-data-engine';
1489
+
1490
+ const manager = ExtensionManager.getInstance();
1491
+
1492
+ // Register all magic items as custom equipment
1493
+ manager.register('equipment', MAGIC_ITEMS, {
1494
+ mode: 'append',
1495
+ weights: MAGIC_ITEMS.reduce((acc, item) => {
1496
+ acc[item.name] = item.spawnWeight ?? 0;
1497
+ return acc;
1498
+ }, {} as Record<string, number>)
1499
+ });
1500
+
1501
+ // Now items will appear in random generation (respecting spawnWeight)
1502
+ // Note: Vorpal Sword and other legendary items have spawnWeight: 0,
1503
+ // so they won't appear randomly but can still be spawned by name
1504
+ ```
1505
+
1506
+ #### Direct Access to Magic Item Collections
1507
+
1508
+ ```typescript
1509
+ import { MAGIC_ITEMS, ITEM_CREATION_TEMPLATES, ENCHANTMENT_LIBRARY } from 'playlist-data-engine';
1510
+
1511
+ // Iterate through all magic items
1512
+ MAGIC_ITEMS.forEach(item => {
1513
+ console.log(`${item.name} (${item.rarity}) - ${item.type}`);
1514
+ });
1515
+
1516
+ // Access specific template
1517
+ const viciousTemplate = ITEM_CREATION_TEMPLATES.vicious_weapon_template;
1518
+ console.log(viciousTemplate.properties);
1519
+ ```
1520
+
1521
+ ### Custom Equipment
1522
+
1523
+ Custom equipment is registered through the ExtensionManager or passed directly to `CharacterGenerator.generate()`. All custom equipment is automatically validated using `EquipmentValidator.validateEquipment()`. See [API Reference - EquipmentValidator](#api-reference) for validation methods.
1524
+
1525
+ ### Example 1: Comprehensive Custom Item
1526
+
1527
+ This example shows a single item that combines multiple equipment capabilities—stat bonuses, AC bonuses, and skill proficiencies. You can include any or all of these properties on your custom items.
1528
+
1529
+ ```typescript
1530
+ import { ExtensionManager, CharacterGenerator, EquipmentValidator } from 'playlist-data-engine';
1531
+ import type { EnhancedEquipment } from './src/core/types/Equipment.js';
1532
+
1533
+ // ===== STEP 1: Define a comprehensive custom item =====
1534
+ const cloakOfTheElder: EnhancedEquipment = {
1535
+ name: 'Cloak of the Elder',
1536
+ type: 'item',
1537
+ rarity: 'very_rare',
1538
+ weight: 1,
1539
+ icon: '/icons/items/cloak-elder.png',
1540
+ image: '/images/equipment/cloak-of-the-elder.png',
1541
+
1542
+ // Stat bonuses: increases ability scores
1543
+ properties: [
1544
+ {
1545
+ type: 'stat_bonus',
1546
+ target: 'WIS',
1547
+ value: 2,
1548
+ description: '+2 Wisdom'
1549
+ },
1550
+ {
1551
+ type: 'stat_bonus',
1552
+ target: 'INT',
1553
+ value: 1,
1554
+ description: '+1 Intelligence'
1555
+ },
1556
+ // AC bonus: increases armor class
1557
+ {
1558
+ type: 'passive_modifier',
1559
+ target: 'ac',
1560
+ value: 2,
1561
+ description: '+2 Armor Class',
1562
+ stackable: true
1563
+ },
1564
+ {
1565
+ type: 'passive_modifier',
1566
+ target: 'saving_throws',
1567
+ value: 1,
1568
+ description: '+1 to all saving throws',
1569
+ stackable: true
1570
+ }
1571
+ ],
1572
+
1573
+ // Skill proficiencies: grants skills when equipped
1574
+ grantsSkills: [
1575
+ { skillId: 'arcana', level: 'expertise' },
1576
+ { skillId: 'history', level: 'proficient' },
1577
+ { skillId: 'insight', level: 'proficient' }
1578
+ ],
1579
+
1580
+ // Optional: grant features or spells
1581
+ grantsFeatures: ['darkvision'],
1582
+ grantsSpells: [
1583
+ { spellId: 'detect_magic', level: 1, uses: null } // unlimited uses
1584
+ ],
1585
+
1586
+ spawnWeight: 0.1,
1587
+ source: 'custom',
1588
+ tags: ['magic', 'cloak', 'wisdom', 'intelligence']
1589
+ };
1590
+
1591
+ // ===== STEP 2: Register via ExtensionManager =====
1592
+ const manager = ExtensionManager.getInstance();
1593
+ manager.register('equipment', [cloakOfTheElder], {
1594
+ weights: { 'Cloak of the Elder': 0.1 }
1595
+ });
1596
+
1597
+ // ===== STEP 3: Register via CharacterGenerator (convenience method) =====
1598
+ const character = CharacterGenerator.generate(
1599
+ 'my-seed',
1600
+ audioProfile,
1601
+ track,
1602
+ { extensions: { equipment: [cloakOfTheElder] } }
1603
+ );
1604
+
1605
+ // ===== STEP 4: Adjust spawn rates after registration =====
1606
+ manager.setWeights('equipment', {
1607
+ 'Cloak of the Elder': 0.05, // Make it rarer
1608
+ 'Potion of Healing': 5.0 // Make potions more common
1609
+ });
1610
+
1611
+ // ===== STEP 5: Validate equipment (optional, happens automatically) =====
1612
+ const validation = EquipmentValidator.validateEquipment(cloakOfTheElder);
1613
+ if (!validation.valid) {
1614
+ console.error('Invalid equipment:', validation.errors);
1615
+ }
1616
+ ```
1617
+
1618
+ **Key Points:**
1619
+ - Items can grant **any combination** of stat bonuses, AC bonuses, and skills
1620
+ - `properties` array contains all modifiers (stats, AC, damage, etc.)
1621
+ - `grantsSkills` array uses `{skillId, level}` format (level: `proficient` | `expertise`)
1622
+ - `grantsFeatures` accepts string references to features in the FeatureQuery
1623
+ - `grantsSpells` accepts `{spellId, level?, uses?, recharge?}` for spell-granting items
1624
+
1625
+ ### Example 2: Enchanting Equipment
1626
+
1627
+ ```typescript
1628
+ import { EquipmentModifier } from './src/core/equipment/EquipmentModifier.js';
1629
+
1630
+ // Create enchantment
1631
+ const enchantment = EquipmentModifier.createModification(
1632
+ 'plus_one_001',
1633
+ '+1 Longsword',
1634
+ [{
1635
+ type: 'passive_modifier',
1636
+ target: 'attack_roll',
1637
+ value: 1,
1638
+ description: '+1 to attack rolls'
1639
+ }],
1640
+ 'enchantment'
1641
+ );
1642
+
1643
+ // Apply to equipment
1644
+ const updatedEquipment = EquipmentModifier.enchant(
1645
+ character.equipment,
1646
+ 'Longsword',
1647
+ enchantment,
1648
+ character
1649
+ );
1650
+
1651
+ // Check if enchanted
1652
+ if (EquipmentModifier.isEnchanted(character.equipment, 'Longsword')) {
1653
+ console.log('Item is enchanted!');
1654
+ }
1655
+
1656
+ // Get item summary
1657
+ const summary = EquipmentModifier.getItemSummary(character.equipment, 'Longsword');
1658
+ console.log(summary);
1659
+ // { name: 'Longsword', modifications: [...], isCursed: false, isEnchanted: true }
1660
+
1661
+ // For predefined enchantments (stat boosts, elemental damage, curses), see [Enchantment Library](#enchantment-library)
1662
+ ```
1663
+
1664
+ ### Example 3: Batch Spawning
1665
+
1666
+ ```typescript
1667
+ import { EquipmentSpawnHelper } from './src/core/equipment/EquipmentSpawnHelper.js';
1668
+ import { SeededRNG } from './src/utils/random.js';
1669
+
1670
+ // Spawn treasure hoard
1671
+ const rng = new SeededRNG('dragon_hoard_123');
1672
+ const hoard = EquipmentSpawnHelper.spawnTreasureHoard(15, rng);
1673
+
1674
+ console.log(`Generated ${hoard.items.length} items worth ~${hoard.totalValue} gp`);
1675
+
1676
+ // Add to character
1677
+ character = EquipmentSpawnHelper.addToCharacter(character, hoard.items, false);
1678
+ ```
1679
+
1680
+ ### Example 4: Template-Based Items
1681
+
1682
+ Templates define reusable property sets that can be applied to any equipment. Two application methods:
1683
+
1684
+ ```typescript
1685
+ import { ExtensionManager, EquipmentSpawnHelper, applyTemplate, type EnhancedEquipment } from 'playlist-data-engine';
1686
+
1687
+ // Step 1: Register template (once)
1688
+ ExtensionManager.getInstance().register('equipment.templates', [{
1689
+ id: 'flaming_weapon',
1690
+ name: 'Flaming Weapon',
1691
+ properties: [{ type: 'damage_bonus', target: 'fire', value: '1d6', description: '+1d6 fire' }]
1692
+ }]);
1693
+
1694
+ // Method 1: spawnFromTemplate - looks up item by name + template by ID
1695
+ const sword1 = EquipmentSpawnHelper.spawnFromTemplate('flaming_weapon', 'Longsword');
1696
+
1697
+ // Method 2: applyTemplate - takes equipment object directly, returns modified equipment or null
1698
+ const baseSword: EnhancedEquipment = {
1699
+ name: 'Longsword', type: 'weapon', rarity: 'common', weight: 3,
1700
+ damage: { dice: '1d8', damageType: 'slashing', versatile: '1d10' },
1701
+ weaponProperties: ['finesse', 'versatile'], source: 'base', tags: ['martial', 'melee']
1702
+ };
1703
+ const sword2 = applyTemplate(baseSword, 'flaming_weapon_template');
1704
+ const sword3 = applyTemplate(baseSword, 'plus_one_weapon'); // Template chaining
1705
+ ```
1706
+
1707
+ | Method | Input | Use Case |
1708
+ |--------|-------|----------|
1709
+ | `spawnFromTemplate(templateId, itemName)` | Template ID + item name | Spawning from templates |
1710
+ | `applyTemplate(equipment, templateId)` | Equipment object + template ID | Modifying existing equipment |
1711
+
1712
+ **Note**: For property type reference, see [EquipmentPropertyType](#equipmentpropertytype) in the Equipment Properties section. For equipment-granted features, see [Equipment-Granted Features](#equipment-granted-features).
1713
+
1714
+ ### Example 5: Items That Grant Spells
1715
+
1716
+ ```typescript
1717
+ // ===== Ring of Spell Storing - Store and cast spells =====
1718
+ const ringOfSpellStoring: EnhancedEquipment = {
1719
+ name: 'Ring of Spell Storing',
1720
+ type: 'item',
1721
+ rarity: 'rare',
1722
+ weight: 0.1,
1723
+ properties: [
1724
+ {
1725
+ type: 'special_property',
1726
+ target: 'spell_storing',
1727
+ value: 5,
1728
+ description: 'Can store up to 5 levels of spells'
1729
+ }
1730
+ ],
1731
+ grantsSpells: [
1732
+ { spellId: 'fireball', level: 3, uses: 1, recharge: 'dawn' },
1733
+ { spellId: 'shield', level: 1, uses: 1, recharge: 'dawn' }
1734
+ ],
1735
+ spawnWeight: 0.2,
1736
+ source: 'custom',
1737
+ tags: ['magic', 'ring', 'spell']
1738
+ };
1739
+
1740
+ // ===== Scroll of Fireball - One-time use spell =====
1741
+ const scrollOfFireball: EnhancedEquipment = {
1742
+ name: 'Scroll of Fireball',
1743
+ type: 'item',
1744
+ rarity: 'uncommon',
1745
+ weight: 0.1,
1746
+ grantsSpells: [
1747
+ { spellId: 'fireball', level: 3, uses: 1 }
1748
+ // No recharge means one-time use
1749
+ ],
1750
+ source: 'custom',
1751
+ tags: ['magic', 'scroll', 'consumable', 'fire']
1752
+ };
1753
+
1754
+ // ===== Wand of Magic Missiles - Cast at will (unlimited) =====
1755
+ const wandOfMagicMissiles: EnhancedEquipment = {
1756
+ name: 'Wand of Magic Missiles',
1757
+ type: 'item',
1758
+ rarity: 'uncommon',
1759
+ weight: 1,
1760
+ grantsSpells: [
1761
+ { spellId: 'magic_missile', level: 1, uses: null }
1762
+ // uses: null means unlimited uses
1763
+ ],
1764
+ source: 'custom',
1765
+ tags: ['magic', 'wand', 'evocation']
1766
+ };
1767
+
1768
+ // ===== Understanding grantsSpells Properties =====
1769
+ /*
1770
+ | Property | Type | Description |
1771
+ |----------|------|-------------|
1772
+ | spellId | string | The spell identifier (must exist in spell database) |
1773
+ | level | number | Spell level (0 for cantrips, 1-9 for spell levels) |
1774
+ | uses | number or null | Number of uses, or null for unlimited |
1775
+ | recharge | string | When uses reset: 'dawn', 'short_rest', 'long_rest', or undefined (one-time) |
1776
+
1777
+ Recharge Options:
1778
+ - undefined or omitted - One-time use (consumable like scrolls)
1779
+ - 'dawn' - Uses reset at dawn (daily items like most magic items)
1780
+ - 'short_rest' - Uses reset on short rest (powerful items)
1781
+ - 'long_rest' - Uses reset on long rest (very powerful items)
1782
+ - uses: null - Unlimited uses (cantrips, wands, at-will items)
1783
+ */
1784
+ ```
1785
+
1786
+ ### Example 6: Fire Damage (Two Methods)
1787
+
1788
+ **Method 1: Using Properties**
1789
+
1790
+ ```typescript
1791
+ const flameTongueWeapon: EnhancedEquipment = {
1792
+ name: 'Flame Tongue',
1793
+ type: 'weapon',
1794
+ rarity: 'rare',
1795
+ weight: 3,
1796
+ damage: { dice: '1d8', damageType: 'slashing' },
1797
+ weaponProperties: ['finesse'],
1798
+ properties: [
1799
+ {
1800
+ type: 'damage_bonus',
1801
+ target: 'fire',
1802
+ value: '2d6',
1803
+ description: '+2d6 fire damage on hit'
1804
+ }
1805
+ ],
1806
+ source: 'custom',
1807
+ tags: ['magic', 'fire', 'weapon']
1808
+ };
1809
+ ```
1810
+
1811
+ **Method 2: Using a Feature Reference**
1812
+
1813
+ ```typescript
1814
+ // Reference an existing feature from the registry
1815
+ const flameTongueWithFeature: EnhancedEquipment = {
1816
+ name: 'Flame Tongue',
1817
+ type: 'weapon',
1818
+ rarity: 'rare',
1819
+ weight: 3,
1820
+ damage: { dice: '1d8', damageType: 'slashing' },
1821
+ weaponProperties: ['finesse'],
1822
+ grantsFeatures: ['flame_weapon'],
1823
+ source: 'custom',
1824
+ tags: ['magic', 'fire', 'weapon']
1825
+ };
1826
+
1827
+ // Or define an inline mini-feature for this item only
1828
+ const flameTongueInlineFeature: EnhancedEquipment = {
1829
+ name: 'Flame Tongue',
1830
+ type: 'weapon',
1831
+ rarity: 'rare',
1832
+ weight: 3,
1833
+ damage: { dice: '1d8', damageType: 'slashing' },
1834
+ weaponProperties: ['finesse'],
1835
+ grantsFeatures: [
1836
+ {
1837
+ id: 'flame_tongue_fire',
1838
+ name: 'Flame Tongue Fire',
1839
+ description: 'This weapon deals extra fire damage.',
1840
+ source: 'equipment_inline',
1841
+ effects: [
1842
+ {
1843
+ type: 'damage_bonus',
1844
+ target: 'fire',
1845
+ value: '2d6',
1846
+ description: '+2d6 fire damage on hit'
1847
+ },
1848
+ {
1849
+ type: 'ability_unlock',
1850
+ target: 'light',
1851
+ value: 'bright_light_20ft',
1852
+ description: 'Sheds bright light in a 20ft radius'
1853
+ }
1854
+ ]
1855
+ }
1856
+ ],
1857
+ source: 'custom',
1858
+ tags: ['magic', 'fire', 'weapon']
1859
+ };
1860
+ ```
1861
+
1862
+ ### Example 7: Conditional Effects
1863
+
1864
+ ```typescript
1865
+ import type { EnhancedEquipment } from './src/core/types/Equipment.js';
1866
+
1867
+ // ===== VS CREATURE TYPE =====
1868
+ const dragonSlayerAxe: EnhancedEquipment = {
1869
+ name: 'Dragon Slayer Axe',
1870
+ type: 'weapon',
1871
+ rarity: 'very_rare',
1872
+ weight: 5,
1873
+ damage: { dice: '1d12', damageType: 'slashing' },
1874
+ weaponProperties: ['two-handed'],
1875
+ properties: [
1876
+ {
1877
+ type: 'damage_bonus',
1878
+ target: 'dragon',
1879
+ value: '3d6',
1880
+ condition: { type: 'vs_creature_type', value: 'dragon' },
1881
+ description: '+3d6 damage vs dragons'
1882
+ }
1883
+ ],
1884
+ spawnWeight: 0.05,
1885
+ source: 'custom',
1886
+ tags: ['magic', 'weapon', 'dragon', 'slayer']
1887
+ };
1888
+
1889
+ // ===== TIME OF DAY =====
1890
+ const moonBlade: EnhancedEquipment = {
1891
+ name: 'Moon Blade',
1892
+ type: 'weapon',
1893
+ rarity: 'rare',
1894
+ weight: 3,
1895
+ damage: { dice: '1d8', damageType: 'slashing' },
1896
+ properties: [
1897
+ {
1898
+ type: 'damage_bonus',
1899
+ target: 'radiant',
1900
+ value: '2d6',
1901
+ condition: { type: 'at_time_of_day', value: 'night' },
1902
+ description: '+2d6 radiant damage at night'
1903
+ },
1904
+ {
1905
+ type: 'damage_bonus',
1906
+ target: 'radiant',
1907
+ value: '1d6',
1908
+ condition: { type: 'at_time_of_day', value: 'dawn' },
1909
+ description: '+1d6 radiant damage at dawn'
1910
+ }
1911
+ ],
1912
+ spawnWeight: 0.1,
1913
+ source: 'custom',
1914
+ tags: ['magic', 'weapon', 'moon', 'radiant']
1915
+ };
1916
+
1917
+ // ===== WIELDER RACE =====
1918
+ const elvenChain: EnhancedEquipment = {
1919
+ name: 'Elven Chain',
1920
+ type: 'armor',
1921
+ rarity: 'rare',
1922
+ weight: 20,
1923
+ acBonus: 16,
1924
+ properties: [
1925
+ {
1926
+ type: 'special_property',
1927
+ target: 'sleep_immunity',
1928
+ value: true,
1929
+ condition: { type: 'wielder_race', value: 'Elf' },
1930
+ description: 'Immunity to magic that puts you to sleep (Elf only)'
1931
+ },
1932
+ {
1933
+ type: 'passive_modifier',
1934
+ target: 'stealth_disadvantage',
1935
+ value: false,
1936
+ condition: { type: 'wielder_race', value: 'Elf' },
1937
+ description: 'No stealth disadvantage (Elf only)'
1938
+ }
1939
+ ],
1940
+ spawnWeight: 0.1,
1941
+ source: 'custom',
1942
+ tags: ['magic', 'armor', 'elf', 'stealth']
1943
+ };
1944
+
1945
+ // ===== WIELDER CLASS =====
1946
+ const holyAvenger: EnhancedEquipment = {
1947
+ name: 'Holy Avenger',
1948
+ type: 'weapon',
1949
+ rarity: 'legendary',
1950
+ weight: 3,
1951
+ damage: { dice: '1d8', damageType: 'slashing' },
1952
+ weaponProperties: ['versatile'],
1953
+ properties: [
1954
+ {
1955
+ type: 'passive_modifier',
1956
+ target: 'saving_throws',
1957
+ value: 3,
1958
+ condition: { type: 'wielder_class', value: 'Paladin' },
1959
+ description: '+3 to saving throws (Paladin only)'
1960
+ },
1961
+ {
1962
+ type: 'damage_bonus',
1963
+ target: 'radiant',
1964
+ value: '2d6',
1965
+ condition: { type: 'wielder_class', value: 'Paladin' },
1966
+ description: '+2d6 radiant damage vs fiends/undead (Paladin only)'
1967
+ }
1968
+ ],
1969
+ spawnWeight: 0.0, // Legendary - never spawns randomly
1970
+ source: 'custom',
1971
+ tags: ['magic', 'weapon', 'paladin', 'holy', 'legendary']
1972
+ };
1973
+ ```
1974
+
1975
+ ### Example 8: Progressive Enchantment Through Gameplay
1976
+
1977
+ Track equipment upgrades as players progress:
1978
+
1979
+ ```typescript
1980
+ import { EquipmentModifier } from './src/core/equipment/EquipmentModifier.js';
1981
+ import type { EquipmentModification } from './src/core/types/Equipment.js';
1982
+
1983
+ // Game loop: Player earns upgrade points
1984
+ let enchantmentLevel = 0;
1985
+
1986
+ function upgradeWeapon(character: CharacterSheet, weaponName: string) {
1987
+ enchantmentLevel++;
1988
+
1989
+ const modification: EquipmentModification = {
1990
+ id: `upgrade_${Date.now()}`,
1991
+ name: `+${enchantmentLevel} ${weaponName}`,
1992
+ properties: [
1993
+ {
1994
+ type: 'passive_modifier',
1995
+ target: 'attack_roll',
1996
+ value: enchantmentLevel,
1997
+ description: `+${enchantmentLevel} to attack rolls`
1998
+ },
1999
+ {
2000
+ type: 'passive_modifier',
2001
+ target: 'damage_roll',
2002
+ value: enchantmentLevel,
2003
+ description: `+${enchantmentLevel} to damage rolls`
2004
+ }
2005
+ ],
2006
+ appliedAt: new Date().toISOString(),
2007
+ source: 'gameplay'
2008
+ };
2009
+
2010
+ // Remove previous upgrade if exists
2011
+ if (enchantmentLevel > 1) {
2012
+ const oldModId = `upgrade_${Date.now() - 10000}`;
2013
+ EquipmentModifier.removeModification(
2014
+ character.equipment!,
2015
+ weaponName,
2016
+ oldModId,
2017
+ character
2018
+ );
2019
+ }
2020
+
2021
+ // Apply new upgrade
2022
+ character.equipment = EquipmentModifier.enchant(
2023
+ character.equipment!,
2024
+ weaponName,
2025
+ modification,
2026
+ character
2027
+ );
2028
+
2029
+ console.log(`Weapon upgraded to +${enchantmentLevel}!`);
2030
+ }
2031
+
2032
+ // Usage:
2033
+ upgradeWeapon(character, 'Longsword'); // +1 Longsword
2034
+ // ... later in game ...
2035
+ upgradeWeapon(character, 'Longsword'); // +2 Longsword
2036
+ // ... even later ...
2037
+ upgradeWeapon(character, 'Longsword'); // +3 Longsword
2038
+ ```
2039
+
2040
+ ### Example 9: Removing Debuffs from Cursed Items
2041
+
2042
+ ```typescript
2043
+ import { EquipmentModifier } from './src/core/equipment/EquipmentModifier.js';
2044
+
2045
+ // ===== DISenCHANT (Remove beneficial enchantments, keep curses) =====
2046
+ const result = EquipmentModifier.disenchant(
2047
+ character.equipment!,
2048
+ 'Cursed Sword of Pain',
2049
+ character
2050
+ );
2051
+ // Removes +1 bonuses but keeps the curse
2052
+
2053
+ // ===== LIFT CURSE (Remove curses, keep enchantments) =====
2054
+ const result = EquipmentModifier.liftCurse(
2055
+ character.equipment!,
2056
+ 'Cursed Sword of Pain',
2057
+ character
2058
+ );
2059
+ // Removes curse effects but keeps the +1 enchantment
2060
+
2061
+ // ===== REMOVE SPECIFIC MODIFICATION =====
2062
+ const result = EquipmentModifier.removeModification(
2063
+ character.equipment!,
2064
+ 'Cursed Sword',
2065
+ 'curse_mod_001', // Modification ID to remove
2066
+ character
2067
+ );
2068
+ // Removes only that specific modification
2069
+ ```
2070
+
2071
+ ### Example 10: Multiple Effects Stacking
2072
+
2073
+ ```typescript
2074
+ import { ExtensionManager } from './src/core/extensions/ExtensionManager.js';
2075
+ import type { EnhancedEquipment } from './src/core/types/Equipment.js';
2076
+
2077
+ // Register two items that both give +1 STR
2078
+ const beltOfStrength1: EnhancedEquipment = {
2079
+ name: 'Belt of Strength I',
2080
+ type: 'item',
2081
+ rarity: 'uncommon',
2082
+ weight: 1,
2083
+ properties: [
2084
+ { type: 'stat_bonus', target: 'STR', value: 1, description: '+1 STR' }
2085
+ ],
2086
+ source: 'custom',
2087
+ tags: ['magic', 'strength']
2088
+ };
2089
+
2090
+ const beltOfStrength2: EnhancedEquipment = {
2091
+ name: 'Belt of Strength II',
2092
+ type: 'item',
2093
+ rarity: 'rare',
2094
+ weight: 1,
2095
+ properties: [
2096
+ { type: 'stat_bonus', target: 'STR', value: 1, description: '+1 STR' }
2097
+ ],
2098
+ source: 'custom',
2099
+ tags: ['magic', 'strength']
2100
+ };
2101
+
2102
+ // If a character equips BOTH items, they get +2 STR total
2103
+ // stackable: true is the default behavior
2104
+ ```
2105
+
2106
+ ### Example 11: Game-Only Items (spawnWeight: 0)
2107
+
2108
+ Items that never spawn randomly but are available to game logic:
2109
+
2110
+ ```typescript
2111
+ import type { EnhancedEquipment } from './src/core/types/Equipment.js';
2112
+
2113
+ // This item will NEVER appear in random loot tables
2114
+ const artifactOfDoom: EnhancedEquipment = {
2115
+ name: 'Artifact of Doom',
2116
+ type: 'item',
2117
+ rarity: 'legendary',
2118
+ weight: 5,
2119
+ properties: [
2120
+ { type: 'stat_bonus', target: 'STR', value: 5, description: '+5 STR' },
2121
+ { type: 'special_property', target: 'curse', value: true, description: 'Cursed!' }
2122
+ ],
2123
+ spawnWeight: 0, // NEVER spawns randomly
2124
+ source: 'custom',
2125
+ tags: ['artifact', 'unique', 'cursed', 'quest']
2126
+ };
2127
+
2128
+ // Can ONLY be obtained through specific game logic:
2129
+ function awardArtifact(character: CharacterSheet) {
2130
+ // Directly add to character's inventory
2131
+ character.equipment = character.equipment || {
2132
+ weapons: [], armor: [], items: [], totalWeight: 0, equippedWeight: 0
2133
+ };
2134
+
2135
+ character.equipment.items.push({
2136
+ name: 'Artifact of Doom',
2137
+ quantity: 1,
2138
+ equipped: false
2139
+ });
2140
+
2141
+ // Manually apply the effects
2142
+ EquipmentEffectApplier.equipItem(character, artifactOfDoom);
2143
+ }
2144
+ ```
2145
+
2146
+ ### Example 12: Complete Custom Magic Item System
2147
+
2148
+ ```typescript
2149
+ import {
2150
+ ExtensionManager,
2151
+ EquipmentSpawnHelper,
2152
+ EquipmentModifier,
2153
+ EquipmentEffectApplier
2154
+ } from './src/core/index.js';
2155
+ import type { EnhancedEquipment, EquipmentModification } from './src/core/types/Equipment.js';
2156
+ import { SeededRNG } from './src/utils/random.js';
2157
+
2158
+ // ===== STEP 1: Define Custom Equipment =====
2159
+ const customItems: EnhancedEquipment[] = [
2160
+ {
2161
+ name: 'Frostbrand',
2162
+ type: 'weapon',
2163
+ rarity: 'very_rare',
2164
+ weight: 3,
2165
+ damage: { dice: '1d8', damageType: 'slashing' },
2166
+ weaponProperties: ['finesse'],
2167
+ properties: [
2168
+ { type: 'damage_bonus', target: 'cold', value: '1d8', description: '+1d8 cold damage' },
2169
+ { type: 'ability_unlock', target: 'fire_resistance', value: true, description: 'Fire resistance' }
2170
+ ],
2171
+ grantsFeatures: ['protection_from_fire'],
2172
+ spawnWeight: 0.2,
2173
+ source: 'custom',
2174
+ tags: ['magic', 'ice', 'weapon']
2175
+ },
2176
+ {
2177
+ name: 'Boots of Striding and Springing',
2178
+ type: 'item',
2179
+ rarity: 'uncommon',
2180
+ weight: 1,
2181
+ properties: [
2182
+ { type: 'passive_modifier', target: 'speed', value: 10, description: '+10 speed' },
2183
+ { type: 'ability_unlock', target: 'long_jump', value: true, description: 'Stand up from prone as bonus action' }
2184
+ ],
2185
+ grantsSkills: [{ skillId: 'athletics', level: 'proficient' }],
2186
+ spawnWeight: 0.5,
2187
+ source: 'custom',
2188
+ tags: ['magic', 'boots', 'movement']
2189
+ },
2190
+ {
2191
+ name: 'Ring of Spell Storing',
2192
+ type: 'item',
2193
+ rarity: 'rare',
2194
+ weight: 0.1,
2195
+ grantsSpells: [
2196
+ { spellId: 'fireball', level: 3, uses: 1, recharge: 'dawn' },
2197
+ { spellId: 'shield', level: 1, uses: 1, recharge: 'dawn' }
2198
+ ],
2199
+ spawnWeight: 0.3,
2200
+ source: 'custom',
2201
+ tags: ['magic', 'ring', 'spell']
2202
+ }
2203
+ ];
2204
+
2205
+ // ===== STEP 2: Register Equipment =====
2206
+ const manager = ExtensionManager.getInstance();
2207
+ manager.register('equipment', customItems, {
2208
+ mode: 'relative',
2209
+ validate: true
2210
+ });
2211
+
2212
+ // ===== STEP 3: Spawn Custom Items =====
2213
+ const rng = new SeededRNG('custom_loot');
2214
+ const customLoot = EquipmentSpawnHelper.spawnByTags(['custom', 'magic'], 3, rng);
2215
+
2216
+ // ===== STEP 4: Add to Character =====
2217
+ const character = CharacterGenerator.generate(seed, audio, track);
2218
+ character = EquipmentSpawnHelper.addToCharacter(character, customLoot, true);
2219
+
2220
+ // ===== STEP 5: Enchant Items During Gameplay =====
2221
+ function enchantItem(character: CharacterSheet, itemName: string, enchantmentLevel: number) {
2222
+ const enchantment: EquipmentModification = {
2223
+ id: `enchant_${itemName}_${Date.now()}`,
2224
+ name: `+${enchantmentLevel} Enhancement`,
2225
+ properties: [
2226
+ {
2227
+ type: 'passive_modifier',
2228
+ target: 'attack_roll',
2229
+ value: enchantmentLevel,
2230
+ description: `+${enchantmentLevel} to attack rolls`
2231
+ }
2232
+ ],
2233
+ appliedAt: new Date().toISOString(),
2234
+ source: 'enchanting'
2235
+ };
2236
+
2237
+ character.equipment = EquipmentModifier.enchant(
2238
+ character.equipment!,
2239
+ itemName,
2240
+ enchantment,
2241
+ character
2242
+ );
2243
+
2244
+ console.log(`${itemName} is now +${enchantmentLevel}!`);
2245
+ }
2246
+
2247
+ // ===== STEP 6: Quest Rewards =====
2248
+ function awardQuestReward(character: CharacterSheet) {
2249
+ // Spawn a rare item as quest reward
2250
+ const reward = EquipmentSpawnHelper.spawnByRarity('rare', 1, new SeededRNG('quest'));
2251
+ if (reward.length > 0) {
2252
+ character = EquipmentSpawnHelper.addToCharacter(character, reward, false);
2253
+ console.log(`Quest complete! You received: ${reward[0].name}`);
2254
+ }
2255
+ }
2256
+
2257
+ // ===== STEP 7: Boss Drops =====
2258
+ function bossLoot(character: CharacterSheet, bossCR: number) {
2259
+ // Spawn treasure hoard (see Example 3 for full spawnTreasureHoard usage)
2260
+ const hoard = EquipmentSpawnHelper.spawnTreasureHoard(
2261
+ bossCR,
2262
+ new SeededRNG(`boss_${Date.now()}`)
2263
+ );
2264
+ character = EquipmentSpawnHelper.addToCharacter(character, hoard.items, false);
2265
+ console.log(`Boss defeated! Found ${hoard.items.length} items worth ~${hoard.totalValue} gp`);
2266
+ }
2267
+ ```
2268
+
2269
+ ---
2270
+
2271
+ ## Related Documentation
2272
+
2273
+ - [DATA_ENGINE_REFERENCE.md](../DATA_ENGINE_REFERENCE.md) - Complete API reference
2274
+ - [USAGE_IN_OTHER_PROJECTS.md](../USAGE_IN_OTHER_PROJECTS.md) - Usage examples
2275
+ - [EXTENSIBILITY_GUIDE.md](EXTENSIBILITY_GUIDE.md) - Custom content registration and spawn rates
2276
+ - [XP_AND_STATS.md](XP_AND_STATS.md) - Progression, stat increases, and level-ups
2277
+ - [PREREQUISITES.md](PREREQUISITES.md) - Level, ability, and skill requirements
2278
+ - [specs/001-core-engine/SPEC.md](../specs/001-core-engine/SPEC.md) - Core engine specification
2279
+