| name | edition-data-author |
| description | Creates and validates catalog items for edition JSON files including spells, qualities, gear, augmentations, weapons, armor, adept powers, foci, and complex forms. Use when adding new items to core-rulebook.json or sourcebook files, or when validating existing catalog entries. |
| allowed-tools | Read, Grep, Glob |
Edition Data Author
This skill guides the creation of catalog items for Shadowrun edition data files. All items live in /data/editions/{editionCode}/ JSON files.
Key Files
- Type definitions:
/lib/types/edition.ts - SR5 core data:
/data/editions/sr5/core-rulebook.json - Rating spec types:
/lib/types/ratings.ts - Quality types:
/lib/types/qualities.ts
ID Naming Convention
All IDs use kebab-case with lowercase letters:
combat-sense
ares-predator-v
wired-reflexes
muscle-replacement
control-thoughts
For items with ratings, include the base name only (not the rating):
wired-reflexes (NOT wired-reflexes-1, wired-reflexes-2)
For items requiring a selection (attribute, skill, limit):
improved-physical-attribute (with requiresAttribute: true)
improved-ability (with requiresSkill: true)
Common Patterns
Availability Format
availability: number; // Base availability value (0-20+)
legality?: "restricted" | "forbidden"; // R or F suffix in books
Cost Patterns
// Fixed cost
cost: 5000;
// Cost with rating (use ratingSpec)
cost: 1000;
ratingSpec: {
rating: { hasRating: true, maxRating: 6 },
costScaling: { perRating: true }
}
Page References
page?: number; // Page number in source book
source?: string; // "SR5" or book abbreviation
Catalog Item Types
1. Spells
Location: modules.magic.payload.spells.{category}[]
Categories: combat, detection, health, illusion, manipulation
interface SpellCatalogItem {
id: string; // kebab-case
name: string; // Display name
category: "combat" | "detection" | "health" | "illusion" | "manipulation";
type: "mana" | "physical";
range: "touch" | "LOS" | "LOS(A)" | "self";
duration: "instant" | "sustained" | "permanent";
drain: string; // "F-2", "F", "F+1", etc.
damage?: string; // For combat spells: "(F-3)P", "(F)S", etc.
description: string;
page?: number;
source?: string;
}
Example:
{
"id": "fireball",
"name": "Fireball",
"category": "combat",
"type": "physical",
"range": "LOS(A)",
"duration": "instant",
"drain": "F",
"damage": "(F)P",
"description": "Deals Fire damage in an area."
}
2. Qualities
Location: modules.qualities.payload.qualities[]
interface Quality {
id: string;
name: string;
type: "positive" | "negative";
category: "physical" | "mental" | "social" | "magical" | "mundane" | "metatype";
cost: number; // Karma cost (positive for positive qualities)
maxRating?: number; // If quality has levels (1-4 typically)
costPerRating?: boolean;
metatypeRestrictions?: string[]; // ["human", "elf"]
prerequisites?: QualityPrerequisites;
incompatible?: string[]; // IDs of incompatible qualities
description: string;
effects?: QualityEffect[];
source?: SourceReference;
}
Example:
{
"id": "toughness",
"name": "Toughness",
"type": "positive",
"category": "physical",
"cost": 9,
"description": "+1 to Physical damage resistance tests.",
"effects": [
{
"type": "dicePoolModifier",
"target": "damageResistance",
"modifier": 1
}
]
}
3. Cyberware
Location: modules.cyberware.payload.cyberware[]
Categories: headware, eyeware, earware, bodyware, cyberlimbs, nervous-system, biomonitors
interface CyberwareCatalogItem {
id: string;
name: string;
category: CyberwareCategory;
essenceCost: number; // Base essence cost
cost: number; // Base nuyen cost
availability: number;
legality?: "restricted" | "forbidden";
// For rated items, use ratingSpec (preferred)
ratingSpec?: CatalogItemRatingSpec;
// Or legacy properties (deprecated but still work)
hasRating?: boolean;
maxRating?: number;
essencePerRating?: boolean;
costPerRating?: boolean;
// Capacity (for cyberlimbs that hold enhancements)
capacity?: number; // Capacity this provides
capacityCost?: number; // Capacity this consumes (for enhancements)
// Bonuses
attributeBonuses?: Record<string, number>;
initiativeDiceBonus?: number;
// Descriptions
description?: string;
wirelessBonus?: string;
page?: number;
source?: string;
}
Example:
{
"id": "wired-reflexes",
"name": "Wired Reflexes",
"category": "nervous-system",
"essenceCost": 2,
"cost": 39000,
"availability": 8,
"legality": "restricted",
"ratingSpec": {
"rating": { "hasRating": true, "minRating": 1, "maxRating": 3 },
"essenceScaling": { "perRating": true },
"costScaling": { "values": [39000, 149000, 217000] }
},
"initiativeDiceBonus": 1,
"description": "Hardwired reflexes for faster reaction time.",
"wirelessBonus": "+1 Initiative Die while wireless enabled."
}
4. Bioware
Location: modules.bioware.payload.bioware[]
Categories: standard, cultured, cosmetic, biosculpting
interface BiowareCatalogItem {
id: string;
name: string;
category: BiowareCategory;
essenceCost: number;
cost: number;
availability: number;
legality?: "restricted" | "forbidden";
ratingSpec?: CatalogItemRatingSpec;
hasRating?: boolean;
maxRating?: number;
attributeBonuses?: Record<string, number>;
description?: string;
wirelessBonus?: string;
page?: number;
source?: string;
}
5. Weapons
Location: modules.gear.payload.weapons.{subcategory}[]
Subcategories: holdout-pistols, light-pistols, heavy-pistols, machine-pistols, smgs, assault-rifles, sniper-rifles, shotguns, lmgs, hmgs, assault-cannons, blades, clubs, exotic-weapons, throwing-weapons, bows, crossbows
interface WeaponCatalogItem {
id: string;
name: string;
subcategory: string;
damage: string; // "5P", "8P(f)", "(STR+2)P"
accuracy: number;
ap: number; // Armor Penetration (usually negative)
mode?: string; // "SA", "SA/BF", "SA/BF/FA"
rc?: number; // Recoil compensation
ammo?: string; // "12(c)", "30(c)", "belt"
cost: number;
availability: number;
legality?: "restricted" | "forbidden";
reach?: number; // For melee weapons
concealability?: number; // Modifier for concealment
description?: string;
page?: number;
source?: string;
}
Example:
{
"id": "ares-predator-v",
"name": "Ares Predator V",
"subcategory": "heavy-pistols",
"damage": "8P",
"accuracy": 5,
"ap": -1,
"mode": "SA",
"rc": 0,
"ammo": "15(c)",
"cost": 725,
"availability": 5,
"legality": "restricted",
"description": "The iconic sidearm of shadowrunners everywhere."
}
6. Armor
Location: modules.gear.payload.armor[]
interface ArmorCatalogItem {
id: string;
name: string;
armorRating: number;
capacity: number; // For armor modifications
cost: number;
availability: number;
legality?: "restricted" | "forbidden";
encumbrance?: number; // Modifier to physical tests
concealability?: number;
description?: string;
page?: number;
source?: string;
}
7. Weapon Modifications
Location: modules.modifications.payload.weaponMods[]
interface WeaponModificationCatalogItem {
id: string;
name: string;
mount?: "top" | "under" | "side" | "barrel" | "stock" | "internal";
occupiedMounts?: WeaponMountType[]; // Additional mounts used
isBuiltIn?: boolean;
compatibleWeapons?: string[];
incompatibleWeapons?: string[];
minimumWeaponSize?: "holdout" | "light-pistol" | "heavy-pistol" | "smg" | "rifle" | "heavy";
cost: number;
costMultiplier?: number;
availability: number;
legality?: "restricted" | "forbidden";
ratingSpec?: CatalogItemRatingSpec;
hasRating?: boolean;
maxRating?: number;
recoilCompensation?: number;
accuracyModifier?: number;
concealabilityModifier?: number;
description?: string;
wirelessBonus?: string;
page?: number;
source?: string;
}
8. Armor Modifications
Location: modules.modifications.payload.armorMods[]
interface ArmorModificationCatalogItem {
id: string;
name: string;
capacityCost: number;
noCapacityCost?: boolean; // If true, doesn't use capacity (bracketed in book)
ratingSpec?: CatalogItemRatingSpec;
hasRating?: boolean;
maxRating?: number;
capacityPerRating?: boolean;
cost: number;
costPerRating?: boolean;
costMultiplier?: number;
availability: number;
availabilityModifier?: number;
legality?: "restricted" | "forbidden";
armorBonus?: number;
requirements?: string[]; // ["full body armor", "helmet"]
description?: string;
wirelessBonus?: string;
page?: number;
source?: string;
}
9. Adept Powers
Location: modules.adeptPowers.payload.powers[]
interface AdeptPowerCatalogItem {
id: string;
name: string;
cost: number | null; // Power point cost (null if table-based)
costType: "fixed" | "perLevel" | "table";
maxLevel?: number;
activation?: "free" | "simple" | "complex" | "interrupt";
// For powers requiring selection
requiresAttribute?: boolean;
validAttributes?: string[];
requiresSkill?: boolean;
validSkills?: string[];
requiresLimit?: boolean;
validLimits?: string[];
// For table-based costs (like Improved Reflexes)
levels?: Array<{
level: number;
cost: number;
bonus: string;
}>;
// For powers with variants (like Improved Sense)
variants?: Array<{
id: string;
name: string;
bonus?: string;
}>;
description: string;
page?: number;
source?: string;
}
10. Foci
Location: modules.foci.payload.foci[]
interface FocusCatalogItem {
id: string;
name: string;
type: "enchanting" | "metamagic" | "power" | "qi" | "spell" | "spirit" | "weapon";
costMultiplier: number; // Cost = Force × multiplier
bondingKarmaMultiplier: number; // Karma = Force × multiplier
availability: number;
legality?: "restricted" | "forbidden";
description?: string;
page?: number;
source?: string;
}
11. Complex Forms
Location: modules.magic.payload.complexForms[]
interface ComplexFormCatalogItem {
id: string;
name: string;
target: "persona" | "device" | "file" | "sprite" | "host" | "self";
duration: "instant" | "sustained" | "permanent";
fading: string; // "L+1", "L-1", "L", etc.
description: string;
page?: number;
source?: string;
}
Unified Ratings Tables (PREFERRED)
For items with ratings, use unified ratings tables - explicit per-rating values that match how source books print data:
interface UnifiedRatingConfig {
hasRating: true; // Must be true to enable ratings
minRating?: number; // Default: 1
maxRating: number; // Maximum rating allowed
ratings: Record<number, { // Explicit values for each rating
cost?: number; // Nuyen cost at this rating
availability?: number; // Availability at this rating
availabilitySuffix?: "R" | "F";
essenceCost?: number; // Essence cost (cyberware/bioware)
capacity?: number; // Capacity provided (cyberlimbs, cybereyes)
capacityCost?: number; // Capacity consumed (enhancements)
karmaCost?: number; // Karma cost (qualities)
powerPointCost?: number; // Power point cost (adept powers)
effects?: { // Mechanical effects at this rating
attributeBonuses?: Record<string, number>;
initiativeDice?: number;
initiativeScore?: number;
limitBonus?: number;
armorBonus?: number;
};
}>;
}
Example - Cybereyes (different capacity per rating):
{
"id": "cybereyes",
"name": "Cybereyes",
"category": "eyeware",
"hasRating": true,
"minRating": 1,
"maxRating": 4,
"ratings": {
"1": { "cost": 4000, "availability": 3, "essenceCost": 0.2, "capacity": 4 },
"2": { "cost": 6000, "availability": 6, "essenceCost": 0.3, "capacity": 8 },
"3": { "cost": 10000, "availability": 9, "essenceCost": 0.4, "capacity": 12 },
"4": { "cost": 14000, "availability": 12, "essenceCost": 0.5, "capacity": 16 }
},
"description": "Replacement eyes with customizable enhancements."
}
Example - Wired Reflexes (non-linear essence/cost):
{
"id": "wired-reflexes",
"name": "Wired Reflexes",
"category": "nervous-system",
"hasRating": true,
"minRating": 1,
"maxRating": 3,
"ratings": {
"1": {
"cost": 39000, "availability": 8, "essenceCost": 2,
"effects": { "initiativeDice": 1, "initiativeScore": 1 }
},
"2": {
"cost": 149000, "availability": 12, "essenceCost": 3,
"effects": { "initiativeDice": 2, "initiativeScore": 2 }
},
"3": {
"cost": 217000, "availability": 20, "essenceCost": 5,
"effects": { "initiativeDice": 3, "initiativeScore": 3 }
}
},
"legality": "restricted",
"description": "Hardwired reflexes for faster reaction time.",
"wirelessBonus": "+1 Initiative Die while wireless enabled."
}
Example - Quality with Levels (Toughness):
{
"id": "toughness",
"name": "Toughness",
"type": "positive",
"category": "physical",
"hasRating": true,
"minRating": 1,
"maxRating": 4,
"ratings": {
"1": { "karmaCost": 9 },
"2": { "karmaCost": 18 },
"3": { "karmaCost": 27 },
"4": { "karmaCost": 36 }
},
"summary": "+1 to Physical damage resistance tests per level."
}
Why Unified Ratings Tables?
- Matches source books - Books print tables, not formulas
- Non-linear scaling - Many items don't follow simple formulas
- Easier to audit - Direct comparison with source material
- Single code path - No special cases for different scaling types
- Complete data - All rating-dependent values in one place
Rating Spec (LEGACY FALLBACK)
The ratingSpec format is still supported for backward compatibility, but unified ratings tables are preferred for new data:
interface CatalogItemRatingSpec {
rating: {
hasRating: boolean;
minRating?: number; // Default: 1
maxRating: number;
};
essenceScaling?: {
perRating?: boolean;
values?: number[]; // Specific essence costs per rating
};
costScaling?: {
perRating?: boolean;
values?: number[]; // Specific costs per rating
};
capacityCostScaling?: {
perRating?: boolean;
values?: number[];
};
attributeBonusScaling?: Record<string, number>; // Bonus per rating
}
Legacy Example - Muscle Replacement (linear scaling):
{
"ratingSpec": {
"rating": { "hasRating": true, "maxRating": 4 },
"essenceScaling": { "perRating": true },
"costScaling": { "perRating": true },
"attributeBonusScaling": { "strength": 1, "agility": 1 }
}
}
Validation Checklist
When creating new items, verify:
- ID is unique within its category
- ID follows kebab-case convention
- Required fields are present (id, name, category/type-specific fields)
- Availability is reasonable (typically 0-20, rarely higher)
- Costs are positive numbers in appropriate ranges
- Legality is only set when restricted/forbidden (omit for legal items)
- Rating specs are consistent:
- For rated items, prefer unified ratings tables over
ratingSpec - If
hasRating: true, must havemaxRatingandratingstable - Each rating in table should have appropriate cost/availability/essence values
- For rated items, prefer unified ratings tables over
- Page references are accurate (if provided)
- Descriptions are concise but informative
- Unified ratings tables match source book values exactly
Adding to JSON Files
Items are added to arrays within the module payloads:
{
"modules": {
"magic": {
"mergeStrategy": "merge",
"payload": {
"spells": {
"combat": [
{ /* spell 1 */ },
{ /* spell 2 */ }
]
}
}
}
}
}
For sourcebooks, use appropriate merge strategy:
merge- Combine with existing data (default)append- Add to arrays without replacingreplace- Completely override module