test(迁移): 批 B 波 1 G4 套2 护甲掷点十五条迁入 roll_multiplier_test.gd——剪切挂载绿(宿主 20 检查)+变异 15/15 验红(条3 首试无效换 roll 上界收窄已记账);套内独占 _first_hero_attack_rows/_weaken_refresh_probe/_ac11_tier_samples 随迁,_run_battle/_auto_play_round 过渡副本(去 G6 专属 Idle 断言,原件保留);smoke_all.gd 同批删函数三 helper 调用行;闸01 剩余面 998 绿
This commit is contained in:
1 parent
fdb77ffeea
commit
58054819ca
3 files changed
+199
-146
No files matched your search
@@ -153,7 +153,6 @@ var _rng
|
||||
func _run_core_smoke(db, rng) -> void:
|
||||
_db = db
|
||||
_rng = rng
|
||||
_smoke_armor_roll()
|
||||
_smoke_pool_wiring()
|
||||
_smoke_type_glow()
|
||||
_smoke_variant_placement(1)
|
||||
@@ -1399,151 +1398,6 @@ func _smoke_level() -> void:
|
||||
|
||||
|
||||
|
||||
# ---- 护甲等级掷点·行为面(折扣档分布 / nat20 暴击 / 伤害下限 / 棋盘来源必中)----
|
||||
|
||||
func _smoke_armor_roll() -> void:
|
||||
print("--- 护甲等级掷点(查档纯函数 + 暴击 + 伤害下限 + AC12 分布 + 棋盘必中)---")
|
||||
# ① 查档纯函数:全掷点域逐位对照(AC10 / AC12)+ EV 锚(AC10 期望减免 21.25% —— registry 参考数)
|
||||
var expect_ac10 := {100: 11, 75: 3, 50: 4, 25: 2} # 掷 10-20 全额 / 7-9 / 3-6 / 1-2
|
||||
var expect_ac12 := {100: 9, 75: 3, 50: 4, 25: 4} # 掷 12-20 全额 / 9-11 / 5-8 / 1-4
|
||||
for cfg in [{"ac": 10, "expect": expect_ac10}, {"ac": 12, "expect": expect_ac12}]:
|
||||
var dist := {100: 0, 75: 0, 50: 0, 25: 0}
|
||||
var tier_ok := true
|
||||
for roll in range(1, 21):
|
||||
var d: int = DamageMath.roll_discount_percent(roll, cfg.ac, _db.settlement.roll_tier_bounds, _db.settlement.roll_tier_discounts)
|
||||
if not dist.has(d):
|
||||
tier_ok = false
|
||||
continue
|
||||
dist[d] += 1
|
||||
check(tier_ok and dist == cfg.expect, "查档 AC%d 全掷点域分布(100/75/50/25 = %s)" % [cfg.ac, str([dist[100], dist[75], dist[50], dist[25]])])
|
||||
var ev_sum := 0
|
||||
for roll in range(1, 21):
|
||||
ev_sum += DamageMath.roll_discount_percent(roll, 10, _db.settlement.roll_tier_bounds, _db.settlement.roll_tier_discounts)
|
||||
check(ev_sum == 1575, "AC10 EV 锚:期望系数合计 %d/2000 = 期望减免 21.25%%(nat20 恒全额含内)" % ev_sum)
|
||||
# ② nat20 暴击(行为面):探针按行为面扫描
|
||||
# 「主角先手首掷 = 20」的种子,不假设 armor_roll scope0 位
|
||||
var crit_seed := -1
|
||||
var crit_rows := {}
|
||||
for s in range(1, 800):
|
||||
crit_rows = _first_hero_attack_rows(s)
|
||||
if int(crit_rows.get("roll", -1)) == int(_db.settlement.crit_roll):
|
||||
crit_seed = s
|
||||
break
|
||||
check(crit_seed > 0, "nat20 种子可达(seed=%d——主角先手首掷命中 %d)" % [crit_seed, int(_db.settlement.crit_roll)])
|
||||
check(int(crit_rows.get("roll", -1)) == int(_db.settlement.crit_roll) and bool(crit_rows.get("crit", false)),
|
||||
"nat20 → 行留档 roll=20 + crit 标记(主角先手普通分量)")
|
||||
check(int(crit_rows.get("normal", -1)) == 2 * int(_db.hero.atk) and int(crit_rows.get("value", -1)) == 2 * int(_db.hero.atk),
|
||||
"nat20 → 普通分量 ×2(%d = 2×%d,暴击倍率只乘该分量,穿刺/魔法不吃)" % [int(crit_rows.get("normal", -1)), int(_db.hero.atk)])
|
||||
# ③ 削弱续时(行为面):授予→衰减一轮→再授予 → 层数保持 1、剩余结算数重置
|
||||
# (削弱为续时语义,无叠层通道)
|
||||
var weaken_probe := _weaken_refresh_probe()
|
||||
check(int(weaken_probe.get("layers", -1)) == 1 and int(weaken_probe.get("remaining", -1)) == 2,
|
||||
"削弱重复授予 → 层数保持 1、剩余结算数重置 2(续时语义——结构性消饱和)")
|
||||
check(int(weaken_probe.get("pool", -999)) == int(_db.status_effect_row("weaken").magnitude),
|
||||
"削弱单层池读数 = 行值(enemy_output 池单层贡献——×2 叠满触底 ×0 不可达,免伤轮根治)")
|
||||
# ④ AC11 折扣档分布(行为面):ac_up 逐轮授给(时效 1)→ Boss 行攻击拍逐拍判定
|
||||
# (ac_up 层数恒 1——局内可达 AC 上限 10+1=11,探针随行取值)
|
||||
var tiers := _ac11_tier_samples()
|
||||
var tier_distinct := {}
|
||||
var tiers_valid := true
|
||||
for t in tiers:
|
||||
tier_distinct[int(t)] = true
|
||||
if not [100, 75, 50, 25].has(int(t)):
|
||||
tiers_valid = false
|
||||
check(tiers_valid and tiers.size() >= 3, "AC11 攻击拍逐拍判定(×%d 拍全部落档域 {100,75,50,25})" % tiers.size())
|
||||
check(tier_distinct.size() >= 2, "折扣档实际流动(AC11 实测档位 %s——≥2 档可见)" % str(tier_distinct.keys()))
|
||||
# ⑤ 棋盘来源必中 + 吃掷点行留档(全战斗流扫描)
|
||||
var run = _run_battle(1, 0)
|
||||
var board_ok := true
|
||||
var attack_ok := true
|
||||
var all_positive := true
|
||||
for row in run.stream:
|
||||
if String(row.get("type", "")) != "damage":
|
||||
continue
|
||||
if int(row.get("value", 0)) <= 0:
|
||||
all_positive = false
|
||||
var src := String(row.get("source_class", ""))
|
||||
if src == "hero_attack" or src == "enemy_action":
|
||||
if not (row.has("roll") and row.has("ac") and row.has("tier")):
|
||||
attack_ok = false
|
||||
elif row.has("roll"):
|
||||
board_ok = false # 棋盘来源(爆裂/直伤/清障炸药)必中全额——不掷不留档
|
||||
check(all_positive, "全战斗流零 v=0 伤害行(伤害下限全局生效)")
|
||||
check(attack_ok, "hero_attack/enemy_action 伤害行掷点读数+档位随行留档(P2 归因)")
|
||||
check(board_ok, "棋盘来源伤害行零掷点字段(爆裂/直伤/清障炸药/奖励即时必中全额)")
|
||||
# ⑥ 全来源伤害下限(任何伤害落地取值域 [1,+∞) 无上限——棋盘克制直伤/反噬/地形变异/爆裂
|
||||
# 全部收在 damage_floor 闸内)
|
||||
var df: int = int(_db.settlement.damage_floor)
|
||||
check(DamageMath.counter_component(2, -1, df) == 1, "棋盘克制直伤贴底 → 下限 1(点数 2 vs 不利修正 −1)")
|
||||
check(DamageMath.counter_component(1, -3, df) == 1, "克制修正触底穿透 → 下限 1(点数 1 vs 修正 −3)")
|
||||
check(DamageMath.counter_component(0, 5, df) == 0, "空桶不吃修正恒 0(零点数分量无伤害可落地——边界不变不受下限影响)")
|
||||
|
||||
|
||||
# 定位种子下首场战斗第一行主角攻击伤害行(停手轮——零授予零充能,普通分量 = 攻击基线裸值)
|
||||
func _first_hero_attack_rows(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": 0, "hero_hp": int(_db.hero.max_hp)}):
|
||||
return {}
|
||||
settlement.minesweeper.settle_batch([{"action": "stop"}])
|
||||
for row in rows:
|
||||
if String(row.get("type", "")) == "damage" and String(row.get("source_class", "")) == "hero_attack":
|
||||
return row
|
||||
return {}
|
||||
|
||||
|
||||
# 削弱续时探测:
|
||||
# 授予 → 衰减一轮 → 再授予,读 weaken 实例投影 {layers, remaining, pool}。
|
||||
# 直授通道(来源格真实坐标惯例——沿前例);depth 2 板(D2 熊地精)仅作宿主,行为与敌种无关。
|
||||
func _weaken_refresh_probe() -> Dictionary:
|
||||
var rng = load("res://core/rng_service.gd").new()
|
||||
rng.set_master_seed(20260916)
|
||||
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng)
|
||||
if not settlement.begin_battle({"level_index": 1, "node_depth": 2, "hero_hp": int(_db.hero.max_hp)}):
|
||||
return {}
|
||||
var src: Dictionary = {"source_class": "mine_hit", "origin_cell": Vector2i(0, 0), "round_index": settlement.round_index}
|
||||
settlement._status_effects.apply_effect("weaken", src)
|
||||
settlement._status_effects.decay_round() # remaining 2 → 1(先兑付后衰减——只借衰减器,不结算)
|
||||
settlement._status_effects.apply_effect("weaken", src) # 续时:remaining ← 2,层数不动
|
||||
for eff in settlement._status_effects.query_active("enemy"):
|
||||
if String(eff.effect_id) == "weaken":
|
||||
# 走伤害控制器公有算术面 clamped_pool
|
||||
return {"layers": int(eff.layers), "remaining": int(eff.remaining_settlements), "pool": int(settlement._damage.clamped_pool("enemy_output"))}
|
||||
return {}
|
||||
|
||||
|
||||
# AC11 折扣档采样:每停手轮前直授 ac_up ×1(时效 1 逐轮衰减)——敌拍逐拍判定留档。
|
||||
# (ac_up 只续时不叠层、层数恒 1——采样上限取 AC11;
|
||||
# 技能出招为摇号制——扫描种子取首个 ≥2 档样本集。)
|
||||
func _ac11_tier_samples() -> Array:
|
||||
for seed_offset in range(20):
|
||||
var rng = load("res://core/rng_service.gd").new()
|
||||
rng.set_master_seed(20260917 + 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": 1, "node_depth": 2, "hero_hp": int(_db.hero.max_hp)}):
|
||||
continue
|
||||
var guard := 12
|
||||
while settlement.battle_result == "" and guard > 0:
|
||||
guard -= 1
|
||||
settlement._status_effects.apply_effect("ac_up", {"source_class": "safe_cell", "origin_cell": Vector2i(0, 0), "round_index": settlement.round_index})
|
||||
settlement.minesweeper.settle_batch([{"action": "stop"}])
|
||||
var tiers: Array = []
|
||||
for row in rows:
|
||||
if String(row.get("type", "")) == "damage" and String(row.get("source_class", "")) == "enemy_action":
|
||||
tiers.append(int(row.get("tier", -1)))
|
||||
var distinct := {}
|
||||
for t in tiers:
|
||||
distinct[int(t)] = true
|
||||
if tiers.size() >= 3 and distinct.size() >= 2:
|
||||
return tiers
|
||||
return []
|
||||
|
||||
|
||||
# ---- 主角乘区接线 + 硬化折半方向 ----
|
||||
# 同种子停手轮恒同掷点(效果只改乘区、不动掷点流)→ 落地伤害精确关系:
|
||||
# 增伤 = 基线 ×1.5、乏力 = ×0.75(hero_offense 池乘攻击合计 = 单分量,无直乘旁路);
|
||||
|
||||
@@ -13,6 +13,7 @@ extends SceneTree
|
||||
|
||||
const Kit := preload("res://tests/unit/test_kit.gd")
|
||||
const ConfigDBScript := preload("res://core/config_db.gd")
|
||||
const AutoPlayer := preload("res://smoke/auto_player.gd") # 自动玩家策略单一实现(迁移期按现路径 preload——账本 §五夹具前提)
|
||||
|
||||
var _db
|
||||
|
||||
@@ -29,6 +30,7 @@ func _init() -> void:
|
||||
push_error(" [%s] %s: %s" % [d.severity, d.category, d.message])
|
||||
# 套执行序沿源编排序
|
||||
_smoke_enemy_attack_type()
|
||||
_smoke_armor_roll()
|
||||
quit(Kit.finish("roll_multiplier_test"))
|
||||
|
||||
|
||||
@@ -91,3 +93,199 @@ func _smoke_enemy_attack_type() -> void:
|
||||
Kit.check("克制矩阵命中证词:同种子三甲型实测 heavy %d == light %d − 1 · ward %d == light %d + 1(迭代 24 N3 全局平移 −1/0/+1)" % [
|
||||
t_heavy, t_light, t_ward, t_light], matrix_found and t_light >= 2)
|
||||
Kit.check("敌方伤害技单据载荷 attack_type 非空(= %s——第 9 参传值面,迭代 24 N3)" % payload_at, payload_at == "normal")
|
||||
|
||||
|
||||
# ---- 护甲等级掷点·行为面(折扣档分布 / nat20 暴击 / 伤害下限 / 棋盘来源必中)----
|
||||
|
||||
func _smoke_armor_roll() -> void:
|
||||
print("--- 护甲等级掷点(查档纯函数 + 暴击 + 伤害下限 + AC12 分布 + 棋盘必中)---")
|
||||
# ① 查档纯函数:全掷点域逐位对照(AC10 / AC12)+ EV 锚(AC10 期望减免 21.25% —— registry 参考数)
|
||||
var expect_ac10 := {100: 11, 75: 3, 50: 4, 25: 2} # 掷 10-20 全额 / 7-9 / 3-6 / 1-2
|
||||
var expect_ac12 := {100: 9, 75: 3, 50: 4, 25: 4} # 掷 12-20 全额 / 9-11 / 5-8 / 1-4
|
||||
for cfg in [{"ac": 10, "expect": expect_ac10}, {"ac": 12, "expect": expect_ac12}]:
|
||||
var dist := {100: 0, 75: 0, 50: 0, 25: 0}
|
||||
var tier_ok := true
|
||||
for roll in range(1, 21):
|
||||
var d: int = DamageMath.roll_discount_percent(roll, cfg.ac, _db.settlement.roll_tier_bounds, _db.settlement.roll_tier_discounts)
|
||||
if not dist.has(d):
|
||||
tier_ok = false
|
||||
continue
|
||||
dist[d] += 1
|
||||
Kit.check("查档 AC%d 全掷点域分布(100/75/50/25 = %s)" % [cfg.ac, str([dist[100], dist[75], dist[50], dist[25]])], tier_ok and dist == cfg.expect)
|
||||
var ev_sum := 0
|
||||
for roll in range(1, 21):
|
||||
ev_sum += DamageMath.roll_discount_percent(roll, 10, _db.settlement.roll_tier_bounds, _db.settlement.roll_tier_discounts)
|
||||
Kit.check("AC10 EV 锚:期望系数合计 %d/2000 = 期望减免 21.25%%(nat20 恒全额含内)" % ev_sum, ev_sum == 1575)
|
||||
# ② nat20 暴击(行为面):探针按行为面扫描
|
||||
# 「主角先手首掷 = 20」的种子,不假设 armor_roll scope0 位
|
||||
var crit_seed := -1
|
||||
var crit_rows := {}
|
||||
for s in range(1, 800):
|
||||
crit_rows = _first_hero_attack_rows(s)
|
||||
if int(crit_rows.get("roll", -1)) == int(_db.settlement.crit_roll):
|
||||
crit_seed = s
|
||||
break
|
||||
Kit.check("nat20 种子可达(seed=%d——主角先手首掷命中 %d)" % [crit_seed, int(_db.settlement.crit_roll)], crit_seed > 0)
|
||||
Kit.check("nat20 → 行留档 roll=20 + crit 标记(主角先手普通分量)",
|
||||
int(crit_rows.get("roll", -1)) == int(_db.settlement.crit_roll) and bool(crit_rows.get("crit", false)))
|
||||
Kit.check("nat20 → 普通分量 ×2(%d = 2×%d,暴击倍率只乘该分量,穿刺/魔法不吃)" % [int(crit_rows.get("normal", -1)), int(_db.hero.atk)],
|
||||
int(crit_rows.get("normal", -1)) == 2 * int(_db.hero.atk) and int(crit_rows.get("value", -1)) == 2 * int(_db.hero.atk))
|
||||
# ③ 削弱续时(行为面):授予→衰减一轮→再授予 → 层数保持 1、剩余结算数重置
|
||||
# (削弱为续时语义,无叠层通道)
|
||||
var weaken_probe := _weaken_refresh_probe()
|
||||
Kit.check("削弱重复授予 → 层数保持 1、剩余结算数重置 2(续时语义——结构性消饱和)",
|
||||
int(weaken_probe.get("layers", -1)) == 1 and int(weaken_probe.get("remaining", -1)) == 2)
|
||||
Kit.check("削弱单层池读数 = 行值(enemy_output 池单层贡献——×2 叠满触底 ×0 不可达,免伤轮根治)",
|
||||
int(weaken_probe.get("pool", -999)) == int(_db.status_effect_row("weaken").magnitude))
|
||||
# ④ AC11 折扣档分布(行为面):ac_up 逐轮授给(时效 1)→ Boss 行攻击拍逐拍判定
|
||||
# (ac_up 层数恒 1——局内可达 AC 上限 10+1=11,探针随行取值)
|
||||
var tiers := _ac11_tier_samples()
|
||||
var tier_distinct := {}
|
||||
var tiers_valid := true
|
||||
for t in tiers:
|
||||
tier_distinct[int(t)] = true
|
||||
if not [100, 75, 50, 25].has(int(t)):
|
||||
tiers_valid = false
|
||||
Kit.check("AC11 攻击拍逐拍判定(×%d 拍全部落档域 {100,75,50,25})" % tiers.size(), tiers_valid and tiers.size() >= 3)
|
||||
Kit.check("折扣档实际流动(AC11 实测档位 %s——≥2 档可见)" % str(tier_distinct.keys()), tier_distinct.size() >= 2)
|
||||
# ⑤ 棋盘来源必中 + 吃掷点行留档(全战斗流扫描)
|
||||
var run = _run_battle(1, 0)
|
||||
var board_ok := true
|
||||
var attack_ok := true
|
||||
var all_positive := true
|
||||
for row in run.stream:
|
||||
if String(row.get("type", "")) != "damage":
|
||||
continue
|
||||
if int(row.get("value", 0)) <= 0:
|
||||
all_positive = false
|
||||
var src := String(row.get("source_class", ""))
|
||||
if src == "hero_attack" or src == "enemy_action":
|
||||
if not (row.has("roll") and row.has("ac") and row.has("tier")):
|
||||
attack_ok = false
|
||||
elif row.has("roll"):
|
||||
board_ok = false # 棋盘来源(爆裂/直伤/清障炸药)必中全额——不掷不留档
|
||||
Kit.check("全战斗流零 v=0 伤害行(伤害下限全局生效)", all_positive)
|
||||
Kit.check("hero_attack/enemy_action 伤害行掷点读数+档位随行留档(P2 归因)", attack_ok)
|
||||
Kit.check("棋盘来源伤害行零掷点字段(爆裂/直伤/清障炸药/奖励即时必中全额)", board_ok)
|
||||
# ⑥ 全来源伤害下限(任何伤害落地取值域 [1,+∞) 无上限——棋盘克制直伤/反噬/地形变异/爆裂
|
||||
# 全部收在 damage_floor 闸内)
|
||||
var df: int = int(_db.settlement.damage_floor)
|
||||
Kit.check("棋盘克制直伤贴底 → 下限 1(点数 2 vs 不利修正 −1)", DamageMath.counter_component(2, -1, df) == 1)
|
||||
Kit.check("克制修正触底穿透 → 下限 1(点数 1 vs 修正 −3)", DamageMath.counter_component(1, -3, df) == 1)
|
||||
Kit.check("空桶不吃修正恒 0(零点数分量无伤害可落地——边界不变不受下限影响)", DamageMath.counter_component(0, 5, df) == 0)
|
||||
|
||||
|
||||
# 定位种子下首场战斗第一行主角攻击伤害行(停手轮——零授予零充能,普通分量 = 攻击基线裸值)
|
||||
func _first_hero_attack_rows(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": 0, "hero_hp": int(_db.hero.max_hp)}):
|
||||
return {}
|
||||
settlement.minesweeper.settle_batch([{"action": "stop"}])
|
||||
for row in rows:
|
||||
if String(row.get("type", "")) == "damage" and String(row.get("source_class", "")) == "hero_attack":
|
||||
return row
|
||||
return {}
|
||||
|
||||
|
||||
# 削弱续时探测:
|
||||
# 授予 → 衰减一轮 → 再授予,读 weaken 实例投影 {layers, remaining, pool}。
|
||||
# 直授通道(来源格真实坐标惯例——沿前例);depth 2 板(D2 熊地精)仅作宿主,行为与敌种无关。
|
||||
func _weaken_refresh_probe() -> Dictionary:
|
||||
var rng = load("res://core/rng_service.gd").new()
|
||||
rng.set_master_seed(20260916)
|
||||
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng)
|
||||
if not settlement.begin_battle({"level_index": 1, "node_depth": 2, "hero_hp": int(_db.hero.max_hp)}):
|
||||
return {}
|
||||
var src: Dictionary = {"source_class": "mine_hit", "origin_cell": Vector2i(0, 0), "round_index": settlement.round_index}
|
||||
settlement._status_effects.apply_effect("weaken", src)
|
||||
settlement._status_effects.decay_round() # remaining 2 → 1(先兑付后衰减——只借衰减器,不结算)
|
||||
settlement._status_effects.apply_effect("weaken", src) # 续时:remaining ← 2,层数不动
|
||||
for eff in settlement._status_effects.query_active("enemy"):
|
||||
if String(eff.effect_id) == "weaken":
|
||||
# 走伤害控制器公有算术面 clamped_pool
|
||||
return {"layers": int(eff.layers), "remaining": int(eff.remaining_settlements), "pool": int(settlement._damage.clamped_pool("enemy_output"))}
|
||||
return {}
|
||||
|
||||
|
||||
# AC11 折扣档采样:每停手轮前直授 ac_up ×1(时效 1 逐轮衰减)——敌拍逐拍判定留档。
|
||||
# (ac_up 只续时不叠层、层数恒 1——采样上限取 AC11;
|
||||
# 技能出招为摇号制——扫描种子取首个 ≥2 档样本集。)
|
||||
func _ac11_tier_samples() -> Array:
|
||||
for seed_offset in range(20):
|
||||
var rng = load("res://core/rng_service.gd").new()
|
||||
rng.set_master_seed(20260917 + 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": 1, "node_depth": 2, "hero_hp": int(_db.hero.max_hp)}):
|
||||
continue
|
||||
var guard := 12
|
||||
while settlement.battle_result == "" and guard > 0:
|
||||
guard -= 1
|
||||
settlement._status_effects.apply_effect("ac_up", {"source_class": "safe_cell", "origin_cell": Vector2i(0, 0), "round_index": settlement.round_index})
|
||||
settlement.minesweeper.settle_batch([{"action": "stop"}])
|
||||
var tiers: Array = []
|
||||
for row in rows:
|
||||
if String(row.get("type", "")) == "damage" and String(row.get("source_class", "")) == "enemy_action":
|
||||
tiers.append(int(row.get("tier", -1)))
|
||||
var distinct := {}
|
||||
for t in tiers:
|
||||
distinct[int(t)] = true
|
||||
if tiers.size() >= 3 and distinct.size() >= 2:
|
||||
return tiers
|
||||
return []
|
||||
|
||||
|
||||
# ---- 全战斗回放驱动(过渡副本——G6 属主件 `_run_battle`/`_auto_play_round` 的宿主侧一致副本:
|
||||
# armor_roll 棋盘来源必中扫描需真实流驱动而 G6 宿主未建;smoke_all.gd 原件保留供未迁套消费,
|
||||
# 收尾批与原件去重。副本去 G6 专属「收束相位回 Idle」断言——该条随 `_run_battle` 本体迁其宿主,
|
||||
# 本副本保持零断言纯驱动形态)----
|
||||
func _run_battle(L: int, D: int, axes: Dictionary = {}) -> Dictionary:
|
||||
# 每遍全新 RngService(同 master 种子)——回放断言的隔离前提(地图战斗资源 AC"两套全新 RngService+地图战斗资源"同款)
|
||||
var rng = load("res://core/rng_service.gd").new()
|
||||
rng.set_master_seed(42)
|
||||
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng)
|
||||
var out := {"stream": [], "events": [], "rounds": 0, "result": "", "terminated": false}
|
||||
settlement.step_row.connect(func(row): out.stream.append(row))
|
||||
settlement.battle_finished.connect(func(payload): out.result = payload.get("result", ""))
|
||||
settlement.minesweeper.cell_revealed.connect(func(evt): out.events.append(evt))
|
||||
settlement.minesweeper.seal_judged.connect(func(evt): out.events.append(evt))
|
||||
settlement.minesweeper.round_end_board.connect(func(p): out.events.append({"ev": "round_end_board", "path_tag": p.path_tag}))
|
||||
settlement.action_economy.round_end_stopped.connect(func(p): out.events.append({"ev": "round_end_stopped", "path_tag": p.path_tag}))
|
||||
var enc := {"level_index": L, "node_depth": D, "hero_hp": _db.hero.max_hp}
|
||||
enc.merge(axes, true) # 两根类型轴注入尾参(缺省零注入=回退面)
|
||||
if not settlement.begin_battle(enc):
|
||||
out.stream.append({"type": "verdict", "value": "begin_failed"})
|
||||
return out
|
||||
var max_rounds := 40
|
||||
while out.result == "" and out.rounds < max_rounds:
|
||||
out.rounds += 1
|
||||
_auto_play_round(settlement, out)
|
||||
if out.result == "":
|
||||
out.result = "timeout"
|
||||
out.terminated = out.result == "victory" or out.result == "defeat"
|
||||
return out
|
||||
|
||||
|
||||
func _auto_play_round(settlement, out: Dictionary) -> void:
|
||||
var minesweeper = settlement.minesweeper
|
||||
var action_economy = settlement.action_economy
|
||||
var start_round: int = settlement.round_index
|
||||
# 确定性自动玩家:挖最近的已探明邻接未开格;无可挖则停手(结算窗口由信号回调提交)
|
||||
var guard := 400
|
||||
while action_economy.phase == "Active" and settlement.round_index == start_round and guard > 0 and out.result == "":
|
||||
guard -= 1
|
||||
var plan := AutoPlayer.choose_dig(minesweeper)
|
||||
if plan.is_empty():
|
||||
break
|
||||
if plan.hero_cell != minesweeper.hero_pos():
|
||||
minesweeper.settle_batch([{"action": "move", "cell": plan.hero_cell}])
|
||||
if action_economy.phase != "Active" or settlement.round_index != start_round:
|
||||
break
|
||||
minesweeper.settle_batch([{"action": "dig", "cell": plan.dig_cell}])
|
||||
if action_economy.phase == "Active" and settlement.round_index == start_round and out.result == "":
|
||||
minesweeper.settle_batch([{"action": "stop"}])
|
||||
@@ -282,6 +282,7 @@
|
||||
逐套变异记账(每条独立一轮:改坏→跑新宿主→红→`git checkout` 还原→`git status` 清洁确认):
|
||||
|
||||
- `_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」红唯一)。
|
||||
|
||||
## 十、支援线 9 项待裁闭合(2026-10-22 主会话裁)
|
||||
|
||||
|
||||
Reference in new issue
Block a user