10 KiB
RuneWaker Champion source follow-up — 2026-10-02
This follow-up checks the RuneWaker Resource/data snapshot against the local SourceCodeVS2019 engine and Lua scripts. It does not use the later CoA interpretation of reused skill IDs. No runtime or source data was changed.
Shield Form (498562): rank slots and toggle path are partly resolved
JobSkillBaseStruct::SaveLvPos is documented as the skill-level storage position in SourceCodeVS2019/Libraries/Union/RoleData/ObjectStruct.h:4147; the player-catalog exporter reads the matching learnmagic.db byte at scripts/runewaker-pipeline/export-player-catalog.mjs:461. In the RuneWaker Champion learn rows, Shield Form 498562 maps to slot 4, Runecraft - Fortify 498563 to slot 5, and Last Line of Defense 498539 to slot 11. Lua 02550.lua reads MagicPowerLv_SP+4, +5, and +11 for those respective skills.
The self-targeted 498562 collection applies 621218 first. That MagicObject has CheckUseLua=Lua_Job10_Skill1_Check; the handler in Resource/luascript/02550.lua calculates the state when 621218 is absent. It converts base Strength using floor(base Strength * (0.1 + Shield Form rank * 0.014)), splits the result into buffs 621216/621217, applies 621222 using the Fortify rank, and applies 621228 when Last Line of Defense rank is positive. When 621218 is already present, the same handler cancels the conversion buffs and 621218, then returns false. MagicProcess.cpp:1517 invokes a component's CheckUseLua, and CheckUseLuaProc removes the target when the handler returns false (:5496), giving the skill a source-backed toggle path.
The 621218 row also stores OnTimeMagic_Magic=498517, an interval of 3 seconds, and timeout child 498753. MagicCollect 498517 stores Lua_Job10_Skill1_State in its LUAScript field. The available server tree serializes these fields but has no execution use site for MagicCollect.LUAScript, OnTimeMagic_Magic, or OnTimeMagic_Time; that recurring refresh path is therefore not established here. The remaining Shield Form collection rows also contain unresolved payloads: 498562 groups 621218, 621288, and a self-skill check for skill 498715 leading to 621426; 498540 references 621229; 498714 conditionally references 621431; 498753 references 621289; and 850048 references 621244. The toggle's complete runtime effect is still not source-complete.
Forge / Chain Drive: attack proc and Rune Pulse bonus are source-backed; Curse is not
MagicObject 621251 carries an on-attack trigger at rate 100 for MagicCollect 498590, lasts 300 seconds, and shares clearFlags=1024 with rank-one Forge aura 621167. The engine dispatches mid-gear on-attack magic after the normal main- or off-hand hit when remaining Damage != 0 (Global_SimpleSchedular.cpp:789, 813-843); the 100 rate therefore makes the 498590 trigger certain at that point, not the child effect. MagicCollect 498590 is self-targeted and uses Normal group processing. The rank-two 498604 is the level-22 upgrade rank of Forge 498523 with Chain Drive 498535 as prerequisite; it is not a separately learned base ability.
The groups also carry rank-two behavior outside the conditional branch: group 0 applies 621168's Forge stack on each qualifying trigger, and group 2 applies 621169's Rage restoration after group 1. Preserve that order so a failed Chain Drive roll still keeps the stack and Rage effects.
Its group 1 entries are ordered and must remain separate:
- 80%
Self_Buffcheck for buff 621218, followed by MagicObject 621252. - 30%
Randcheck, followed by MagicObject 621252.
The engine maps condition types to Check_MyBuff and Check_Rand (MagicProcess.cpp:51-60); both use rand() % 100 < chance, and the buff check first requires the exact buff ID (:4905, 5143). It also skips a target already marked IsSpellOneMagic; that marker is reset at the start of each group (:1396) and set after an effect attempt (:2932-55). Thus the second entry is a fallback for targets the first entry did not process, not an independent roll to combine into a derived overall chance. The first branch is specifically conditioned on the source Shield Form buff 621218; no rank or other inferred Shield Form state is involved.
MagicObject 621252 is an eight-second self effect with EndUseLua=Cl_ArtisanSkill_621252. The Magic01_Assist path first resolves AssistMagic, then invokes EndUseLua for each result before classifying misses or no-effect results (MagicProcess.cpp:3876-4123). It is therefore an effect-use/application callback, not a buff-expiry callback; the callback can still run for a no-effect result. The Lua handler resets Rune Pulse 498529, and also 498726 when the owner's sub-job is 3 (Resource/luascript/02545.lua:361-369).
Rune Pulse 498529's pre-use CheckUseScript is cl_ChackBuff_498535 (magiccollectobject.db row 498529), and its catalog cast time is two seconds. MagicProcess::SpellBegin invokes the script before cast scheduling (MagicProcess.cpp:187-210). It checks for 621252 or the separate alternate buff 622185 and, when either is present, adds 621253 for one second; otherwise it still permits the cast. MagicObject 621253 contains modifiers 204=20 and 205=20. The source enum names these AttackPlayerPowerRate and AttackNPCPowerRate (ObjectStruct.h:1646-47); RoleAttr.cpp:1823 applies percentage rate as OrgSpeed * (1 + AddSpeed/100), and both physical and magical damage paths multiply through these outgoing rates (MagicProcess.cpp:3614-16, 3840-42). The source-supported effect is therefore +20% outgoing power against either player or NPC targets while the one-second buff is active. The modifier is role-wide in the engine; Rune Pulse is the cast whose pre-use script grants it, but the DB modifier itself is not restricted to that one spell.
621252 also stores settingFlags=524387, whose bit 19 (524288) is MagicSpellFlagStruct::DisableShowMagicInfo (ObjectStruct.h:1392). The source consumers found for that field only suppress client effect display (rolesprite.cpp:5476, NetCli_MagicChild.cpp:651, 805, 955); they do not change cast timing. Thus the one-second bonus begins at Rune Pulse cast acceptance and expires before its two-second impact. 498726 is reset by the 621252 callback for sub-job 3, but has no matching CheckUseScript, so it does not receive the 621253 bonus from that path.
The exact next-Curse spell and instant-cast transform/consumption remain unidentified. The 621252 callback only resets cooldowns, and the Rune Pulse script only applies 621253. The available Champion collection rows and scripts provide no bound Curse MagicObject or cast-time handler, so that tooltip rider must remain unwired.
Dark Energy Strike (498599): Rune Siphon reset has a sourced 15% post-hit gate
The RuneWaker 498599 collection is single-target enemy, has MagicSpellGroupType_Vampire1, and has these ordered groups in magiccollectobject.db:
- Group 0: MagicObject 621305, physical calculation, damage power -0.7.
- Group 1: MagicObject 621306, Darkness magic calculation, damage power -70 with skill-level argument 1.
- Group 2: Rand 15, then MagicObject 621307.
For Vampire1, the engine enables erase-on-failure only for group 0 and applies the child in group 2 to the owner only if a group-0 target remains non-deleted (MagicProcess.cpp:1426-30, 1489-1513, 1545-63). In group 0, Magic00_AddHP deletes the target for Absorb, NoEffect, Miss, ShieldBlock, Dodge, or zero result value (:2932-55). A nonzero result such as Normal, Critical, or Parry survives; a parry remains eligible because it carries nonzero damage. Group 1 runs only for that surviving target, but its failure does not erase the target because erase-on-failure is off for later groups. Therefore the proc gate is the physical group-0 packet's surviving nonzero result; the later Darkness packet cannot turn that gate into a hit-or-both-packets requirement.
Group 2 then replaces the surviving target list with the owner and runs the 15% Rand check once for this single-target cast. MagicObject 621307 has CheckUseLua=cl_ArtisanSkill_498599; CheckUseLuaProc only calls the handler for a HIT target (MagicProcess.cpp:5496-5512), and Lua 02545.lua:114-118 resets Rune Siphon 498595. This supports a 15% Rune Siphon reset after a group-0 nonzero, non-blocked/non-absorbed result, not a guaranteed reset and not a roll on a miss, full absorb, block, dodge, or zero-value result.
Remaining source gaps and implementation boundary
The Forge attack trigger, unconditional stack/Rage behavior, ordered 80%-conditioned/30%-fallback application, eight-second 621252 effect and cooldown callback, and the one-second role-wide 621253 modifier are implemented for player and companion actors. Rank-one and rank-two Forge use a shared exclusivity group, matching their common source clear flag. The simulator now grants 621253 when the cast is accepted and preserves its one-second expiry; the focused runtime test checks +20% while active and no bonus at the two-second impact. Keep the next-Curse rider unwired until a specific RuneWaker skill and cast-time handler are found. Dark Energy Strike's ordered physical/dark packets and post-physical-result 15% reset gate are source-backed as described above; the separate runtime work is owned by the Dark Energy change.
Focused validation passed: npx vitest run src/game/romChampionForgeRuntime.test.ts src/game/romChampionRuntime.test.ts (25 tests), including ordered roll, failed-roll stack/Rage, positive/zero/absorbed packet, rank upgrade, scoped cooldown reset, and Rune Pulse timing/damage cases. The default-memory npx tsc -b --pretty false check exhausted Node's 4 GB heap before reporting diagnostics; the repository owner is running the final aggregate TypeScript/release validation after concurrent edits freeze.
Shield Form still lacks complete hidden-child and recurring-state execution semantics. Rune Overload still lacks sourced target center, area geometry/radius, cap, eligibility/LOS, and per-tick target behavior as recorded in ROM_CHAMPION_REMAINING_MECHANICS_AUDIT.md; no new dispatch evidence closed that gap in this pass.