Effects, modifiers, source types, effect fail conditions and item
enchant values all come from Effects.cfg (4.6b). Removes
dbEffectsBaseHandler, dbPowerHandler, the EffectsBase and effect
modifier ResultSet constructors, EffectsBase.getFailConditions and
LOAD_ENCHANT_VALUES.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each EffectsBase builds its modifiers from its EffectTemplate MODS
lines, with ramps from the CompoundCurve helper. The Value mods now
fill Item.enchantValues, replacing LOAD_ENCHANT_VALUES. The ItemName
skip list of DB row IDs is gone; it only hid Centurion's and
Imperturbable from ./makeitem.
Value changes (cfg wins): weapon power X effects lose the DB-only OCV
mod; PRE-210A..237A resists and SUF-153A..171A resists (doubled);
Speed on SPRINT, SNT-010A, BHN-001A, HNT/RNG-037A, BRD-016A, PRS-031A,
and RNG-049A loses Speed; ARM-058A Necromancy +1; TMP-040A, WLR-018A;
PARRY/CRU-032A gain their Parry line. RESTORE's damage type is NONE
(cfg typo, as the client reads it).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Effects, their fail conditions and source types come from
EffectTemplate. IDs are assigned at boot in file order. The power
actions that the server also keys as effects (item enchantment parents
and the action tokens sent in effect messages) are built from the
ApplyEffect/ApplyEffects/Invis actions with no Effects.cfg block: 776
keys, the 712 old registry rows plus 64 such as BRD-014A, which live
also sent.
Value changes (cfg wins): BRD-016A/PRS-031A lose the Chant source,
RUC-002A..004A lose Buff/Effect, and PRS-031A/RNG-037A no longer break
on Attack. Effect modifiers still load from the DB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ActionsBase IDs (IgnoreStack effect keys) and EffectsBase IDs are both
written as Integer.toString keys into the same effects map, and their
ranges overlapped. Action lines now start at 10001. Also drops the
unused EffectsBase copy constructor and its NewID counter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both branches of the getUUID() != 334 checks were identical, and they
tied the code to a DB row ID.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client hash (0x5547E0) has no overrides. Hashing the power id
SafeMode must give the real power token for 4.8. Source types are now
keyed by the effect's own token, which keeps the SafeModeA/B override
in EffectsBase.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deletes dbEffectsResourceCostHandler, EffectsResourceCosts and
DbManager.EffectsResourceCostsQueries. GameObjectType.EffectsResourceCosts
stays because the enum ordinals are sent to the client and stored in the DB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PowersManager._effect_costMaps now comes from PowActionCostTemplate
instead of static_effect_costmaps. Parity (scripts/p45.py, dev DB):
524/524 ids, 522 identical. Value changes, cfg wins (D2, user
decision 2026-10-26):
- PRE-092 Yglandor: Sulfur 1 -> 6
- PRE-097 Jagged: adds Sulfur 1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PowersManager.AnimationOverrides was loaded from
static_power_animation_override and never read. validItemFlags on
AbstractPowerAction was always 0 and its getter had no callers; its
loader (static_power_effect_allowed_item) was already commented out.
No value change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SkillsBase comes from Skills.cfg since ff52d1d7 (tested). Drop the
ResultSet ctor; static_skill_skillsbase is shrunk to ID, name
(sql/4.3-skillsbase-shrink.sql, run on mbdb_108 and patched into
magicbane.sql).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SkillsBase name, description, token (SBStringHash of the name) and
attribute weights now come from SkillTemplate._skills. The skill ids
stay in static_skill_skillsbase (ID, name), which dyn_character_skill
and SkillReq use; the loader joins each row's name to the cfg skill and
logs an error for a name with no cfg entry. Both servers load all 63 at
boot after Skills.cfg; GET_BASE/GET_BASE_BY_NAME/GET_BASE_BY_TOKEN read
the boot caches only. The other 31 cfg skills stay in SkillTemplate.
Parity (scripts/p43.py, dev DB): 63/63 names, 63/63 tokens, 63/63
weights. Only value change: description was empty for all 63 in the DB
and is now the cfg text (no server reader).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kits come from StartingKitTable.cfg since c1e1ed91 (tested); drop
dbKitHandler, DbManager.KitQueries, the Kit ResultSet ctor and
RaceClassIDMap.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kit is built from StartingKitTemplate._kits and looked up by race name,
sex and class name like the client's kit map (0x620250), replacing the
hard-coded racebaseclass IDs in GetKitIDByRaceClass. Equip slots come
from each item's item_eq_slots_or, filled from the last cfg item so the
weapon keeps RHELD. dbKitHandler and the ResultSet ctor stay until the
removal commit.
Parity (scripts/p42.py, dev static_rune_validkit vs cfg): 68/68 headers,
451/451 item sets, 449/451 kits with identical slots. Value changes:
- Irekei female Mage kit 3 (182460/185090/29970) was kitNumber 4 in the
DB, so the client's kit 3 was refused at creation; now selectable.
- Dwarf male Fighter kits 4 and 5 list two boots; the DB put Black Cuffed
Boots (185120) in CHEST. They are a FEET item, so they are now skipped
with a warning and Black Riding Boots (185090) stay in FEET.
- Old Half-Giant male (2019) now finds the Half-Giant male kits by name
instead of a missing map entry.
Noob gear item set unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces the hard-coded NON_CREATION_TALENTS exclusions. ATTRRUNES (84)
equals the 92 flagged creation talents minus exactly those 8 runes, so
the talent lists are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FlatLine -> (0, add), SlopedLine p -> (p, add), SlopedInitValLine p ->
(p, no add), checked against every static_power_effectmod and
static_power_action row (6228 rows; the 11 ramp value diffs are the
known 4.0 effectmod diffs). DefaultCurveUp/Down have no ramp form and
no mod or action uses them. The world boot checks five known DB rows
and logs curves without a ramp form.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CompoundCurves (needed by the Powers.cfg parser), Powers, Skills,
StartingKitTable and CharCreateRuneList now load at login boot from the
shared mb.data/wpak. No consumer reads them yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wpak parse now runs ahead of initPowersManager, so the Powers.cfg
target type step runs at the end of InitializePowers instead of as a
separate call after the wpak load. No value change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wall archers are now sent 252622 Wall Guard in place of 252623 Standing
Guard (serialize and getClientRuneIDs). The rune roots its holder (ROOT2,
ROOTFSMID=Power), which matches wall archers never moving (canRoam false).
No server value change: 252622 and 252623 add the same +100 to all five
attributes with the same max, the only fields the server reads from the
sent runes. Wall archers don't cast, so the 252623 power rank steps don't
apply to them. The rune's effects and its MOB-SNARE grant are not applied
by the server, as with the other guard runes.
Client side: 252623 is the only rune that sets the client's is-guard flag
(0x67AB1B), so the CloseToGuard hint no longer fires near wall archers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client ranks a power by adding every POWERSADJ (item_power_action)
step at or below the character's level (0x4B34B0). The Standing Guard
rune's Wizard steps (10@10, 10@20, 10@30, 5@40..70) now set the rank of
every captain spell, capped at the power's MaxLevel as player casts are.
Value change by guard rank 1-7:
before 10/15/20/25/30/35/40
after 10/20/30/35/40/40/40
All 28 captain spells have MaxLevel 40; the uncapped sum would be 50.
Using one curve for the non-Wizard faction spells and the cap are user
decisions (Task 3.9 follow-up 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client sets its is-guard flag only for rune 252623 (0x67AB1B), and
that flag drives the CloseToGuard passive help hint. The server sent
252621 Guard, so the hint never fired. Contract mobs and player guards
now get 252623 in place of 252621.
No server value change: both runes add +100 to all five attributes
with the same max, the only fields the server reads from the sent runes.
The runes differ only in their effects (252621 MOVE-B-15%, OCV-B-20%,
RES-ELEMENTS-B-50; 252623 GUARD), which the server never applied.
The dead 100570/100962/100965 branches keep 252621.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing reads the table since the captain powers moved to MinionType.
Drops LOAD_RUNE_POWERS and the ResultSet RunePowerEntry constructor.
The table drop is MBDATA sql/3.9-rune-powers-drop.sql, run after deploy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 14 Magister Captain contracts' powers have no client rune or deed
(Task 3.9), so they live on the MinionType mage entries instead of the
DB fallback in loadAllRunePowers. canCast, mobCast and aiInfoCmd read
them through MinionType.getCaptainPowers.
No value change: same 56 power ids, rank 40, and all 56 DB tokens equal
the powerbase token of their power id (checked on mbdb_108).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Client ArcCharacter_RecalcMaxAttributes (0x470440) and RecalcAttributes
(0x470610) treat the stats in the mob message as spent points and add
rune_attr_adj from all of the character's runes, clamped to
[1, max(1, sum of rune_max_attr_adj)]. The server summed only the race
rune and sent the result, so the client counted it twice.
Mob.getClientRuneIDs lists the runes serializeForClientMsgOtherPlayer
sends (race, plus Pet 2522 for pets, Warrior 2518 + Guard 252621 for
contract and guard mobs); getRuneAttribute applies the client rule.
calculateModifiedStats and the mob branch of CharacterSkill use it,
and the message now sends 0 (no spent points), as NPCs already do.
MobBaseStats (race rune only) has no users left and is removed.
Value changes (server side):
- Contract mobs and player guards: +100 to all five attributes
(Guard rune 252621; its max adj is also +100, so no new clamp).
- Pets: + Pet rune 2522 (Str 76, Dex 76, Con 120, Int 40, Spi 40;
max Str 500, Dex 500, Con 2000, Int 40, Spi 40).
- The new max clamp changes no loaded mobbase on its own (checked all
2375 against their race runes).
These feed mob skill values (CharacterSkill) and statXxxCurrent.
Guard ATR/defense/damage/health still come from the NPCManager guard
formulas.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mob message paired Dex with the Spirit hash (0xACB82E33), Int with
the Dexterity hash (0xE07B3336) and Spi with the Intelligence hash
(0xFF665EC3); the inline comments were wrong. Verified with
Hasher.SBStringHash. Mob and NPC now use MBServerStatics.STAT_*_ID.
Value change: the client now gets a mob's Dex, Int and Spi in the right
slots. NPCs send 0 for all five, so no change there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client builds a mob's scale from its race rune SCALEFACTOR
(ArcCharacter_BuildCharFromRune 0x458160) and overwrites the three floats
the server sends, so the DB column (always 1) never had a visible effect.
MobBase now holds rune_scale_factor (1,1,1 when absent) and the mob message
sends it. No client-visible change. The column drop is
sql/3.7-mobbase-scale-drop.sql, to run after this build is deployed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every static_npc_mobbase row has ID = loadID since 3.6 dropped the
server-made rows, so the race rune and rune effects are looked up by ID and
the loaders no longer filter on loadID. No value change (dev: 2375 of 2375
rows have ID = loadID). Mob.loadID and NPC.loadID are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LOAD_ALL_MOBBASE_SPEEDS and MobBase.updateSpeeds have had no caller since
3.3 moved mob speeds to rune_speed. No value change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbstractCharacter.getResists fell back to Resists.getResists(0), but the
table has no ID 0, so the fallback always returned null. It now returns
the field directly. Removes dbResistHandler, the cache, the ResultSet
constructor and the empty setMobResists stub. No value change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MobBase builds its stats from the race rune's rune_attr_adj since 3.2, so
LOAD_STATS and the MobBaseStats ResultSet constructor have no callers.
No value change. The table is dropped by sql/3.6-mob-stats-resists-drop.sql.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each line now has the effects map key (effect UUID or stack type such as
PetBuff), the effect id and name, rank, time left or static, and the
power that applied it. Help and usage strings no longer describe
/printstats.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PowersBase.monsterTypeRestrictions is now an EnumSet<MonsterType>
(DB values Siege/Centaur/Dwarf/Vampire read with CacheParserUtil.readEnum).
MobBase gets monsterType from its race rune's rune_sub_type (all 2380
RACE runes convert); players use RaceType.getMonsterType(). No string
compares and no rune lookup per cast. Same results as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Players use their race rune's rune_sub_type, like mobs. Only GOR-003
Crimson Pact (single-target ghoul transformation) restricts player
types: it no longer lands on Centaur 2004/2005, Dwarf 2006 or Vampire
2028/2029, matching its description ("Cannot be cast on a vampire,
centaur, or dwarf"). No player race rune has sub-type Siege, so the
Siege-restricted PC/GROUP powers are unaffected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
static_power_powerbase monsterTypeRestrict1-3 were loaded into
PowersBase.monsterTypeRestrictions but never read. getAllTargets now
drops a Mob whose race rune rune_sub_type is restricted (the rune
sub-type is the client's monster type, g_MonsterTypeEnum at rune +0xC).
86 powers restrict Siege (DB and Powers.cfg agree). Value changes:
- Imbue Golem, Dark Lord's Boon, Strengthen Beast no longer land on the
Siege-type summons Siege Golem 11999 (ANM-004) and Ice Fiend 11998
(SUM-005); they still buff the other pets.
- The 83 Siege-restricted harmful powers (bleeds, DOTs, procs, debuffs,
Challenge, The Lesser Kiss, ...) no longer affect Trebuchet 13171,
Mangonel 13757, Ballista 13758 or the two Siege summons.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
validateTarget tested MASK_MOB before MASK_PET, and summoned pets carry
both, so every single-target PET power (pet heals and buffs such as
Repair Golem, Dark Lord's Favor, Strengthen Beast, Vashteera's Favor)
was rejected on any pet. Pets are now tested first and accept PET or
MOBILE powers, matching the client, where Mobile covers every ArcMobile
and Pet is left to the server (0x49E440). Non-pet mobs are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No power uses it after DRU-066 moved to the Powers.cfg Pet type, and its
area query (WorldGrid.getObjectsInRangePartialNecroPets) was an empty
stub. The DB rows are aligned with sql/necro-pet-targeting.sql.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DRU-066 Braialla's Judgement targets Pet and requires NECROPETSTATS, so
necro pets must be found by the Pet AoE query; the client leaves Pet
targeting to the server (0x49E440 has no Pet case). Reverts the necro
pet exclusion from 40799f96.
Other MASK_PET readers: MobAI pet aggro scan is still off
(PET_AGGRO_ENABLED = false); InterestManager and validateTarget test
MASK_MOB first, so they don't change. DRU-066 is the only AoE PET power.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PowersBase gets setTargetType (the old inline parse, now resetting the
flags first) and the world server re-applies every power's target type
from PowerTemplate after the wpak load. The login server only uses power
tokens and keeps the DB value.
Parity vs static_power_powerbase (1465 powers): 1460 identical, area,
radius type, exclude and target effect prereqs identical for all.
Value changes:
- NEC-018 Sepulchre's Bounty: PET -> MOBILE. Its AoE now collects mobs
and pets; the NECROPETSTATS target prereq keeps only necro pets.
- DRU-066 Braialla's Judgement: NECROPET -> PET.
Kept on the DB (user, behaviour change, for the Phase 4 power task):
HLR-025 Succor PC (cfg CORPSE), HNT-023 Phaedra's Fury PC (cfg SELF),
RNG-033 Fleet of Foot GROUP (cfg SELF).
Client: ArcPower_IsValidTargetForType 0x49E440 accepts any live
ArcMobile for Mobile and has no case for Pet (server decides).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mob effects come from the race rune (2.10b builder) and necro pets get
NECROPETSTATS in code, so GET_RUNEBASE_EFFECTS, the MobBaseEffects
ResultSet constructor, its unused mobBaseID and the equals/hashCode used
for the merge are gone. The table is now unread; drop it with
sql/3.4-mobbase-effects-drop.sql.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No client data grants NECROPETSTATS (no rune, summon power or item; no
reference in sb.exe), but NEC-018 Sepulchre's Bounty and DRU-066
Braialla's Judgement require it on the target, so the server adds it to
Grobones 12021 and Wight 12022 in applyMobbaseEffects.
Rank is the highest rank of the pet's own ladder (PET_NECRO /
PET_NECRO_HARDHIT) at or below its level, which is exactly what the 100
server-made static_npc_mobbase_effects rows held (same 50 rank/reqLvl
pairs). Tying it to that ladder is a guess at the official behaviour.
No value changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
static_npc_mobbase_effects is shrunk to the 100 NECROPETSTATS rows
(sql/3.4-mobbase-effects-shrink.sql); the Wild Boar 13236 PEACE row is
dropped as unused (no spawns, contracts or summon power).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MobBase now takes its effects from MobBaseEffects.getEffectsForRune (the
2.10b builder) and adds the DB rows the rune doesn't have on top.
Parity (scripts/p34.py, 2375 mobbases): 1028 identical, 1344 empty on
both sides, 3 with server-only DB rows that stay in the DB:
- 12021 Grobones, 12022 Wight: 50 NECROPETSTATS rows each
- 13236 Wild Boar: PEACE rank 40, reqLvl 1
No value changes. Effect order does not matter: applyMobbaseEffects keeps
the highest eligible rank per effect.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MobBase walk/walkCombat/run/runCombat now come from rune_speed
WALK/COMBATWALK/RUN/COMBATRUN instead of static_npc_mobbase_race.
Parity (JSON-Refactor-Plan/scripts/p33.py, compared as Java floats):
2375/2375 loaded mobbases identical, every one has a race row on dev.
No value changes. The 253 unused race rows (no mobbase) and the
fly/swim columns had no reader.
LOAD_ALL_MOBBASE_SPEEDS and static_npc_mobbase_race are now unread;
they go in Task 3.6.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
static_npc_mobbase_stats.baseSkills never matches a static_skill_skillsbase
token on dev (2007496699 on 2208 rows is no known skill hash and is absent
from the client), so applyMobbaseSkill never added a skill and only cost a
skillsbase query per mob spawn. No value changes. Nothing reads the
baseSkills/skillAmount columns now; the table goes in 3.6.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Parity (scripts/p32.py, static_npc_mobbase_stats by loadID vs rune_attr_adj):
2301 of 2375 mobbases identical. 74 have no stats row and went from all-zero
stats to the rune values: CSR 2020/2021, summoner pets 12024-12035 and
13266-13301, clay constructs 12200-12202/12375/12382, Guard 13170 (not spawned
or contracted), necro undead 13339-13348, and 13961, 14167, 14168, 14260-14262,
14285, 14292. On dev 47 obj_mob spawns use these (13343, 14167, 14168, 14285,
14292). Effect: higher rune skill values for these mobs via CharacterSkill.
LOAD_STATS stays until the table is dropped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 406 ID <> loadID mobbases (15000-17266) were junk: no RUNE file, nothing
on dev references them. MobBase now always reads its core fields from the
race rune and logs an error if the rune is missing; the loader skips any
ID <> loadID rows still present. GetClassType only matched those IDs, so it
always returned 2518 and is replaced by the constant.
DB: MBDATA/JSON-Refactor-Plan/sql/3.6-mobbase-cleanup.sql deletes the rows,
the 10 orphan static_npc_mobbase_effects rows (17061/17122/17154) and drops
13 columns no longer read: lootTableID, name, level, health, atr, defense,
minDmg, maxDmg, noaggro, raceType, fsm, notEnemy, enemy.
Value changes: none for the 2375 loaded mobbases.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mobbases whose ID is their own race rune (2375 of 2781) now take name,
level, health, atr, defense, min/max damage, fsm and enemy/notEnemy from
the rune; the 406 server-made mobbases keep their DB row. Server-only
columns (bootySet, flags, seeInvis, spawnTime, goldMod, scale,
attackRange) stay DB.
Parity (MBDATA scripts/p31.py vs magicbane.sql), JSON for all (D2,
user decision 2026-10-23):
- fsm 84: DB None where the rune has no ROOTFSMID; no change
- enemy/notEnemy: no change
- level 95: rune 0 (player races, guards, pets, golems, necro pets)
- level 8: 13314 53->42, 13581 53->40, 13758 78->40, 14034 54->40,
14172 54->40, 14174 55->40, 14177 54->40, 13171 10->1
- health/atr/def/dmg 61: DB defaults 100/10/20 -> 0 (tombstones
2030-2040, pets 12024-12035 and 13266-13301, CSR 2020/2021 atr/def/dmg)
- defense 27: DB 100 -> rune 140-780 (clay, risen/doom, skeletal lines,
12011, 13961, 14167, 14168, 14285, 14292); damage/atr there and on
14037/14087/14296/14303 already came from the rune at runtime
- name 33: DB names -> obj_name (e.g. Sharduk -> Hell Hound)
No ParityCheck comparator: p31.py covers it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Client ArcCharacter_ExpForLevelUp (0x4715D0): XP to reach level L is
(L-1)^3 * max(1, 1 + (L-40) * 0.0512f) * class * race rune_exp_req_to_level,
rounded half up. getLevel steps levels with it up to LEVELCAP. Both methods
now take the PlayerCharacter so the factors come from its runes; the CSR
promotions 2901+ have no rune and use the base class factor.
Same values: every player race is 10 and every class rune 15, and the formula
equals the removed table for all 76 levels. Levels above the table no longer
use the old approximate fallback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The DB views char_perAccount and dyn_trash_detail lost their race and class
names when 2.11 dropped the Phase 2 tables. The world server now rewrites the
generated static_rune_lookup table (ID, type, name) after the second
Race.loadAllRaces(): 26 player races, 4 base classes, the promotion runes and
the CSR promotion rows 2901-2911. One transaction (delete, batch insert,
commit); on any failure it rolls back and logs a warning, the boot continues.
Names come from the runes: races use obj_name (as in 2.3), classes the rune
names, so DB typos such as "Theif" become "Thief" in the views. No gameplay
change. Needs JSON-Refactor-Plan/sql/2.12-rune-lookup.sql (table, level
functions, views) run before boot.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>