Initial import of HEROLL source and design documents

This commit is contained in:
toshiocn
2026-10-26 16:05:39 +08:00
commit fdb55672c1
1846 changed files with 290692 additions and 0 deletions
@@ -0,0 +1,182 @@
# 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/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 小于百分比”。三种判定的边界不同:
```text
命中: hitPercent > random(0..100)
暴击: critRate > random(0..100)
反击: counterRate >= random(1..100)
```
因此 MVP 若用 `Math.random()*100 < rate` 同时替代三者,会产生边界差异。特别是暴击率 100 在原作中仍会在 roll=100 时失败;只有大于 100 才能在该路径保证暴击。
### 3.2 伤害数据流
```mermaid
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. 互联网交叉资料
- [Heroll Wiki — Relics](https://heroll.fandom.com/wiki/Relics)
- [Reddit r/heroll — Counter attack?](https://www.reddit.com/r/heroll/comments/1l20mkh/counter_attack/)
- [Reddit r/heroll — Slightly in-depth mechanics and interactions](https://www.reddit.com/r/heroll/comments/1l3sn2n/)
- [Google Play — Heroll : Dice Roguelike](https://play.google.com/store/apps/details?id=com.percent.aos.rollinghero)
+10
View File
@@ -0,0 +1,10 @@
# 03-战斗
实时冷却行动模型、命中、暴击、防御、吸血、反击与Double消费者。
## 文件
- [2026-08-28_逆向-HEROLL战斗规则-report.md](./2026-08-28_%E9%80%86%E5%90%91-HEROLL%E6%88%98%E6%96%97%E8%A7%84%E5%88%99-report.md) — report;来源:`analysis/combat-rules/2026-08-28_逆向-HEROLL战斗规则-report.md`
- [combat-formulas.csv](./combat-formulas.csv) — table;来源:`analysis/combat-rules/combat-formulas.csv`
模块内Markdown与CSV均来自同一v0.2.8规则域;跨模块综合报告会在相关模块中保留副本。
@@ -0,0 +1,17 @@
formula_id,system,formula_or_rule,rounding_or_boundary,source_tag,native_location,implementation_status
COMBAT-STAT-001,final_status,"independentBonus + flatSum * (100 + percentBonus) / 100; special query modes may exclude layers","float; mode-dependent branches",NATIVE_CONFIRMED,CharacterStatus.GetFinalStatus@0x5409B54,IMPLEMENT_CORE
COMBAT-DMG-001,player_base_damage,GetFinalStatus(ATTACK_POWER),float,NATIVE_CONFIRMED,BaseActor.GetBaseDamageAndConvertWithPlayerDamage@0x5494CD8,IMPLEMENT
COMBAT-DMG-002,enemy_base_damage,"Random(attackLower*(1-rangePct),attackUpper*(1+rangePct)); rangePct=0.15","float Range",NATIVE_AND_CONFIG_CONFIRMED,BaseActor.GetBaseDamageAndConvertWithPlayerDamage@0x5494CD8,IMPLEMENT
COMBAT-HIT-001,hit,"hitPercent > RandomInt(0,101)","roll 0..100; negative hitPercent clamped to 0",NATIVE_CONFIRMED,BaseMonoCharacter.HitAndGetResult@0x549B2CC,IMPLEMENT
COMBAT-CRIT-001,critical,"finalCriticalRate > RandomInt(0,101)","roll 0..100; strict greater-than",NATIVE_CONFIRMED,BaseMonoPlayer.OnBeforeAttack@0x55A95EC,IMPLEMENT
COMBAT-CRIT-002,critical_damage,"damage * criticalDamagePercent * 0.01","float; BaseCritical=150",NATIVE_AND_CONFIG_CONFIRMED,BaseMonoPlayer.OnBeforeAttack@0x55A9704,IMPLEMENT
COMBAT-DEF-001,defense,"max(0,preDefenseDamage-finalDefense)",float,NATIVE_CONFIRMED,BaseMonoCharacter.HitAndGetResult@0x549BD18,IMPLEMENT
COMBAT-HP-001,hp,"targetHp-finalDamage","float; display rounding separate",NATIVE_CONFIRMED,BaseMonoCharacter.HitAndGetResult@0x549BE7C,IMPLEMENT
COMBAT-ASPD-001,attack_speed,"100/finalAttackSpeed","seconds; subtract elapsed; no global 0.15 clamp observed",NATIVE_CONFIRMED,<WaitAttackCooldown>d__45.MoveNext@0x5497280,IMPLEMENT
COMBAT-LS-001,life_steal,"ceil(finalDamage*finalLifeSteal*0.01)","only if positive; minimum heal 1",NATIVE_CONFIRMED,BaseMonoPlayer.OnAfterAttack@0x55A9E48,IMPLEMENT
COMBAT-COUNTER-001,counter_chance,"finalCounterRate >= RandomInt(1,101)","roll 1..100; inclusive",NATIVE_CONFIRMED,BaseMonoPlayer.OnCounterAttack@0x55AC048,IMPLEMENT
DOUBLE-001,natural_double,diceA==diceB,"built-in 36 pairs equal weight => 1/6 absent override",NATIVE_CONFIG_VIDEO_CONFIRMED,<CheckLastTile>d__130.MoveNext,IMPLEMENT
DOUBLE-002,double_movement,diceA+diceB,"never multiply by 2 solely because Double",NATIVE_VIDEO_CONFIRMED,<CheckLastTile>d__130.MoveNext,IMPLEMENT
DOUBLE-003,double_stack,TryAddDoubleStack(diceA),"accumulate face value; threshold/reward/reset unresolved",NATIVE_VIDEO_PARTIAL,BoardController.TryAddDoubleStack@0x56A4BE0,IMPLEMENT_ACCUMULATION_ONLY
DOUBLE-004,double_combat,"no unconditional damage multiplier; dispatch TriggerOnDiceDouble to equipped consumers",event-driven,NATIVE_CONFIG_CONFIRMED,EventManager.TriggerOnDiceDouble@0x51AEDEC,IMPLEMENT
1 formula_id system formula_or_rule rounding_or_boundary source_tag native_location implementation_status
2 COMBAT-STAT-001 final_status independentBonus + flatSum * (100 + percentBonus) / 100; special query modes may exclude layers float; mode-dependent branches NATIVE_CONFIRMED CharacterStatus.GetFinalStatus@0x5409B54 IMPLEMENT_CORE
3 COMBAT-DMG-001 player_base_damage GetFinalStatus(ATTACK_POWER) float NATIVE_CONFIRMED BaseActor.GetBaseDamageAndConvertWithPlayerDamage@0x5494CD8 IMPLEMENT
4 COMBAT-DMG-002 enemy_base_damage Random(attackLower*(1-rangePct),attackUpper*(1+rangePct)); rangePct=0.15 float Range NATIVE_AND_CONFIG_CONFIRMED BaseActor.GetBaseDamageAndConvertWithPlayerDamage@0x5494CD8 IMPLEMENT
5 COMBAT-HIT-001 hit hitPercent > RandomInt(0,101) roll 0..100; negative hitPercent clamped to 0 NATIVE_CONFIRMED BaseMonoCharacter.HitAndGetResult@0x549B2CC IMPLEMENT
6 COMBAT-CRIT-001 critical finalCriticalRate > RandomInt(0,101) roll 0..100; strict greater-than NATIVE_CONFIRMED BaseMonoPlayer.OnBeforeAttack@0x55A95EC IMPLEMENT
7 COMBAT-CRIT-002 critical_damage damage * criticalDamagePercent * 0.01 float; BaseCritical=150 NATIVE_AND_CONFIG_CONFIRMED BaseMonoPlayer.OnBeforeAttack@0x55A9704 IMPLEMENT
8 COMBAT-DEF-001 defense max(0,preDefenseDamage-finalDefense) float NATIVE_CONFIRMED BaseMonoCharacter.HitAndGetResult@0x549BD18 IMPLEMENT
9 COMBAT-HP-001 hp targetHp-finalDamage float; display rounding separate NATIVE_CONFIRMED BaseMonoCharacter.HitAndGetResult@0x549BE7C IMPLEMENT
10 COMBAT-ASPD-001 attack_speed 100/finalAttackSpeed seconds; subtract elapsed; no global 0.15 clamp observed NATIVE_CONFIRMED <WaitAttackCooldown>d__45.MoveNext@0x5497280 IMPLEMENT
11 COMBAT-LS-001 life_steal ceil(finalDamage*finalLifeSteal*0.01) only if positive; minimum heal 1 NATIVE_CONFIRMED BaseMonoPlayer.OnAfterAttack@0x55A9E48 IMPLEMENT
12 COMBAT-COUNTER-001 counter_chance finalCounterRate >= RandomInt(1,101) roll 1..100; inclusive NATIVE_CONFIRMED BaseMonoPlayer.OnCounterAttack@0x55AC048 IMPLEMENT
13 DOUBLE-001 natural_double diceA==diceB built-in 36 pairs equal weight => 1/6 absent override NATIVE_CONFIG_VIDEO_CONFIRMED <CheckLastTile>d__130.MoveNext IMPLEMENT
14 DOUBLE-002 double_movement diceA+diceB never multiply by 2 solely because Double NATIVE_VIDEO_CONFIRMED <CheckLastTile>d__130.MoveNext IMPLEMENT
15 DOUBLE-003 double_stack TryAddDoubleStack(diceA) accumulate face value; threshold/reward/reset unresolved NATIVE_VIDEO_PARTIAL BoardController.TryAddDoubleStack@0x56A4BE0 IMPLEMENT_ACCUMULATION_ONLY
16 DOUBLE-004 double_combat no unconditional damage multiplier; dispatch TriggerOnDiceDouble to equipped consumers event-driven NATIVE_CONFIG_CONFIRMED EventManager.TriggerOnDiceDouble@0x51AEDEC IMPLEMENT