SkillReq.getSkillReqsForRune builds each rune's list from the RUNE JSON
on first use and caches it: skill_value is the grant level and
skill_granted_skills are the prereqs. The prereq amounts (80, Warrior 50,
Sun Dancer 60) are loaded but not yet read; CharacterSkill keeps its
hard-coded 80 and validForWarrior.
Parity (scripts/p29.py): 47 of 79 DB runes match. Value changes (JSON
wins, Task 2.9 decisions):
- Thief Dagger Mastery, Throwing, Unarmed Combat Mastery: level 15 -> 20
- Sentinel Axe/Great Axe/Hammer/Great Hammer/Sword Mastery: 20 -> 15;
gains Great Sword Mastery (15, Sword); loses Wear Armor, Medium
- Channeler loses Sword; Storm Lord loses Stormcalling
- Blood Horn gains Hammer Mastery (20, Hammer)
- No weapon prereq any more on Archer Archery, Blade Weaver Blade Weaving,
Sun Dancer Sun Dancing and the 11 Master of X talents
- No effect: Druid/Prelate Sword and Fury Spear 10 -> 1 (promotion is at
10), discipline/talent level changes (they need level 20), Throwing
prereqs (always granted), Blade Master Sword prereq (granted at 1)
- JSON-only runes (Archon, Duelist, War Correspondent, Pet, mob/guard
races) gain lists; Archon resistances and Duelist Dueling are not
skills and are skipped with a warning
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LoadAllPromotions builds one PromotionClass per promotion CLASS rune (22,
2504-2526 without 2522) and caches it, then runs the DB loader, which skips
cached ids, so only the CSR/admin rows 2901-2911 still come from
static_rune_promotion (decision: DB fallback). The world server calls it in
place of GET_ALL_PROMOTIONS; the login server now preloads promotions too, so
character load there gets the same cached objects. The SQL loader stays until
task 2.11.
Value changes: Templar 2516 manaMod 14 -> 15 (decision: JSON, D2).
description NULL -> rune_dsc blurb (unread). All other fields unchanged;
final parity before the switch (scripts/p22.py): 22 promotion rows, 0
differences in name, token, health, stamina and the 163 allowed rune tokens.
The static_rune_promotion comparator is removed; it would only compare the
JSON with itself now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds PromotionClass(RuneTemplate): health/mana/stamina from rune_health/mana/
stamina, token = SBStringHash(rune_sub_type), description from rune_dsc, and
allowedRunes = the sub type tokens of the race and base class runes named in
item_race_req/item_class_req (all whitelists). Skills, powers and effects still
load from the DB by rune id (tasks 2.8, 2.9, 2.10). isPromotionRune selects the
non-creation CLASS runes that require a base class (skips 2500-2503 and Pet).
A static_rune_promotion comparator checks every field against a JSON-built
PromotionClass; rows 2901+ (TALENT runes in the JSON) report as missing.
Offline parity (scripts/p22.py, 33 rows + 163 promotionrunereq rows vs 22
promotion runes): 0 differences in name, token, health, stamina and allowed
rune tokens. Templar 2516 manaMod is 14 in the DB and 15 in the JSON.
2901-2911 have no promotion rune. description is NULL in the DB and the
rune_dsc blurb in the JSON; nothing reads PromotionClass.getDescription().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>