feat(迭代24): N3 敌方attack_type轴开列——20行全填normal+schema枚举校验+单位快照+开单传值+敌方结算克制消费点(卡片§2漏项,V5矩阵证词强制)+V5四断言(冒烟949/0)

This commit is contained in:
DKChan committed 2026-10-13 07:14:58 +08:00
1 parent 26d669f83d
commit ab3ba4f4a0
6 files changed
+109 -9

No files matched your search

+4 -3
View File
@@ -1397,14 +1397,15 @@ func _combat_segment(ri: int) -> void:
_enemy_system.commit_action(u) _enemy_system.commit_action(u)
continue continue
# 伤害技(含普攻——倍率恒 1)= 敌方伤害技单据:基线 = round(atk×倍率)、rider 预计算归开单侧 # 伤害技(含普攻——倍率恒 1)= 敌方伤害技单据:基线 = round(atk×倍率)、rider 预计算归开单侧
# (表值算术零随机),掷点/克制恒中性/hero_defense×enemy_output 池乘区/下限/rider 后置/commit # (表值算术零随机),掷点/克制查表(第 9 参 = 敌方 attack_type——迭代 24 N3 轴开列前恒空串不查表)/
# 全归控制器管线(兑现序 = 开单序;来源敌已死在兑现时作废——死亡即不动手)。 # hero_defense×enemy_output 池乘区/下限/rider 后置/commit 全归控制器管线(兑现序 = 开单序;
# 来源敌已死在兑现时作废——死亡即不动手)。
var baseline: int = int(round(float(u.atk) * float(srow.get("damage_mult", 1.0)))) var baseline: int = int(round(float(u.atk) * float(srow.get("damage_mult", 1.0))))
var rider := 0 var rider := 0
if srow.has("rider_mult"): if srow.has("rider_mult"):
rider = maxi(int(db.settlement.damage_floor), int(floor(float(u.atk) * float(srow.rider_mult)))) rider = maxi(int(db.settlement.damage_floor), int(floor(float(u.atk) * float(srow.rider_mult))))
var et := _damage.open_ticket(DamageController.KIND_ENEMY_SKILL, "enemy_action", "%s %s" % [u.display_name, String(srow.display_name)], "combat", \ var et := _damage.open_ticket(DamageController.KIND_ENEMY_SKILL, "enemy_action", "%s %s" % [u.display_name, String(srow.display_name)], "combat", \
baseline, DamageController.TARGET_HERO, ri, Vector2i(-1, -1), "", rider) baseline, DamageController.TARGET_HERO, ri, Vector2i(-1, -1), String(u.get("attack_type", "normal")), rider)
et["source_unit"] = u et["source_unit"] = u
et["context"] = {"skill_id": skill_id, "baseline": baseline} et["context"] = {"skill_id": skill_id, "baseline": baseline}
_damage.submit(et) _damage.submit(et)
+8 -5
View File
@@ -33,7 +33,8 @@ const POLICY_SEGMENT := "segment" # B:攒批到段界 flush 统一结(
# hero_attack 战斗段·主角先手:掷点折扣暴击 → 克制点数(attack_type vs 敌护甲类型)→ # hero_attack 战斗段·主角先手:掷点折扣暴击 → 克制点数(attack_type vs 敌护甲类型)→
# hero_offense×enemy_defense 池乘区 → 下限 → 交付;行载荷带 roll/ac/tier/killed。 # hero_offense×enemy_defense 池乘区 → 下限 → 交付;行载荷带 roll/ac/tier/killed。
# enemy_skill 战斗段·敌方伤害技(含普攻):基线 = round(atk×倍率)(开单侧算好入 base_value)→ # enemy_skill 战斗段·敌方伤害技(含普攻):基线 = round(atk×倍率)(开单侧算好入 base_value)→
# 掷点折扣暴击 → 克制恒中性(敌打主角无甲轴 delta 0)→ hero_defense×enemy_output # 掷点折扣暴击 → 克制点数(attack_type vs 主角护甲类型——迭代 24 N3 轴开列,
# 前态恒中性 delta 0)→ hero_defense×enemy_output
# 池乘区 → 下限 → +rider(必中附加)→ 交付 → commit_action;来源敌已死 = 作废。 # 池乘区 → 下限 → +rider(必中附加)→ 交付 → commit_action;来源敌已死 = 作废。
# board_typed 棋盘·单目标克制直伤(穿刺弹/魔法矢正向):必中全额,克制点数进加法核,下限保底。 # board_typed 棋盘·单目标克制直伤(穿刺弹/魔法矢正向):必中全额,克制点数进加法核,下限保底。
# board_blast 棋盘·群伤(爆裂粉尘/清障炸药):无类型恒定表值,全体存活敌逐个交付。 # board_blast 棋盘·群伤(爆裂粉尘/清障炸药):无类型恒定表值,全体存活敌逐个交付。
@@ -413,7 +414,8 @@ func _resolve_hero_attack(t: Dictionary) -> void:
_outcomes[int(t.ticket_id)] = "redeemed" _outcomes[int(t.ticket_id)] = "redeemed"
# 敌方伤害技:掷点 → 折扣/暴击 → 克制恒中性 delta 0(敌打主角无甲轴)→ hero_defense×enemy_output # 敌方伤害技:掷点 → 折扣/暴击 → 克制点数(attack_type vs 主角护甲类型——迭代 24 N3 轴开列;
# 前态恒中性 delta 0,写法同 hero_attack 的 _counter_delta 消费位)→ hero_defense×enemy_output
# 池乘区 → 下限 → +rider(后置必中附加)→ 交付 → commit_action(作废单不 commit——死亡即不动手) # 池乘区 → 下限 → +rider(后置必中附加)→ 交付 → commit_action(作废单不 commit——死亡即不动手)
func _resolve_enemy_skill(t: Dictionary) -> void: func _resolve_enemy_skill(t: Dictionary) -> void:
var hero: Dictionary = units.hero_unit() var hero: Dictionary = units.hero_unit()
@@ -421,7 +423,8 @@ func _resolve_enemy_skill(t: Dictionary) -> void:
var threshold: int = defender_ac(hero) + int(db.settlement.attack_bonus) var threshold: int = defender_ac(hero) + int(db.settlement.attack_bonus)
var crit: bool = bool(roll_read.crit) var crit: bool = bool(roll_read.crit)
var discount: int = _roll_discount(int(roll_read.roll), threshold) var discount: int = _roll_discount(int(roll_read.roll), threshold)
var rolled: int = DamageMath.rolled_component(float(t.base_value), discount, crit, int(db.settlement.crit_multiplier), 0) var cdelta: int = _counter_delta(String(t.attack_type), String(hero.get("armor_type", "light")))
var rolled: int = DamageMath.rolled_component(float(t.base_value), discount, crit, int(db.settlement.crit_multiplier), cdelta)
var pooled: int = DamageMath.pool_damage(rolled, clamped_pool("hero_defense"), clamped_pool("enemy_output")) var pooled: int = DamageMath.pool_damage(rolled, clamped_pool("hero_defense"), clamped_pool("enemy_output"))
var total: int = maxi(int(db.settlement.damage_floor), pooled) + int(t.rider) # rider 后置于伤害下限——独立必中加项,不进乘区 var total: int = maxi(int(db.settlement.damage_floor), pooled) + int(t.rider) # rider 后置于伤害下限——独立必中加项,不进乘区
# damage_taken 发射+施加(内容数据驱动扩展批 T3——kind=enemy_skill;铁壁整备减伤订阅面: # damage_taken 发射+施加(内容数据驱动扩展批 T3——kind=enemy_skill;铁壁整备减伤订阅面:
@@ -446,7 +449,7 @@ func _resolve_enemy_skill(t: Dictionary) -> void:
total = maxi(int(db.settlement.damage_floor), total - shield) total = maxi(int(db.settlement.damage_floor), total - shield)
units.apply_damage(hero, total) units.apply_damage(hero, total)
var payload: Dictionary = {"target": "hero", "skill_id": String(t.context.get("skill_id", "")), "baseline": int(t.context.get("baseline", 0)), var payload: Dictionary = {"target": "hero", "skill_id": String(t.context.get("skill_id", "")), "baseline": int(t.context.get("baseline", 0)),
"roll": int(roll_read.roll), "ac": threshold, "tier": discount} "roll": int(roll_read.roll), "ac": threshold, "tier": discount, "attack_type": String(t.attack_type)}
if crit: if crit:
payload["crit"] = true payload["crit"] = true
if int(t.rider) > 0: if int(t.rider) > 0:
@@ -547,7 +550,7 @@ func defender_ac(unit: Dictionary) -> int:
# 克制修正点数读值(SettlementTable.armor_effectiveness:{攻击类型: {护甲: 点数}}——缺项回退 0 中性; # 克制修正点数读值(SettlementTable.armor_effectiveness:{攻击类型: {护甲: 点数}}——缺项回退 0 中性;
# attack_type "" = 无类型恒不查(群伤/反噬/地形/敌方技)) # attack_type "" = 无类型恒不查(群伤/反噬/地形——敌方技自迭代 24 N3 起随单轴查表))
func _counter_delta(attack_type: String, armor: String) -> int: func _counter_delta(attack_type: String, armor: String) -> int:
if attack_type == "": if attack_type == "":
return 0 return 0
+1
View File
@@ -118,6 +118,7 @@ func spawn(level_index: int, node_depth: int, hero_hp: int, unit_count_overrides
"basic_streak": 0, # 保底计数:连续执行普攻的拍数(执行上报驱动) "basic_streak": 0, # 保底计数:连续执行普攻的拍数(执行上报驱动)
"armor_type": String(row.get("armor_type", "light")), # 护甲类型快照(克制矩阵消费面——战斗结算·战斗段;2026-09-15 命名统一批:light/ward/heavy) "armor_type": String(row.get("armor_type", "light")), # 护甲类型快照(克制矩阵消费面——战斗结算·战斗段;2026-09-15 命名统一批:light/ward/heavy)
"armor_class": int(row.get("armor_class", 0)), # 护甲等级快照(掷点判定阈值基数——战斗结算·战斗段;2026-09-16 护甲等级掷点批) "armor_class": int(row.get("armor_class", 0)), # 护甲等级快照(掷点判定阈值基数——战斗结算·战斗段;2026-09-16 护甲等级掷点批)
"attack_type": String(row.get("attack_type", "normal")), # 攻击类型快照(克制矩阵消费面——敌方伤害单据第 9 参;迭代 24 N3 轴开列,缺省 normal 向后兼容)
"alive": true, "alive": true,
"is_hero": false, "is_hero": false,
}) })
+9
View File
@@ -17,6 +17,11 @@ extends Resource
# 2026-09-15 命名统一批:位置键 {none, a, b} → 语义键 {light, ward, heavy} = 轻甲 / 魔法护甲 / 重甲) # 2026-09-15 命名统一批:位置键 {none, a, b} → 语义键 {light, ward, heavy} = 轻甲 / 魔法护甲 / 重甲)
const ARMOR_TYPES := ["light", "ward", "heavy"] const ARMOR_TYPES := ["light", "ward", "heavy"]
# 攻击类型封闭枚举(迭代 24 N3 2026-10-13——敌方攻击类型轴开列,承接锻造工坊迁移面⑥;
# 克制矩阵点数制键前缀 = 本枚举:normal → light 0 / ward +1 / heavy −1;pierce/magic 为差异化
# 预留档,差异化填值属新设计另立项。可省略 + 缺省 normal 向后兼容)
const ATTACK_TYPES := ["normal", "pierce", "magic"]
# 实例化数量域(2026-10-02 修复批 T2 魔法数结构化——数量域 [1,3] 双落点收口单点: # 实例化数量域(2026-10-02 修复批 T2 魔法数结构化——数量域 [1,3] 双落点收口单点:
# 本表 validate 与 单位容器 spawn 守卫共用;遭遇骰结果与行值 unit_count/unit_count_max 同域。 # 本表 validate 与 单位容器 spawn 守卫共用;遭遇骰结果与行值 unit_count/unit_count_max 同域。
# 2026-10-07 行集多行批:MAX 语义 = **单种数**(单行实例数上限),行集实例总数 ≤4 另由 # 2026-10-07 行集多行批:MAX 语义 = **单种数**(单行实例数上限),行集实例总数 ≤4 另由
@@ -111,6 +116,10 @@ func validate() -> Array:
var at: String = String(row.get("armor_type", "light")) var at: String = String(row.get("armor_type", "light"))
if not ARMOR_TYPES.has(at): if not ARMOR_TYPES.has(at):
errs.append({"category": "enum", "message": "%s armor_type 未知枚举: %s(域 {light, ward, heavy})" % [eid, at]}) errs.append({"category": "enum", "message": "%s armor_type 未知枚举: %s(域 {light, ward, heavy})" % [eid, at]})
# attack_type(迭代 24 N3——可省略,缺省 normal 向后兼容;写法沿用 armor_type 同款)
var att: String = String(row.get("attack_type", "normal"))
if not ATTACK_TYPES.has(att):
errs.append({"category": "enum", "message": "%s attack_type 未知枚举: %s(域 {normal, pierce, magic})" % [eid, att]})
# armor_class(可省略——缺省 0 向后兼容;2026-09-16 护甲等级掷点批:防守方掷点判定阈值基数) # armor_class(可省略——缺省 0 向后兼容;2026-09-16 护甲等级掷点批:防守方掷点判定阈值基数)
var ac: int = int(row.get("armor_class", 0)) var ac: int = int(row.get("armor_class", 0))
if ac < 0 or ac > 999: if ac < 0 or ac > 999:
+24 -1
View File
@@ -22,6 +22,7 @@ rows = [{
"skill_id": "lunge", "skill_id": "lunge",
"weight": 1 "weight": 1
}], }],
"attack_type": "normal",
"armor_type": "light", "armor_type": "light",
"armor_class": 0 "armor_class": 0
}, { }, {
@@ -42,6 +43,7 @@ rows = [{
"skill_id": "lunge", "skill_id": "lunge",
"weight": 1 "weight": 1
}], }],
"attack_type": "normal",
"armor_type": "light", "armor_type": "light",
"armor_class": 1 "armor_class": 1
}, { }, {
@@ -64,6 +66,7 @@ rows = [{
"skill_id": "heavy", "skill_id": "heavy",
"weight": 2 "weight": 2
}], }],
"attack_type": "normal",
"armor_type": "light", "armor_type": "light",
"armor_class": 2 "armor_class": 2
}, { }, {
@@ -87,6 +90,7 @@ rows = [{
"skill_id": "heavy", "skill_id": "heavy",
"weight": 2 "weight": 2
}], }],
"attack_type": "normal",
"armor_type": "light", "armor_type": "light",
"armor_class": 2 "armor_class": 2
}, { }, {
@@ -113,6 +117,7 @@ rows = [{
"weight": 2, "weight": 2,
"unlock_hp_pct": 50 "unlock_hp_pct": 50
}], }],
"attack_type": "normal",
"armor_type": "light", "armor_type": "light",
"armor_class": 8 "armor_class": 8
}, { }, {
@@ -135,7 +140,8 @@ rows = [{
"skill_id": "heavy", "skill_id": "heavy",
"weight": 2 "weight": 2
}], }],
"armor_type": "light", "attack_type": "normal",
"armor_type": "light",
"armor_class": 1 "armor_class": 1
}, { }, {
"level_index": 2, "level_index": 2,
@@ -158,6 +164,7 @@ rows = [{
"skill_id": "heavy", "skill_id": "heavy",
"weight": 2 "weight": 2
}], }],
"attack_type": "normal",
"armor_type": "light", "armor_type": "light",
"armor_class": 2 "armor_class": 2
}, { }, {
@@ -180,6 +187,7 @@ rows = [{
"skill_id": "charge", "skill_id": "charge",
"weight": 1 "weight": 1
}], }],
"attack_type": "normal",
"armor_type": "light", "armor_type": "light",
"armor_class": 2 "armor_class": 2
}, { }, {
@@ -202,6 +210,7 @@ rows = [{
"skill_id": "heavy", "skill_id": "heavy",
"weight": 2 "weight": 2
}], }],
"attack_type": "normal",
"armor_type": "light", "armor_type": "light",
"armor_class": 8 "armor_class": 8
}, { }, {
@@ -228,6 +237,7 @@ rows = [{
"skill_id": "venom_spit", "skill_id": "venom_spit",
"weight": 2 "weight": 2
}], }],
"attack_type": "normal",
"armor_type": "light", "armor_type": "light",
"armor_class": 2 "armor_class": 2
}, { }, {
@@ -254,6 +264,7 @@ rows = [{
"skill_id": "venom_spit", "skill_id": "venom_spit",
"weight": 2 "weight": 2
}], }],
"attack_type": "normal",
"armor_type": "light", "armor_type": "light",
"armor_class": 2 "armor_class": 2
}, { }, {
@@ -280,6 +291,7 @@ rows = [{
"skill_id": "venom_spit", "skill_id": "venom_spit",
"weight": 2 "weight": 2
}], }],
"attack_type": "normal",
"armor_type": "light", "armor_type": "light",
"armor_class": 3 "armor_class": 3
}, { }, {
@@ -309,6 +321,7 @@ rows = [{
"weight": 2, "weight": 2,
"unlock_hp_pct": 50 "unlock_hp_pct": 50
}], }],
"attack_type": "normal",
"armor_type": "ward", "armor_type": "ward",
"armor_class": 10 "armor_class": 10
}, { }, {
@@ -334,6 +347,7 @@ rows = [{
"skill_id": "shield_bash", "skill_id": "shield_bash",
"weight": 2 "weight": 2
}], }],
"attack_type": "normal",
"armor_type": "heavy", "armor_type": "heavy",
"armor_class": 2 "armor_class": 2
}, { }, {
@@ -356,6 +370,7 @@ rows = [{
"skill_id": "charge", "skill_id": "charge",
"weight": 1 "weight": 1
}], }],
"attack_type": "normal",
"armor_type": "light", "armor_type": "light",
"armor_class": 3 "armor_class": 3
}, { }, {
@@ -379,6 +394,7 @@ rows = [{
"skill_id": "charge", "skill_id": "charge",
"weight": 1 "weight": 1
}], }],
"attack_type": "normal",
"armor_type": "light", "armor_type": "light",
"armor_class": 3 "armor_class": 3
}, { }, {
@@ -404,6 +420,7 @@ rows = [{
"skill_id": "shield_bash", "skill_id": "shield_bash",
"weight": 2 "weight": 2
}], }],
"attack_type": "normal",
"armor_type": "heavy", "armor_type": "heavy",
"armor_class": 9 "armor_class": 9
}, { }, {
@@ -427,6 +444,7 @@ rows = [{
"skill_id": "charge", "skill_id": "charge",
"weight": 1 "weight": 1
}], }],
"attack_type": "normal",
"armor_type": "light", "armor_type": "light",
"armor_class": 3 "armor_class": 3
}, { }, {
@@ -453,6 +471,7 @@ rows = [{
"skill_id": "shield_bash", "skill_id": "shield_bash",
"weight": 2 "weight": 2
}], }],
"attack_type": "normal",
"armor_type": "heavy", "armor_type": "heavy",
"armor_class": 4 "armor_class": 4
}, { }, {
@@ -479,6 +498,7 @@ rows = [{
"weight": 2, "weight": 2,
"unlock_hp_pct": 50 "unlock_hp_pct": 50
}], }],
"attack_type": "normal",
"armor_type": "heavy", "armor_type": "heavy",
"armor_class": 12 "armor_class": 12
}] }]
@@ -646,3 +666,6 @@ rows = [{
; 2026-09-24 阶名化解方案((1,3) 改名大地精以阶名归位化解同族血量倒挂观感)实机验证未达预期,按体验反馈重裁: ; 2026-09-24 阶名化解方案((1,3) 改名大地精以阶名归位化解同族血量倒挂观感)实机验证未达预期,按体验反馈重裁:
; 第一关总量曲线 12/10/50/35/72 的 5 倍墙消除(22 后曲线 12/10/22/35/72 相邻比最大 2.2);敌种 bugbear/池/数量/atk 区间零改动。 ; 第一关总量曲线 12/10/50/35/72 的 5 倍墙消除(22 后曲线 12/10/22/35/72 相邻比最大 2.2);敌种 bugbear/池/数量/atk 区间零改动。
; 曲线标定其余行归 M4 数值批。 ; 曲线标定其余行归 M4 数值批。
; N3 敌方攻击类型轴开列(2026-10-13 迭代 24 卡①,承接锻造工坊实装批迁移面⑥):20 行全填 normal 挂敌种行——
; 不做差异化(pierce/magic 属新设计另立项)。全局平移效应=主角穿重甲受伤 −1 / 魔法护甲 +1 / 轻甲 0,
; 锻造甲路线首次具备战斗意义;克制矩阵数值零触碰(settlement.tres armor_effectiveness 不改)。
+63
View File
@@ -73,6 +73,7 @@ func _run_core_smoke(db, rng) -> void:
_db = db _db = db
_rng = rng _rng = rng
_smoke_ac_swap_guard() _smoke_ac_swap_guard()
_smoke_enemy_attack_type()
_smoke_armor_roll() _smoke_armor_roll()
_smoke_pool_wiring() _smoke_pool_wiring()
_smoke_board_generator() _smoke_board_generator()
@@ -1235,6 +1236,68 @@ func _smoke_level() -> void:
"模拟后 hero 表恢复基线(atk=%d hp=%d ac=%d——对配置快照)" % [int(_db.hero.atk), int(_db.hero.max_hp), int(_db.hero.armor_class)]) "模拟后 hero 表恢复基线(atk=%d hp=%d ac=%d——对配置快照)" % [int(_db.hero.atk), int(_db.hero.max_hp), int(_db.hero.armor_class)])
# ---- 迭代 24 N3:敌方攻击类型轴(2026-10-13——表列/单位快照/单据载荷/矩阵命中证词)----
# 克制矩阵命中证词 = 同种子三甲型配对实测(不是查表存在性证词):同种子下 armor_roll 流消费逐位一致、
# 阈值(hero armor_class)不变,三甲型唯一差异 = 克制点数 cdelta(normal_vs_light 0 / heavy −1 / ward +1)
# → heavy 伤害 == light −1、ward == light +1(要求基线伤害 ≥ 2——地板 1 会吞 −1 位移)。
# 承伤位移红线(卡片 §8 L3):全局位移只允许 ±1 量级——非此量级 = 矩阵消费面有未盘点的第二落点(停机)。
func _smoke_enemy_attack_type() -> void:
print("--- 敌方攻击类型轴(迭代 24 N3)---")
# ① 表列:20 行全填 normal(N3 全填不做差异化——Q9)
var at_ok := true
var at_detail := ""
for row in _db.enemy.rows:
var at := String(row.get("attack_type", ""))
if at != "normal":
at_ok = false
at_detail += " %s:%s" % [String(row.get("enemy_id", "?")), at]
check(at_ok, "敌方 %d 行 attack_type 全填 normal(迭代 24 N3 轴开列,越轨行%s)" % [
_db.enemy.rows.size(), at_detail if at_detail != "" else "无"])
# ②③④ 矩阵命中证词扫描:(3,0) 灰矮人 atk 区间 [2,4] 基线足够((1,2) 熊地精 atk 1 会塌地板);
# 同 seed 三甲型各开一场、各取首个敌方伤害行,对拍 ±1。
var matrix_found := false
var snap_ok := true
var payload_at := ""
var t_light := -1
var t_heavy := -1
var t_ward := -1
for seed_offset in range(40):
var totals := {}
for armor in ["light", "heavy", "ward"]:
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(20261013 + seed_offset)
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng)
var rows: Array = []
settlement.step_row.connect(func(row): rows.append(row))
if not settlement.begin_battle({"level_index": 3, "node_depth": 0, "hero_hp": int(_db.hero.max_hp), "armor_type": armor}):
totals[armor] = -1
continue
if armor == "light":
for u in settlement._enemy_system.all_enemies():
if String(u.get("attack_type", "")) == "":
snap_ok = false
settlement.minesweeper.settle_batch([{"action": "stop"}])
for row in rows:
if String(row.get("type", "")) == "damage" and String(row.get("source_class", "")) == "enemy_action":
totals[armor] = int(row.get("value", 0))
if armor == "light":
payload_at = String(row.get("attack_type", "")) # 载荷摊平在行顶层(_emit_row 摊平 extra)
break
if not totals.has(armor):
totals[armor] = -1
if int(totals.get("light", -1)) >= 2 and int(totals.get("heavy", -1)) >= 1 and int(totals.get("ward", -1)) >= 1:
t_light = int(totals["light"])
t_heavy = int(totals["heavy"])
t_ward = int(totals["ward"])
matrix_found = (t_heavy == t_light - 1 and t_ward == t_light + 1)
break
check(snap_ok, "敌方单位快照 attack_type 非空(spawn 注入面——迭代 24 N3)")
check(matrix_found and t_light >= 2,
"克制矩阵命中证词:同种子三甲型实测 heavy %d == light %d − 1 · ward %d == light %d + 1(迭代 24 N3 全局平移 −1/0/+1)" % [
t_heavy, t_light, t_ward, t_light])
check(payload_at == "normal", "敌方伤害技单据载荷 attack_type 非空(= %s——第 9 参传值面,迭代 24 N3)" % payload_at)
# ---- 护甲等级掷点批·换装护栏(2026-09-16 用户拍板:百分比减伤行整体退役换装 AC,无双轨)---- # ---- 护甲等级掷点批·换装护栏(2026-09-16 用户拍板:百分比减伤行整体退役换装 AC,无双轨)----
# ① 换装在位:defense_up 状态行/池映射/奖励行三处全无残留,ac_up 三处就位(unit=ac,2026-09-20 改只续时不叠层); # ① 换装在位:defense_up 状态行/池映射/奖励行三处全无残留,ac_up 三处就位(unit=ac,2026-09-20 改只续时不叠层);
# ② 免伤墙归零护栏泛化保留(BUG-0003 事故类永久免疫):hero_defense 池任何 percent 行 # ② 免伤墙归零护栏泛化保留(BUG-0003 事故类永久免疫):hero_defense 池任何 percent 行