Files
HEROLL/HEROLL_RULES_v0.2.8/03-战斗/2026-08-28_逆向-HEROLL战斗规则-report.md

14 KiB
Raw Permalink Blame History

HEROLL 2.0.1 战斗规则与 Double 影响逆向报告

分析日期:2026-08-28
目标:HEROLL : Dice Roguelike 2.0.1(Android / Unity IL2CPP / ARM64)
结论用途:HEROLL 本地玩法验证 MVP
证据标签:NATIVE_CONFIRMED 原生指令闭合;CONFIG_CONFIRMED 源表确认;VIDEO_CONFIRMED 录像确认;COMMUNITY_CROSSCHECK 社区交叉验证;PARTIAL 仍有未闭合分支。

1. 执行摘要

本次从解密后 libil2cpp.runtime-decrypted.so 的真实 ARM64 方法体、IL2CPP 方法地址表、CSV 配置和原作录像中重建了主线战斗核心。可以直接用于 MVP 的闭合结论是:

  1. 玩家基础攻击取最终攻击力;敌人攻击在自身攻击区间上叠加 EnemyDamgeRange=15 的浮动。
  2. 命中、暴击和反击是三次独立判定,不能共用一个随机结果。旧报告把 0x549B934 误当暴击 RNG;该点实际是命中/闪避 RNG。
  3. 暴击率判定为 CR > RandomInt(0,101);基础暴伤为 150%,暴击伤害为 damage * criticalDamagePercent / 100。
  4. 防御是线性减伤:max(0, 防御前伤害 - 最终防御),不是比例减伤。
  5. 攻速换算为 攻击间隔秒 = 100 / 最终攻速;裸装攻速 100 对应 1.0 秒基础间隔。
  6. 吸血为 ceil(本次最终伤害 * 吸血% / 100),只要计算值为正,最低恢复 1 HP。
  7. Double 的基础含义只是“两骰相等”并广播 Double 事件;移动仍为 diceA + diceB,不会自动再×2,也没有无条件伤害倍率。
  8. Double 对战斗的影响来自已获得的遗物、Joker、宠物或武器封印等条件消费者,不是全局基础规则。

2. 目标与范围

  • 授权与边界:work/heroll-stage-2-5/scope.md
  • 主样本:work/heroll-stage-2-5/libil2cpp.runtime-decrypted.so
  • SHA-256:AC357FBA5BF8A68BA02829593020F29DA220EAC8D5ECA91212C1A6744B0100DA
  • 工具:radare2 6.2.0;IDA 启动脚本已实际执行,但本机未配置 IDADIR 且未发现 IDA Pro,因此未伪造 IDA 输出。
  • 网络:本地证据不足以恢复遗物的用户文案时,使用 Heroll Wiki 和玩家实测帖交叉验证;互联网证据不替代原生证据。

3. 可实现的战斗公式

