diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index ac83b56d..09a2fa00 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -162,7 +162,6 @@ func _run_core_smoke(db, rng) -> void: _smoke_level() _smoke_seal_ap_exhausted() _smoke_active_refresh() - _smoke_damage_types() _smoke_playtest_logger() _smoke_battle_log_view() _smoke_global_theme() @@ -1868,68 +1867,6 @@ func _active_refresh_replay() -> Array: return rows -# ---- 攻击类型 × 护甲克制 + 标错惩罚冒烟(护甲快照/克制矩阵/充能/反噬三选一)---- - -func _smoke_damage_types() -> void: - print("--- 攻击类型 × 护甲克制 + 标错惩罚 ---") - # ① 护甲类型快照传播(spawn 行值 → 单位字典) - var enemy_system = load("res://core/unit_container.gd").new(_db) - check(enemy_system.spawn(1, 0, int(_db.hero.max_hp)) and String(enemy_system.all_enemies()[0].armor_type) == "light", "goblin 护甲 = light(轻甲——教学位)") - var enemy_system_b = load("res://core/unit_container.gd").new(_db) - check(enemy_system_b.spawn(1, 2, int(_db.hero.max_hp)) and String(enemy_system_b.all_enemies()[0].armor_type) == "light", "bugbear 护甲 = light(护甲类型重配批 2026-09-18:关卡 1 全轻甲)") - var enemy_system_c = load("res://core/unit_container.gd").new(_db) - check(enemy_system_c.spawn(1, 4, int(_db.hero.max_hp), [], -1, -1, [], "", "boss") and String(enemy_system_c.all_enemies()[0].armor_type) == "light", "ogre_chief 护甲 = light(Boss 行集——护甲类型重配批 2026-09-18:关卡 1 全轻甲)") - # ② 克制矩阵表值在位(三档点数 克制 +1 / 中性 0 / 被克 −1,加法核非 percent) - var am: Dictionary = _db.settlement.armor_effectiveness - check(int(am.normal.light) == 0 and int(am.normal.ward) == 1 and int(am.normal.heavy) == -1, "普通行:轻甲 0 / 魔法护甲 +1 / 重甲 −1(加法核)") - check(int(am.pierce.light) == -1 and int(am.pierce.ward) == 1 and int(am.pierce.heavy) == 0, "穿刺行:轻甲 −1 / 魔法护甲 +1 / 重甲 0 中性(用户拍板)") - check(int(am.magic.light) == 0 and int(am.magic.ward) == -1 and int(am.magic.heavy) == 1, "魔法行:轻甲 0 / 魔法护甲 −1 / 重甲 +1(加法核)") - # ③ 标错(类型化负向兑付)+ 标中正向兑付(封印命中只兑格面资源正向;Boss 行多轮样本;板真相直读保证 hit/miss 确定性) - var run1 = _run_seal_backlash_round(20260912) - var miss_rows := 0 - var negative_rows := 0 - var hit_rows := 0 - for row in run1.rows: - if row.get("type", "") == "seal_miss": - miss_rows += 1 - if String(row.get("source_class", "")) == "seal_miss" and String(row.get("type", "")) in ["effect", "damage"]: - negative_rows += 1 # 标错的负向兑付行(该安全格资源的负向字典行——effect/直伤) - if String(row.get("source_class", "")) == "seal_hit": - hit_rows += 1 # 标中的正向兑付行(该危险格资源的正向字典行——标中只走格面资源正向) - check(miss_rows >= 1, "标错封印产生 seal_miss 归因行(×%d)" % miss_rows) - check(negative_rows >= 1, "标错封印兑付该安全格资源的负向字典行(×%d——attack/defense/weaken 效果行或 heal 直伤行)" % negative_rows) - check(hit_rows >= 1, "封印命中兑付该危险格资源的正向字典行(×%d——充能退役后无魔法分量,正向=格面资源)" % hit_rows) - # ④ 同种子回放(极性路由零随机——同种子逐行相同) - var run2 = _run_seal_backlash_round(20260912) - check(JSON.stringify(run1.rows) == JSON.stringify(run2.rows), "标错惩罚轮回放断言:同种子步骤流逐行相同(类型化负向兑付确定性)") - - -# 构造轮:标 1 面"安全格封印"(必 miss → 该格资源负向兑付)+ 标 1 面"雷封印"(必 hit → 正向兑付 + 充能)后停手收轮。 -# 返回 sealed safe 格与其 cell_type(Ready 域内查询——类型化断言的数据面)。 -func _run_seal_backlash_round(seed_val: int) -> Dictionary: - var rng = load("res://core/rng_service.gd").new() - rng.set_master_seed(seed_val) - 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": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}): - return {"rows": rows, "safe_cell": Vector2i(-1, -1), "safe_type": ""} - var minesweeper = settlement.minesweeper - var safe_t := _find_seal_target(minesweeper, false) - var mine_t := _find_seal_target(minesweeper, true) - var safe_type := "" - if not safe_t.is_empty(): - var ident = settlement.query_reward_identity(safe_t.seal_cell) - safe_type = String(ident.get("cell_type", "")) if ident != null else "" - if safe_t.hero_cell != minesweeper.hero_pos(): - minesweeper.settle_batch([{"action": "move", "cell": safe_t.hero_cell}]) - minesweeper.settle_batch([{"action": "seal_place", "cell": safe_t.seal_cell}]) - if not mine_t.is_empty(): - if mine_t.hero_cell != minesweeper.hero_pos(): - minesweeper.settle_batch([{"action": "move", "cell": mine_t.hero_cell}]) - minesweeper.settle_batch([{"action": "seal_place", "cell": mine_t.seal_cell}]) - minesweeper.settle_batch([{"action": "stop"}]) - return {"rows": rows, "safe_cell": safe_t.get("seal_cell", Vector2i(-1, -1)), "safe_type": safe_type} # 板真相直读找封印目标:未开 + 指定雷/安全属性 + 存在可达已开邻格(测试允许读板真相——排雷玩法 冒烟同口径) diff --git a/game/tests/unit/battle_settlement/roll_multiplier_test.gd b/game/tests/unit/battle_settlement/roll_multiplier_test.gd index cbe3e939..f8969ccb 100644 --- a/game/tests/unit/battle_settlement/roll_multiplier_test.gd +++ b/game/tests/unit/battle_settlement/roll_multiplier_test.gd @@ -32,6 +32,7 @@ func _init() -> void: _smoke_enemy_attack_type() _smoke_armor_roll() _smoke_pool_wiring() + _smoke_damage_types() quit(Kit.finish("roll_multiplier_test")) @@ -332,3 +333,94 @@ func _hero_attack_row_with(opts: Dictionary, hero_atk_val: int, seed_val: int) - if String(row.get("type", "")) == "damage" and String(row.get("source_class", "")) == "hero_attack": return row return {} + + +# ---- 攻击类型 × 护甲克制 + 标错惩罚冒烟(护甲快照/克制矩阵/充能/反噬三选一)---- + +func _smoke_damage_types() -> void: + print("--- 攻击类型 × 护甲克制 + 标错惩罚 ---") + # ① 护甲类型快照传播(spawn 行值 → 单位字典) + var enemy_system = load("res://core/unit_container.gd").new(_db) + Kit.check("goblin 护甲 = light(轻甲——教学位)", + enemy_system.spawn(1, 0, int(_db.hero.max_hp)) and String(enemy_system.all_enemies()[0].armor_type) == "light") + var enemy_system_b = load("res://core/unit_container.gd").new(_db) + Kit.check("bugbear 护甲 = light(护甲类型重配批 2026-09-18:关卡 1 全轻甲)", + enemy_system_b.spawn(1, 2, int(_db.hero.max_hp)) and String(enemy_system_b.all_enemies()[0].armor_type) == "light") + var enemy_system_c = load("res://core/unit_container.gd").new(_db) + Kit.check("ogre_chief 护甲 = light(Boss 行集——护甲类型重配批 2026-09-18:关卡 1 全轻甲)", + enemy_system_c.spawn(1, 4, int(_db.hero.max_hp), [], -1, -1, [], "", "boss") and String(enemy_system_c.all_enemies()[0].armor_type) == "light") + # ② 克制矩阵表值在位(三档点数 克制 +1 / 中性 0 / 被克 −1,加法核非 percent) + var am: Dictionary = _db.settlement.armor_effectiveness + Kit.check("普通行:轻甲 0 / 魔法护甲 +1 / 重甲 −1(加法核)", + int(am.normal.light) == 0 and int(am.normal.ward) == 1 and int(am.normal.heavy) == -1) + Kit.check("穿刺行:轻甲 −1 / 魔法护甲 +1 / 重甲 0 中性(用户拍板)", + int(am.pierce.light) == -1 and int(am.pierce.ward) == 1 and int(am.pierce.heavy) == 0) + Kit.check("魔法行:轻甲 0 / 魔法护甲 −1 / 重甲 +1(加法核)", + int(am.magic.light) == 0 and int(am.magic.ward) == -1 and int(am.magic.heavy) == 1) + # ③ 标错(类型化负向兑付)+ 标中正向兑付(封印命中只兑格面资源正向;Boss 行多轮样本;板真相直读保证 hit/miss 确定性) + var run1 = _run_seal_backlash_round(20260912) + var miss_rows := 0 + var negative_rows := 0 + var hit_rows := 0 + for row in run1.rows: + if row.get("type", "") == "seal_miss": + miss_rows += 1 + if String(row.get("source_class", "")) == "seal_miss" and String(row.get("type", "")) in ["effect", "damage"]: + negative_rows += 1 # 标错的负向兑付行(该安全格资源的负向字典行——effect/直伤) + if String(row.get("source_class", "")) == "seal_hit": + hit_rows += 1 # 标中的正向兑付行(该危险格资源的正向字典行——标中只走格面资源正向) + Kit.check("标错封印产生 seal_miss 归因行(×%d)" % miss_rows, miss_rows >= 1) + Kit.check("标错封印兑付该安全格资源的负向字典行(×%d——attack/defense/weaken 效果行或 heal 直伤行)" % negative_rows, negative_rows >= 1) + Kit.check("封印命中兑付该危险格资源的正向字典行(×%d——充能退役后无魔法分量,正向=格面资源)" % hit_rows, hit_rows >= 1) + # ④ 同种子回放(极性路由零随机——同种子逐行相同) + var run2 = _run_seal_backlash_round(20260912) + Kit.check("标错惩罚轮回放断言:同种子步骤流逐行相同(类型化负向兑付确定性)", + JSON.stringify(run1.rows) == JSON.stringify(run2.rows)) + + +# 构造轮:标 1 面"安全格封印"(必 miss → 该格资源负向兑付)+ 标 1 面"雷封印"(必 hit → 正向兑付 + 充能)后停手收轮。 +# 返回 sealed safe 格与其 cell_type(Ready 域内查询——类型化断言的数据面)。 +func _run_seal_backlash_round(seed_val: int) -> Dictionary: + var rng = load("res://core/rng_service.gd").new() + rng.set_master_seed(seed_val) + 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": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}): + return {"rows": rows, "safe_cell": Vector2i(-1, -1), "safe_type": ""} + var minesweeper = settlement.minesweeper + var safe_t := _find_seal_target(minesweeper, false) + var mine_t := _find_seal_target(minesweeper, true) + var safe_type := "" + if not safe_t.is_empty(): + var ident = settlement.query_reward_identity(safe_t.seal_cell) + safe_type = String(ident.get("cell_type", "")) if ident != null else "" + if safe_t.hero_cell != minesweeper.hero_pos(): + minesweeper.settle_batch([{"action": "move", "cell": safe_t.hero_cell}]) + minesweeper.settle_batch([{"action": "seal_place", "cell": safe_t.seal_cell}]) + if not mine_t.is_empty(): + if mine_t.hero_cell != minesweeper.hero_pos(): + minesweeper.settle_batch([{"action": "move", "cell": mine_t.hero_cell}]) + minesweeper.settle_batch([{"action": "seal_place", "cell": mine_t.seal_cell}]) + minesweeper.settle_batch([{"action": "stop"}]) + return {"rows": rows, "safe_cell": safe_t.get("seal_cell", Vector2i(-1, -1)), "safe_type": safe_type} + + +# 板真相直读找封印目标:未开 + 指定雷/安全属性 + 存在可达已开邻格(测试允许读板真相——排雷玩法 冒烟同口径) +# (过渡副本——G5 四套仍消费,smoke_all.gd 原件保留;宿主侧一致副本承共享 helper 双份过渡形态,收尾批去重) +func _find_seal_target(minesweeper, want_mine: bool) -> Dictionary: + var revealed: Array = minesweeper.revealed_cells() + var revealed_set := {} + for c in revealed: + revealed_set[c] = true + for y in minesweeper.current_board().height: + for x in minesweeper.current_board().width: + var c := Vector2i(x, y) + if minesweeper.is_revealed(c) or minesweeper.has_seal(c): + continue + if minesweeper.current_board().is_mine(c) != want_mine: + continue + for n in minesweeper.current_board().neighbors8(c): + if revealed_set.has(n) and (minesweeper.compute_move_path(n).size() > 0 or n == minesweeper.hero_pos()): + return {"seal_cell": c, "hero_cell": n} + return {} diff --git a/reports/批B-断言迁移账本-2026-10-22.md b/reports/批B-断言迁移账本-2026-10-22.md index 56a10aa3..20166995 100644 --- a/reports/批B-断言迁移账本-2026-10-22.md +++ b/reports/批B-断言迁移账本-2026-10-22.md @@ -284,6 +284,7 @@ - `_smoke_enemy_attack_type`(4 条):4/4 验红。变异点——enemy.tres:26 goblin attack_type normal→pierce(红唯一 1/4,越轨行 goblin:pierce);unit_container.gd:110 快照注入硬编码空串(红 3/4——快照为载荷/矩阵共同源,条3 条4 连带属预期形态);damage_controller.gd:471 敌方技载荷 attack_type 置空串(红唯一 1/4);damage_controller.gd:577 `_counter_delta` 查表短路恒中性(红唯一 1/4)。 - `_smoke_armor_roll`(15 条):15/15 验红(条3 含一次无效首试换点,共 16 轮)。变异点——damage_math.gd:36 档位边界比较 `>`→`>=`(红「查档分布」双迭代+「EV 锚」连带——纯函数共面);settlement.tres roll_tier_discounts 75→74(红「EV 锚」1572≠1575,连带查档双迭代+AC11 落域);damage_controller.gd:540 暴击判定恒假(**首试无效变异**——「nat20 种子可达」只查掷点值==crit_roll 不查标记,判路面不被触及;该轮实际验红条4/条5 记为下两条的顺带证据)→换点 :539 roll 上界收窄 20 不可达(红「种子可达」seed=-1,连带条4/条5——种子扫描共同源);:375 hero 载荷 crit 标记写入恒假(红「crit 标记留档」红唯一 1/20);:369 暴击倍率传参 200→100(红「普通分量 ×2」红唯一);status_container.gd:52 续时刷新分支 pass 化(红「削弱续时」红唯一);:40 幅度快照 −1 偏移(红「削弱池读数=行值」红唯一);damage_math.gd:38 档值 +7 偏移出域(红「AC11 逐拍落域」,连带查档双迭代+EV 锚——纯函数共面;「折扣档流动」保持绿);damage_controller.gd:539 掷点折叠恒 nat 面(红「折扣档流动」——采样 distinct 恒 1 穷尽返空 ×0 拍连带「AC11 逐拍落域」);:515 board_blast 下限拆除恒 0(红「零 v=0 伤害行」红唯一——流中确有群伤行);:374 hero 载荷 tier 键 pass 化(红「掷点读数留档」红唯一);:504 board_typed 载荷塞 roll 键(红「棋盘行零掷点字段」红唯一);damage_math.gd counter_component 三点隔离变异:贴底特例 `sum==1→99`(红「贴底下限」红唯一)、负和特例 `sum<0→99`(红「触底穿透」红唯一)、空桶早退 `return 0→1`(红「空桶恒 0」红唯一)。 - `_smoke_pool_wiring`(4 条):4/4 验红。变异点——damage_controller.gd:271 hero 侧 atk_type 加 "_x" 后缀(红「单分量基线在位」——payload 分量键改名 normal 键失位;armor_roll 套「普通分量 ×2」同键消费连带属预期形态);status_effects.tres attack_up magnitude 50→40(红「增伤 ×1.5」140≠150 红唯一);sapped magnitude −25→−10(红「乏力 ×0.75」90≠75 红唯一);fortify magnitude −50→−20(红「硬化折半方向」80≠50 红唯一)。 +- `_smoke_damage_types`(10 条):10/10 验红(条3 含一次无效首试换点,共 11 轮)。变异点——enemy.tres goblin 行 armor_type light→heavy:27(红「goblin 护甲」+armor_roll「普通分量 ×2」连带——goblin 是 crit 探针攻击目标);bugbear 行 armor_type light→heavy:73(红唯一 1/34);ogre_chief 行 armor_type light→heavy:126(**首试无效变异**——该条 spawn 直呼第 9 参 "boss" 实落 hero_armor_type 形参、row_kind 缺省 battle(源套形态照迁),(1,4) battle 域 band_of ceil 落带命中 hobgoblin band3 行、ogre_chief 属 boss 域不在键域)→换点 hobgoblin 行 armor_type light→heavy:98(红唯一 1/34——实证解析落带);settlement.tres normal.light 0→1:33(红「普通行矩阵值」+套1 矩阵证词/armor_roll ×2 连带——同表源);pierce.heavy 0→1:40(红唯一);magic.ward −1→1:44(红唯一);grant_segment.gd:312 归因行类型 seal_miss→seal_mis(红「seal_miss 归因行」×0 红唯一);:316 标错负向路由 pass 化(红「负向字典行」×0 红唯一);:290 标中正向路由 pass 化(红「正向字典行」×0 红唯一);damage_controller.gd:539 armor_roll 流换全局未播种 randi_range(红「同种子回放逐行相同」——G3 同款确定性击破形态;套1 矩阵证词连带属预期)。 ## 十、支援线 9 项待裁闭合(2026-10-22 主会话裁)