From 5ed1ec99af53fe0c759d5adefda9f120cfc827dd Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 23 Oct 2026 03:38:50 +0800 Subject: [PATCH] =?UTF-8?q?test(=E8=BF=81=E7=A7=BB):=20=E6=89=B9=20B=20?= =?UTF-8?q?=E6=B3=A2=201=20G5=20=E5=A5=971=20=E6=9C=AB=E7=82=B9=20AP=20?= =?UTF-8?q?=E5=B0=81=E5=8D=B0=E5=9B=9E=E5=BD=92=E4=BA=94=E6=9D=A1=E8=BF=81?= =?UTF-8?q?=E5=85=A5=E6=96=B0=E5=BB=BA=E5=AE=BF=E4=B8=BB=20seal=5Fpolarity?= =?UTF-8?q?=5Ftest.gd=E2=80=94=E2=80=94=E6=96=B0=E5=AE=BF=E4=B8=BB?= =?UTF-8?q?=E8=90=BD=E4=BD=8D=EF=BC=8Bmanifest=20=E7=99=BB=E8=AE=B0?= =?UTF-8?q?=EF=BC=8B=E5=89=AA=E5=88=87=E6=8C=82=E8=BD=BD=E7=BB=BF=EF=BC=88?= =?UTF-8?q?=E5=AE=BF=E4=B8=BB=205=20=E6=A3=80=E6=9F=A5=EF=BC=89=EF=BC=8B?= =?UTF-8?q?=E6=8C=89=E6=9D=A1=E5=8F=98=E5=BC=82=205/5=20=E9=AA=8C=E7=BA=A2?= =?UTF-8?q?=EF=BC=88=E5=8F=97=E7=90=86=E5=AE=88=E5=8D=AB=E6=81=92=E6=8B=92?= =?UTF-8?q?/dig=20=E6=89=A3=E8=B4=B9+1/revealed=5Fcells=20=E7=BD=AE?= =?UTF-8?q?=E7=A9=BA/seal=20=E8=AE=A1=E8=B4=B9=2099/try=5Fspend=20?= =?UTF-8?q?=E8=80=97=E5=B0=BD=E4=BB=B2=E8=A3=81=E6=81=92=E5=81=87=E5=90=84?= =?UTF-8?q?=E9=AA=8C=E7=BA=A2=EF=BC=89=EF=BC=9Bsmoke=5Fall.gd=20=E5=90=8C?= =?UTF-8?q?=E6=89=B9=E5=88=A0=E5=87=BD=E6=95=B0=E8=8A=82=E5=A4=B4=E5=9D=97?= =?UTF-8?q?=E4=B8=8E=E8=B0=83=E7=94=A8=E8=A1=8C=EF=BC=9B=E9=97=B801=20?= =?UTF-8?q?=E5=89=A9=E4=BD=99=E9=9D=A2=20976=20=E7=BB=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/smoke/smoke_all.gd | 37 ---------- game/tests/run_all.gd | 1 + .../battle_settlement/seal_polarity_test.gd | 74 +++++++++++++++++++ reports/批B-断言迁移账本-2026-10-22.md | 7 ++ 4 files changed, 82 insertions(+), 37 deletions(-) create mode 100644 game/tests/unit/battle_settlement/seal_polarity_test.gd diff --git a/game/smoke/smoke_all.gd b/game/smoke/smoke_all.gd index ede2345c..2fc0c92c 100644 --- a/game/smoke/smoke_all.gd +++ b/game/smoke/smoke_all.gd @@ -160,7 +160,6 @@ func _run_core_smoke(db, rng) -> void: _smoke_full_battle(1, 0, "L1 · 地精") _smoke_full_battle(2, 0, "L2 · 熊地精") _smoke_level() - _smoke_seal_ap_exhausted() _smoke_active_refresh() _smoke_playtest_logger() _smoke_battle_log_view() @@ -1322,42 +1321,6 @@ func _ms_digest(ms, ae) -> Array: return [[int(ms.hero_pos().x), int(ms.hero_pos().y)], rev, seals, ae.ap, ae.phase, ms.seal_count()] -# ---- 排雷玩法 封印动作耗尽末点 AP 回归(playtest 缺陷②——v1 冒烟盲区:自动玩家不用封印)---- - -func _smoke_seal_ap_exhausted() -> void: - print("--- 排雷玩法·封印动作耗尽末点 AP 回归 ---") - var rng = load("res://core/rng_service.gd").new() - rng.set_master_seed(20260911) - var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng) - var rounds: Array = [] - settlement.round_started.connect(func(ri): rounds.append(ri)) - check(settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"}), "回归战斗启动(Boss 行——D0 20HP 级会被盲挖粉尘秒杀,无法承载封印回归场景)") - var minesweeper = settlement.minesweeper - # 恰花 (AP-1) 点挖掘,留末点给封印(表驱动——AP 从表读不写死) - var ap_total: int = int(_db.action_economy.ap_per_round) - for i in range(ap_total - 1): - if settlement.action_economy.phase != "Active" or settlement.action_economy.ap == 0: - break - 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}]) - minesweeper.settle_batch([{"action": "dig", "cell": plan.dig_cell}]) - check(settlement.action_economy.phase == "Active" and settlement.action_economy.ap == 1, "%d 次挖掘后恰剩 1 AP(phase=%s ap=%d)" % [ap_total - 1, str(settlement.action_economy.phase), settlement.action_economy.ap]) - # 末点 AP 花在封印——轮次必须终结并结算进入第 2 轮 - # 封印目标 = 挖掘策略的下一目标格(构造保证:未开、与小人邻接——先把小人移到其邻接已开格) - var plan2 = AutoPlayer.choose_dig(minesweeper) - check(not plan2.is_empty(), "仍存在可挖目标(封印目标来源)") - var out: Array = [] - if not plan2.is_empty(): - if plan2.hero_cell != minesweeper.hero_pos(): - minesweeper.settle_batch([{"action": "move", "cell": plan2.hero_cell}]) - out = minesweeper.settle_batch([{"action": "seal_place", "cell": plan2.dig_cell}]) - check(not out.is_empty() and bool(out[0].accepted), "末点 AP 封印被受理") - check(rounds.size() >= 2, "封印动作耗尽 AP 触发轮末结算并进入第 2 轮(rounds=%s)" % str(rounds)) - - # ---- 关卡节点地图·线性推进冒烟---- func _smoke_level() -> void: diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index e8571156..8c2336f7 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -60,6 +60,7 @@ func _suites() -> Array: {"path": "res://tests/unit/battle_settlement/damage_math_test.gd", "expect": 0}, {"path": "res://tests/unit/battle_settlement/damage_controller_test.gd", "expect": 0}, {"path": "res://tests/unit/battle_settlement/roll_multiplier_test.gd", "expect": 0}, # 掷点与乘区宿主(敌攻击类型轴/护甲掷点行为面/乘区接线/伤害类型克制与标错/fresh 池攻击链——批 B 自 smoke_all 迁入) + {"path": "res://tests/unit/battle_settlement/seal_polarity_test.gd", "expect": 0}, # 封印与极性宿主(末点 AP 封印/封印不可逆四情况/清图奖励双份与弱形式/刷新 provenance 守卫/极性路由与封印溢价/负向字典/格面资源资格——批 B 自 smoke_all 迁入) {"path": "res://tests/unit/battle_settlement/skill_unlock_test.gd", "expect": 0}, # Boss 半血解锁技·容器机制(门谓词/闩不可逆/双面过滤/保底不逼锁定/零扰动) {"path": "res://tests/unit/contracts/system_contracts_test.gd", "expect": 0}, {"path": "res://tests/unit/presentation/ui_font_asset_test.gd", "expect": 0}, diff --git a/game/tests/unit/battle_settlement/seal_polarity_test.gd b/game/tests/unit/battle_settlement/seal_polarity_test.gd new file mode 100644 index 00000000..01846251 --- /dev/null +++ b/game/tests/unit/battle_settlement/seal_polarity_test.gd @@ -0,0 +1,74 @@ +# tests/unit/battle_settlement/seal_polarity_test.gd — 封印与极性单元测试宿主 +# 断言面 = 八套(批 B 断言迁移自 smoke_all.gd,G5 施工组;八套按源编排序分八笔原子提交迁入): +# 末点 AP 封印轮末结算(playtest 缺陷②回归——action_economy 末点花费与轮终结) +# 封印不可逆四情况(禁撤封印 + 挖封印格兑付改判——minesweeper_rules 拒绝面) +# 清图奖励双份兑付链(真实流完美封印——orchestrator grant_clear 身份兑付) +# 清图奖励弱形式分支(零兑付 / 高光守卫 / 第二份拒——直调编排私有面构造) +# 轮内刷新 provenance 守卫(刷新图清图不发清图奖励) +# 极性路由 + 封印溢价 125%(挖安全格正向 / 挖危险格负向 / 标中标空兑付极性) +# 负向字典逐型直验(效果行 / heal 直伤 / blast 警觉 / action 行动点排队) +# 格面资源资格面(揭示事件 resource 标记 + 封锁格身份查询) +# 协议照单元侧惯例:extends SceneTree+Kit.check 主通道;--script 模式无 autoload, +# ConfigDB 显式实例化构造注入(各套自建 RngService——源套掷点隔离形态,宿主不持统一流); +# 表加载失败不中止(沿被迁断言面的源语义——诊断在此展开,表值变异的红由具体断言落,承 G2/G3/G4 同款)。 +# 运行:godot --headless --path game --script res://tests/unit/battle_settlement/seal_polarity_test.gd +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 + + +func _init() -> void: + _db = ConfigDBScript.new() + # 表加载失败不中止(承 G2/G3/G4 同款:表损坏的红落在具体断言上—— + # 诊断在此展开,后续断言按坏表实况逐条验红;collect_only=true 下成员照常赋值可评) + var load_ok: bool = _db.load_tables("res://data/tables", true) + if not load_ok: + push_error("[seal_polarity_test] 表加载返回失败——诊断如下(红由具体断言落)") + for d in _db.diagnostics(): + if d.severity == "error" or d.severity == "fatal": + push_error(" [%s] %s: %s" % [d.severity, d.category, d.message]) + # 套执行序沿源编排序 + _smoke_seal_ap_exhausted() + quit(Kit.finish("seal_polarity_test")) + + +# ---- 末点 AP 封印轮末结算(playtest 缺陷②回归:末点 AP 花在封印必须终结轮次进入下一轮)---- + +func _smoke_seal_ap_exhausted() -> void: + print("--- 排雷玩法·封印动作耗尽末点 AP 回归 ---") + var rng = load("res://core/rng_service.gd").new() + rng.set_master_seed(20260911) + var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng) + var rounds: Array = [] + settlement.round_started.connect(func(ri): rounds.append(ri)) + Kit.check("回归战斗启动(Boss 行——D0 20HP 级会被盲挖粉尘秒杀,无法承载封印回归场景)", + settlement.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "node_type": "boss"})) + var minesweeper = settlement.minesweeper + # 恰花 (AP-1) 点挖掘,留末点给封印(表驱动——AP 从表读不写死) + var ap_total: int = int(_db.action_economy.ap_per_round) + for i in range(ap_total - 1): + if settlement.action_economy.phase != "Active" or settlement.action_economy.ap == 0: + break + 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}]) + minesweeper.settle_batch([{"action": "dig", "cell": plan.dig_cell}]) + Kit.check("%d 次挖掘后恰剩 1 AP(phase=%s ap=%d)" % [ap_total - 1, str(settlement.action_economy.phase), settlement.action_economy.ap], + settlement.action_economy.phase == "Active" and settlement.action_economy.ap == 1) + # 末点 AP 花在封印——轮次必须终结并结算进入第 2 轮 + # 封印目标 = 挖掘策略的下一目标格(构造保证:未开、与小人邻接——先把小人移到其邻接已开格) + var plan2 = AutoPlayer.choose_dig(minesweeper) + Kit.check("仍存在可挖目标(封印目标来源)", not plan2.is_empty()) + var out: Array = [] + if not plan2.is_empty(): + if plan2.hero_cell != minesweeper.hero_pos(): + minesweeper.settle_batch([{"action": "move", "cell": plan2.hero_cell}]) + out = minesweeper.settle_batch([{"action": "seal_place", "cell": plan2.dig_cell}]) + Kit.check("末点 AP 封印被受理", not out.is_empty() and bool(out[0].accepted)) + Kit.check("封印动作耗尽 AP 触发轮末结算并进入第 2 轮(rounds=%s)" % str(rounds), rounds.size() >= 2) diff --git a/reports/批B-断言迁移账本-2026-10-22.md b/reports/批B-断言迁移账本-2026-10-22.md index 8fdaf749..fc3691a6 100644 --- a/reports/批B-断言迁移账本-2026-10-22.md +++ b/reports/批B-断言迁移账本-2026-10-22.md @@ -291,6 +291,13 @@ 施工发现(供后续组参考,接 G3 序号):⑧ 剪切块必须以「节头/函数头至下一函数头」整块为界、删后必跑闸 01——本轮套5 首剪以行号配平差一行,游离 check 行残留 Parse Error(发现⑦同源再现:调用/断言行残留只有跑才现形);⑨ 直呼生产函数的变异定位先核签名——`spawn(..., "", "boss")` 九参实落 hero_armor_type 形参、row_kind 缺省 battle(套4 条3 首试无效根因),行集解析按 battle 域 band_of ceil 落带、boss 域行不在键域。 +**G5 单元·封印与极性 → game/tests/unit/battle_settlement/seal_polarity_test.gd:八套 72 条逐套原子提交(沿 G3/G4 组内按套提交)。** +挂载终态=宿主 `_init` 自建 `_db`(ConfigDB 构造注入,`load_tables(..., true)` 失败不中止承 G2/G3/G4 同款);各套自建 RngService(源套掷点隔离形态)。helper 归属:`_find_seal_target/_run_full_clear/_drive_clear/_rows_at/_rows_of_type/_has_source_row/_has_effect_id/_run_polarity_probe/_polarity_probe_accepts`+常量 `POSITIVE_EFFECTS/NEGATIVE_EFFECTS/CLEAR_SMOKE_DEPTH/CLEAR_SMOKE_HERO_HP`+成员 `_regular_clear_reward_rows` 均为 G5 组内独占(消费面普查实证),随首用套迁入宿主、smoke_all.gd 原件在该 helper 最后一个 smoke 侧消费者迁走的那笔同批删;`_run_battle/_auto_play_round`(G6 属主)双份过渡——宿主持零断言纯驱动一致副本(G4 同款,副本去 G6 专属 Idle 断言)、smoke_all.gd 原件保留。`AutoPlayer` 按现路径 `res://smoke/auto_player.gd` preload;`RewardTable` 全局 class_name 裸用(G3 先例)。manifest 登记随套1 commit。 + +逐套变异记账(每条独立一轮:改坏→跑新宿主→红→`git checkout` 还原→`git status` 清洁确认): + +- `_smoke_seal_ap_exhausted`(5 条):5/5 验红。变异点——battle_orchestrator.gd:304 begin_battle 受理守卫 `!= 0`→`>= 0` 恒拒(红 5/5——开局受理为全套共同入口,连带属自然半径);action_economy.gd:128 dig 扣费 `_ap -= cost`→`-= cost + 1`(红「恰剩 1 AP」红唯一 1/5——宿主挖掘循环 break 守卫下实测 ap 停 2,封印受理与轮末不受扰);minesweeper_rules.gd:59 `revealed_cells()` 置空 `return []`(红「仍存在可挖目标」4/5——choose_dig 共同源,挖掘循环首轮 break 致条2/4/5 连带属预期形态);action_economy.gd:73 `_cost` seal_place 计费 `return ae.seal_place_ap_cost`→`return 99`(红「末点 AP 封印被受理」2/5——计费付不起被拒,条5 连带=受理失败轮末不触发属预期);action_economy.gd:149 `_spend_and_arbitrate` 耗尽仲裁 `elif _ap == 0`→`elif _ap < 0` 恒假(红「触发轮末结算进第 2 轮」红唯一 1/5——seal 走 try_spend 路径仲裁、dig 走 `_spend_cost_and_arbitrate` 另一处不动,隔离干净)。 + ## 十、支援线 9 项待裁闭合(2026-10-22 主会话裁) 1. `extract_checks.py` **常驻保留**——承担清单维护义务(test-conventions §9.3 锚)的再提取/diff 职能;其 DEFAULT_SRC 悬空问题列入波 2:默认源改为三宿主(game/tests/unit、integration、system_test.gd)遍历,或显式路径参数,波 3 出口对账用显式模式。