环节 原作规则 / 公式 边界与取整 证据 置信度
裸装初始值 ATK 20,HP 500,AIM 1.5,DEF 10,EVA 1,Counter 10,Attack Speed 100,Crit Rate 10,Life Steal 0 浮点状态 battleplayerdefaultstat.csv 高
最终属性 常规路径为 independentBonus + flatSum * (100 + percentBonus) / 100,再经特殊模式修正 某些调用可请求“仅 flat+independent”或扣除特定层 CharacterStatus.GetFinalStatus @ 0x5409B54 中高
玩家基础伤害 baseDamage = GetFinalStatus(ATTACK_POWER) 此阶段不做随机浮动 BaseActor.GetBaseDamageAndConvertWithPlayerDamage @ 0x5494CD8 高
敌人基础伤害 根据攻击状态上/下界做浮点随机;配置浮动为 ±15% 随机边界由浮点 Range 实现 同上 + EnemyDamgeRange=15 高
命中 hit = hitPercent > RandomInt(0,101) RNG 为 0..100;hitPercent<=roll 失败;负的 hitPercent 先归 0 BaseMonoCharacter.HitAndGetResult @ 0x549B2CC 高(判定)/中(hitPercent 合成)
暴击 critical = finalCritRate > RandomInt(0,101) 0..100,严格大于;10 对应 10/101≈9.901% BaseMonoPlayer.OnBeforeAttack @ 0x55A95EC 高
暴击伤害 damage = damage * GetCriticalDamagePercent() * 0.01 BaseCritical=150,即裸配 1.5×;常数 0x1EB7214=0.01f 0x55A9704..0x55A9728 高
防御减伤 finalDamage = max(0, preDefenseDamage - finalDefense) 伤害保留 float;字体/日志可单独取整 0x549BB14..0x549BD9C 高
HP 扣除 targetHP = targetHP - finalDamage 证据中未见在扣 HP 前对伤害做整数化 0x549BE7C..0x549BE94 高
攻速 interval = 1 / (finalAttackSpeed * 0.01) = 100/finalAttackSpeed 协程再减去已经过的时间;剩余时间≤0 则立即继续 <WaitAttackCooldown>d__45.MoveNext @ 0x5497280 高
吸血 heal = ceil(finalDamage * finalLifeSteal * 0.01) heal>0 时最低 1;调用 CharacterStatus.Heal BaseMonoPlayer.OnAfterAttack @ 0x55A9908, 0x55A9E48..0x55A9EC0 高
反击触发 counter = finalCounterRate >= RandomInt(1,101) RNG 为 1..100,因此 10 就是精确 10% BaseMonoPlayer.OnCounterAttack @ 0x55ABEE0 高
反击伤害 以本次敌方攻击/受到伤害链构建反击 DamageMeta,再走命中、暴击和对方防御 玩家仍承受原伤害;无伤害护盾可不触发;玩家实测表明反击不触发吸血 原生部分闭合 + 社区实测 中高

3.1 概率边界必须照原作实现

原作没有统一使用“0..99 小于百分比”。三种判定的边界不同:

命中: hitPercent  > random(0..100)
暴击: critRate    > random(0..100)
反击: counterRate >= random(1..100)

因此 MVP 若用 Math.random()*100 < rate 同时替代三者,会产生边界差异。特别是暴击率 100 在原作中仍会在 roll=100 时失败;只有大于 100 才能在该路径保证暴击。

3.2 伤害数据流

flowchart LR
  AP["最终攻击力 / 敌人浮动攻击"] --> META["DamageMeta.damage"]
  META --> HIT{"命中判定"}
  HIT -- "miss" --> ZERO["0 伤害"]
  HIT -- "hit" --> CRIT{"暴击判定"}
  CRIT -- "yes" --> CMUL["× 暴伤% / 100"]
  CRIT -- "no" --> HOOK["攻击/受击钩子"]
  CMUL --> HOOK
  HOOK --> DEF["max(0, damage - 最终DEF)"]
  DEF --> HP["HP -= finalDamage"]
  HP --> LS["ceil(finalDamage × 吸血% / 100)"]

注:TOTAL_DAMAGE、TRUE_DAMAGE、装备/遗物钩子会在链中插入修正,但不应在没有闭合每个消费者时压成一条伪精确的总公式。

4. Double 对战斗的真实影响

4.1 Double 基础规则

  1. dicerate.csv 包含 36 个有序 D6 组合,APK 内置权重全为 10。原生代码按 weight/sum(weight) 抽取。在没有远程覆盖时,每个有序结果为 1/36,自然 Double 概率为 6/36=1/6。
  2. isDouble = (diceA == diceB) OR forcedDoubleBranch。原作存在独立的强制 Double 分支,但当前启用内容中 Potion_GuaranteedDouble 为禁用。
  3. moveCount = diceA + diceB。录像中 1+1 显示总点数 2,不是 4。
  4. Double 发生后,第一枚骰子的值传入 BoardController.TryAddDoubleStack(diceA);录像中 1+1 后显示“Double 点数 +1”。这是“累计骰面点数”,不应实现成每次 Double 固定 +1。
  5. EventManager.TriggerOnDiceDouble 会广播事件,各个已装备内容自行订阅;基础战斗公式中没有“Double 直接伤害×2”。

4.2 条件性战斗效果

