vagabond-rpg-foundryvtt/scripts/validate_weapons.py
Cal Corum 49b60954fe Add complete weapons compendium (43 weapons)
- Generate all 43 weapon JSON files with proper data model
- Parse damage dice, grip types, ranges, properties from source
- Convert values to copper (1g = 10s = 100c)
- Map properties: Brawl, Brutal, Cleave, Entangle, Finesse, Keen, Long, Near, Ranged, Shield, Thrown
- Calculate versatile damage (increased die size for 2H grip)
- Assign damage types: blunt/piercing/slashing based on weapon type
- Add validation script for future verification
- All weapons validated against NoteDiscovery source

Weapon categories:
- Melee: 28 (swords, axes, hammers, polearms)
- Ranged: 10 (bows, crossbows, firearms)
- Brawl: 5 (caestus, gauntlet, katar, unarmed)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 23:19:30 -06:00

194 lines
6.3 KiB
Python

#!/usr/bin/env python3
"""Validate generated weapon JSONs against NoteDiscovery source."""
import json
import os
import re
import subprocess
def get_source_content():
"""Fetch weapon source from NoteDiscovery."""
result = subprocess.run(
['python', 'client.py', 'read', 'gaming/vagabond-rpg/gear-weapons.md'],
cwd=os.path.expanduser('~/.claude/skills/notediscovery'),
capture_output=True,
text=True
)
data = json.loads(result.stdout)
return data['content']
def parse_source_weapons(text):
"""Parse source weapon data into a dictionary."""
weapons = {}
# Find the weapons table
lines = text.split('\n')
in_table = False
for line in lines:
if '| Weapon | Damage | Grip |' in line:
in_table = True
continue
if in_table and line.startswith('|---'):
continue
if in_table and line.startswith('|') and line.count('|') >= 7:
parts = [p.strip() for p in line.split('|')[1:-1]]
if len(parts) >= 7 and parts[0] != 'Weapon':
name = parts[0]
damage = parts[1]
grip = parts[2]
range_val = parts[3]
properties = parts[4]
value = parts[5]
slots = parts[6]
weapons[name.lower()] = {
'name': name,
'damage': damage,
'grip': grip,
'range': range_val,
'properties': properties,
'value': value,
'slots': slots
}
elif in_table and not line.startswith('|'):
in_table = False
return weapons
def parse_value_to_copper(value_str):
"""Convert value string to copper."""
if not value_str or value_str == '-':
return 0
total = 0
# Match gold
gold_match = re.search(r'(\d+)g', value_str)
if gold_match:
total += int(gold_match.group(1)) * 100 # 1g = 100c (1g = 10s, 1s = 10c)
# Match silver
silver_match = re.search(r'(\d+)s', value_str)
if silver_match:
total += int(silver_match.group(1)) * 10 # 1s = 10c
return total
def compare_weapons(source, weapons_dir):
"""Compare generated weapons against source."""
discrepancies = []
for filename in sorted(os.listdir(weapons_dir)):
if not filename.endswith('.json'):
continue
filepath = os.path.join(weapons_dir, filename)
with open(filepath, 'r') as f:
generated = json.load(f)
weapon_name = generated['name']
weapon_key = weapon_name.lower()
if weapon_key not in source:
discrepancies.append({
'weapon': weapon_name,
'file': filename,
'issue': f'NOT IN SOURCE - weapon "{weapon_name}" not found in source data'
})
continue
src = source[weapon_key]
gen_system = generated.get('system', {})
issues = []
# Check damage
src_damage = src['damage'].strip()
gen_damage = gen_system.get('damage', '')
# Normalize: "d6" == "1d6", "1" == "1"
src_normalized = src_damage if src_damage.startswith('1') or src_damage == '1' else f"1{src_damage}"
if src_normalized != gen_damage and src_damage != gen_damage:
issues.append(f"DAMAGE: source='{src_damage}' vs generated='{gen_damage}'")
# Check grip
grip_map = {'1H': '1h', '2H': '2h', 'V': 'versatile', 'F': 'fist'}
expected_grip = grip_map.get(src['grip'].strip(), src['grip'].strip().lower())
if expected_grip != gen_system.get('grip', ''):
issues.append(f"GRIP: source='{src['grip']}' ({expected_grip}) vs generated='{gen_system.get('grip', '')}'")
# Check slots
try:
src_slots = int(src['slots'].strip()) if src['slots'].strip() not in ['-', ''] else 0
gen_slots = gen_system.get('slots', 0)
if src_slots != gen_slots:
issues.append(f"SLOTS: source={src_slots} vs generated={gen_slots}")
except ValueError:
pass
# Check value
src_value = parse_value_to_copper(src['value'])
gen_value = gen_system.get('value', 0)
if src_value != gen_value:
issues.append(f"VALUE: source={src_value}c ({src['value']}) vs generated={gen_value}c")
if issues:
discrepancies.append({
'weapon': weapon_name,
'file': filename,
'issues': issues
})
# Check for missing weapons
generated_names = set()
for filename in os.listdir(weapons_dir):
if filename.endswith('.json'):
with open(os.path.join(weapons_dir, filename), 'r') as f:
data = json.load(f)
generated_names.add(data['name'].lower())
for weapon_key in source:
if weapon_key not in generated_names:
discrepancies.append({
'weapon': source[weapon_key]['name'],
'file': 'MISSING',
'issue': 'MISSING - no generated file for this weapon'
})
return discrepancies
def main():
print("Fetching source content from NoteDiscovery...")
source_content = get_source_content()
print("Parsing source weapons...")
source = parse_source_weapons(source_content)
print(f"Found {len(source)} weapons in source\n")
weapons_dir = 'packs/_source/weapons'
weapon_files = [f for f in os.listdir(weapons_dir) if f.endswith('.json')]
print(f"Found {len(weapon_files)} generated weapon files\n")
print("Comparing weapons...")
discrepancies = compare_weapons(source, weapons_dir)
if not discrepancies:
print("\n" + "="*60)
print("NO DISCREPANCIES FOUND")
print(f"All {len(weapon_files)} weapons match source!")
print("="*60)
else:
print("\n" + "="*60)
print(f"FOUND {len(discrepancies)} WEAPON(S) WITH DISCREPANCIES")
print("="*60 + "\n")
for d in discrepancies:
print(f"### {d['weapon']} ({d.get('file', 'N/A')})")
if 'issue' in d:
print(f" - {d['issue']}")
if 'issues' in d:
for issue in d['issues']:
print(f" - {issue}")
print()
if __name__ == '__main__':
main()