14 KiB
14 KiB
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 的闭合结论是:
- 玩家基础攻击取最终攻击力;敌人攻击在自身攻击区间上叠加
EnemyDamgeRange=15的浮动。 - 命中、暴击和反击是三次独立判定,不能共用一个随机结果。旧报告把
0x549B934误当暴击 RNG;该点实际是命中/闪避 RNG。 - 暴击率判定为
CR > RandomInt(0,101);基础暴伤为 150%,暴击伤害为damage * criticalDamagePercent / 100。 - 防御是线性减伤:
max(0, 防御前伤害 - 最终防御),不是比例减伤。 - 攻速换算为
攻击间隔秒 = 100 / 最终攻速;裸装攻速 100 对应 1.0 秒基础间隔。 - 吸血为
ceil(本次最终伤害 * 吸血% / 100),只要计算值为正,最低恢复 1 HP。 - Double 的基础含义只是“两骰相等”并广播 Double 事件;移动仍为
diceA + diceB,不会自动再×2,也没有无条件伤害倍率。 - 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 基础规则
dicerate.csv包含 36 个有序 D6 组合,APK 内置权重全为 10。原生代码按weight/sum(weight)抽取。在没有远程覆盖时,每个有序结果为 1/36,自然 Double 概率为 6/36=1/6。isDouble = (diceA == diceB) OR forcedDoubleBranch。原作存在独立的强制 Double 分支,但当前启用内容中Potion_GuaranteedDouble为禁用。moveCount = diceA + diceB。录像中 1+1 显示总点数 2,不是 4。- Double 发生后,第一枚骰子的值传入
BoardController.TryAddDoubleStack(diceA);录像中 1+1 后显示“Double 点数 +1”。这是“累计骰面点数”,不应实现成每次 Double 固定 +1。 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 — 一次普通攻击的原生调用流
RunAttackSequence调用攻击者OnBeforeAttack,建立伤害与暴击标志 — E-COMBAT-001 — F-COMBAT-001。HitAndGetResult获得 AIM/目标命中百分比并执行 0..100 判定 — E-COMBAT-001。- 命中后进入受击钩子,执行
max(0, damage-DEF)并扣 HP — E-COMBAT-001 — F-COMBAT-002。 OnAfterAttack以最终伤害计算吸血 — E-COMBAT-001 — F-COMBAT-002。WaitAttackCooldown用100/AttackSpeed计算下一次攻击时间 — E-COMBAT-001 — F-COMBAT-002。
P-DOUBLE-001 — Double 到战斗效果的事件流
DiceRate加权抽取有序骰子对 — E-DOUBLE-001。- 两骰相等或命中强制分支后置
isDouble— E-DOUBLE-001 — F-DOUBLE-001。 - 仍以
A+B移动,同时把 A 送入 Double Stack — E-DOUBLE-001 — F-DOUBLE-001。 TriggerOnDiceDouble广播,已装备遗物/Joker/宠物/事件按各自规则结算 — E-DOUBLE-002 — F-DOUBLE-002。
7. 未闭合与建议最小实测
下列内容不影响核心 MVP 公式,但如要 1:1 实现全部构筑,需补录:
- 命中百分比的完整
AIM/EVA/宠物/遗物合成顺序:同一敌人下使用 3 组已知 AIM,每组至少 200 次攻击。 - Battleship Horn +30% 攻速的持续时间和叠加方式:录制 Double 前后至少 10 次攻击时间戳。
- Gas Mask 护盾量和刷新规则:连续触发两次 Double,记录护盾 UI 数值。
- Scissors 传奇追加攻击次数:分别强制 1+1..6+6,记录攻击段数。
- Double Stack:连续强制不同骰面 Double,记录累计值、阈值、奖励与清空时机。
8. Timeline 摘要
- 2026-08-19:建立授权范围,重建 IL2CPP 地址映射和解密 SO。
- 2026-08-19:提取 93 个有边界的原生方法体,封存 Stage 2.5.1 证据。
- 2026-08-28:复核主线战斗、纠正暴击地址,闭合攻速/吸血/反击边界,完成 Double 战斗消费者矩阵。