来源 配置技能 Double 效果 数值 证据等级
Scissors(剪刀)遗物 DoubleAttack Double 时对敌人发动一次基于 ATK 的攻击 100% ATK 配置 + Wiki
Scissors 传奇效果 DoubleAttackEnforce Double 时按骰面点数增加额外攻击次数 次数与 dot count 相关 Wiki;精确次数还需动态验证
Chilled Tome(寒冰书) DoubleFreeze Double 时冻结敌人 1 s 配置 + Wiki
Chilled Tome 传奇效果 DoubleFreezeEnforce 增加 Double 冻结时长 +1 s 配置 + Wiki
Battleship Horn(号角) DoubleAspdUp Double 时攻速提高 +30% 配置 + Wiki;持续时间未静态闭合
Gas Mask(防毒面具) DoubleShield Double 时有概率获得护盾 50% 触发概率 配置 + Wiki;护盾量未闭合
Rejuvenate Tea(恢复茶) DoubleBonusStat Double 时永久提升一项随机基础属性 +10% 配置 + Wiki
Monster Dice(怪物骰子) DoubleExecute Double 后抵达敌人格时,概率处决 1 个敌人 35% 配置 + Wiki
Joker DoubleRollPoints Double 时获得额外点数,可间接与 PointToAttackPower 构筑联动 Normal +2 / Hero +5 / Legend +10 joker.csv + OnDiceDoubleHandler
宠物 BasePet.CheckDoubleDice / Pet_DoubleAttack 部分宠物在 Double 后进入自身攻击链 取决于宠物 元数据方法链,数值未闭合
武器封印负面事件 OnWeaponSealDoubleHandler Double 增加解封计数 取决于事件配置 元数据方法链

4.3 不应加入 MVP 的伪规则

  • 不要把 Double 做成移动点数×2。
  • 不要把 Double 做成无条件伤害×2。
  • 不要把“随机基础属性 +10%”与“下次战斗先攻”做成全局 Double 规则。前者是 Rejuvenate Tea 遗物的稀有效果;当前证据不支持后者是原作基础规则。
  • 不要将 DoubleHeal 名称直接解读为“骰子 Double 回血”。该 Augment 的现有调用链没有证明其订阅 OnDiceDouble;更可能是“治疗事件二次结算概率”,在未闭合前不纳入 Double 基础链。

5. 对现有 GDD / MVP 的覆盖修正

位置 现有内容 覆盖结论
Heroll_Gameplay_Validation_GDD.md Double 段 对子统一触发“机缘”、随机属性 +10%和下次先攻 与原作不符。删除全局规则;将 +10% 放回 Rejuvenate Tea 遗物条件。
formula-fit.csv 吸血 floor(finalDamage*lifeStealPct/100) 改为 ceil(finalDamage*lifeStealPct/100),正数最低 1。
formula-fit.csv 攻速 候选 max(0.15,1/(attackSpeed/100)) 原生主干已证明 100/attackSpeed;未见全局 0.15 s 下限,不应擅自加 clamp。
暴击 RNG 旧结论 0x549B934 该地址是命中 RNG;暴击在 0x55A96C4..0x55A98C0。

6. Evidence → Finding → Path

6.1 Evidence

E-id source_ref 可复现内容 content_hash
E-COMBAT-001 libil2cpp.runtime-decrypted.so + all_method_addresses.csv 用 radare2 按 RVA 反汇编 GetFinalStatus / HitAndGetResult / OnBeforeAttack / OnAfterAttack / OnCounterAttack SO SHA-256 见第 2 节
E-COMBAT-002 analysis/combat-rules/native-slices/ 独立 ARM64 切片;例:radare2 -q -a arm -b 64 -m 0x5412454 -c "pd 253" CharacterStatus_GetPlayerEvasionPercent.bin 各切片 SHA-256 见目录中文件固定值
E-COMBAT-003 battleplayerdefaultstat.csv, gameconfig.csv 裸装数值、BaseCritical=150、EnemyDamgeRange=15 35DAC0A6...32D8, A57510DE...E3D
E-DOUBLE-001 dicerate.csv + <CheckLastTile>d__130.MoveNext + 原作录像 00:03:35 等权 36 对、相等判 Double、移动取和、1+1→2且累计 +1 已有 Stage 2.5 证据包固定值
E-DOUBLE-002 relic.csv, equipskill.csv, joker.csv Double 消费者名称与 100/1/30/50/10/35、2/5/10 数值 relic.csv E396B478...BE9; equipskill.csv EB8A8674...75C2
E-WEB-001 Heroll Wiki Relics 遗物用户可见语义,用于解释本地 localization key n/a
E-WEB-002 Reddit 玩家反击实测 反击基于敌方攻击,可暴击/未命中,仍受伤,不吸血 n/a

6.2 Findings

F-id 结论 evidence_ids confidence location status
F-COMBAT-001 暴击与命中 RNG 已分离,旧地址归属错误 E-COMBAT-001, E-COMBAT-002 high 0x549B934, 0x55A96C4 validated
F-COMBAT-002 防御线性减伤、攻速取倒数、吸血向上取整 E-COMBAT-001, E-COMBAT-003 high 见第 3 节 validated
F-DOUBLE-001 Double 不倍增移动,不内建伤害倍率 E-DOUBLE-001 high <CheckLastTile>d__130.MoveNext validated
F-DOUBLE-002 Double 的战斗价值由遗物/Joker/宠物/事件订阅者提供 E-DOUBLE-001, E-DOUBLE-002, E-WEB-001 high TriggerOnDiceDouble 消费链 validated
F-DOUBLE-003 Double Stack 的完整阈值、清空时机和终点奖励仍未闭合 E-DOUBLE-001 medium TryAddDoubleStack async state machine candidate

6.3 Path

P-COMBAT-001 — 一次普通攻击的原生调用流

  1. RunAttackSequence 调用攻击者 OnBeforeAttack,建立伤害与暴击标志 — E-COMBAT-001 — F-COMBAT-001。
  2. HitAndGetResult 获得 AIM/目标命中百分比并执行 0..100 判定 — E-COMBAT-001。
  3. 命中后进入受击钩子,执行 max(0, damage-DEF) 并扣 HP — E-COMBAT-001 — F-COMBAT-002。
  4. OnAfterAttack 以最终伤害计算吸血 — E-COMBAT-001 — F-COMBAT-002。
  5. WaitAttackCooldown 用 100/AttackSpeed 计算下一次攻击时间 — E-COMBAT-001 — F-COMBAT-002。

P-DOUBLE-001 — Double 到战斗效果的事件流

  1. DiceRate 加权抽取有序骰子对 — E-DOUBLE-001。
  2. 两骰相等或命中强制分支后置 isDouble — E-DOUBLE-001 — F-DOUBLE-001。
  3. 仍以 A+B 移动,同时把 A 送入 Double Stack — E-DOUBLE-001 — F-DOUBLE-001。
  4. TriggerOnDiceDouble 广播,已装备遗物/Joker/宠物/事件按各自规则结算 — E-DOUBLE-002 — F-DOUBLE-002。

7. 未闭合与建议最小实测

下列内容不影响核心 MVP 公式,但如要 1:1 实现全部构筑,需补录:

  1. 命中百分比的完整 AIM/EVA/宠物/遗物 合成顺序:同一敌人下使用 3 组已知 AIM,每组至少 200 次攻击。
  2. Battleship Horn +30% 攻速的持续时间和叠加方式:录制 Double 前后至少 10 次攻击时间戳。
  3. Gas Mask 护盾量和刷新规则:连续触发两次 Double,记录护盾 UI 数值。
  4. Scissors 传奇追加攻击次数:分别强制 1+1..6+6,记录攻击段数。
  5. Double Stack:连续强制不同骰面 Double,记录累计值、阈值、奖励与清空时机。

8. Timeline 摘要

  • 2026-08-19:建立授权范围,重建 IL2CPP 地址映射和解密 SO。
  • 2026-08-19:提取 93 个有边界的原生方法体,封存 Stage 2.5.1 证据。
  • 2026-08-28:复核主线战斗、纠正暴击地址,闭合攻速/吸血/反击边界,完成 Double 战斗消费者矩阵。

9. 互联网交叉资料