Files
gplan-2/game/tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd
T
DKChan 9f21236f80 工程(注释标记词换代·oracle→判据出处): 用户 2026-11-07 令——判据出处注记的标记词改用项目已有中文名,避免 Oracle 被读成数据库。注释格式句、机检正则、标识符、卡片与登记同步换代;零行为改动。
改动面 112 文件 572+/572-:game/tests/** 与 game/probe/** 共 106 件 545 行 `# oracle:`→`# 判据出处:`;tools/capability_audit.py 正则字面量换中文+ORACLE_*→CRITERION_*/check_oracle_markers→check_criterion_markers/oracle_marked(flagged)→criterion_* 共 11 行;test-conventions.md:119 格式句+:155 措辞;待办清单与需求卡片-QA覆盖余量收口批各 1 处字面量;coverage_enum.py:21 与 camp_scene_layout_test.gd:101 旁支措辞;ui_click_probe.gd 对拍局部变量 oracle→expected 7 处;auto_player.gd 三处 safe_set 真值措辞。不动:历史审计报告两份、test-oracle-anchor 分支名、git 史、生成日志;指针格式/扫描面/判红条件/验收义务全不变。

读数(区内实跑):改前 capability_audit --strict「417 注记函数·红项 0」→改后「417 判据出处注记函数·红项 0」exit=0;新标记 545 行/106 文件,现行义务面 oracle 残留 0(在册沿革除外);diff 自检 game/ 非注释新增行仅 6 行且全在 ui_click_probe.gd 的 expected 变量段;112 改动文件 CRLF=0;py_compile tools/capability_audit.py OK;--docs exit=0【验收通过】无新增红;--gates exit=0(静态扫描绿、闸壳 bash -n ×13 失败 0)。
2026-11-07 07:11:15 +08:00

503 lines
13 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd — 结算接线同种子逐项 golden 对拍宿主(架构审查 5.1 封装修复批 R1 主证)
# 断言面 = 同种子定死驱动序下 step_row 全量行流 + settlement_snapshot 全量载荷 + battle_finished
# 载荷 + 收束 presentation_snapshot 关键键(round/battle_result/hero.hp/enemies.size()),
# 与 GOLDEN 常量逐位相等〔封装5.1 V1〕——段类束注入改造的零行为变更主证:
# 改前基线在未改树上固化(GOLDEN 为空时走转储通道取数),改后全程保持绿。
# 驱动序(卡面逐字定死):begin_battle(voucher_rap=20) → 轮1 choose_dig 移动/挖/封邻格/停 →
# player_request_refresh(换图刷新链入流)→ 轮2 一挖一停 → 轮3 径直停手。
# 协议照单元侧惯例:extends SceneTree+Kit.check 主通道;--script 模式无 autoload,
# ConfigDB 显式实例化构造注入+自建 RngService(掷点隔离形态)。
# 取数通道:GOLDEN 为空时打印捕获序列 JSON(两格缩进)后按专用码 3 退出——非判定码族(0 绿/1 红/2 空跑)。
# 运行:godot --headless --path game --script res://tests/unit/battle_settlement/settlement_wiring_snapshot_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://sim/auto_player.gd")
const TEST_SEED := 4242 # golden 固化种子(卡面定死——改前改后同种子对拍前提)
const DUMP_EXIT := 3 # 空跑转储专用退出码(GOLDEN 未固化态取数用)
# 改前基线(JSON 两格缩进单文件固化——与捕获序列 JSON.stringify 逐字相等即绿;空串=未固化→转储模式)
const GOLDEN := r"""
{
"final": {
"battle_result": "",
"enemies_n": 2,
"hero_hp": 21,
"round": 4
},
"finished": [],
"rows": [
{
"display_name": "迟滞(下轮行动点 -1)",
"effect_id": "ap_delta",
"origin_cell": [
1,
0
],
"pool_value": -1,
"round_index": 1,
"source_class": "mine_hit",
"type": "effect",
"value": -1
},
{
"display_name": "行动点(下轮行动点 +1)",
"effect_id": "ap_delta",
"highlight": "seal_hit",
"origin_cell": [
1,
1
],
"pool_value": 0,
"round_index": 1,
"source_class": "seal_hit",
"type": "effect",
"value": 1
},
{
"ac": 1,
"display_name": "主角攻击",
"killed": false,
"normal": 2,
"origin_cell": [
-1,
-1
],
"roll": 12,
"round_index": 1,
"source_class": "hero_attack",
"target": "地精一伙",
"target_id": "goblin",
"tier": 100,
"type": "damage",
"value": 2
},
{
"ac": 10,
"attack_type": "normal",
"baseline": 1,
"crit": true,
"display_name": "地精一伙 普通攻击(暴击)",
"origin_cell": [
-1,
-1
],
"roll": 20,
"round_index": 1,
"skill_id": "attack",
"source_class": "enemy_action",
"target": "hero",
"tier": 100,
"type": "damage",
"value": 2
},
{
"ac": 10,
"attack_type": "normal",
"baseline": 1,
"display_name": "地精一伙 普通攻击(被压制)",
"origin_cell": [
-1,
-1
],
"roll": 2,
"round_index": 1,
"skill_id": "attack",
"source_class": "enemy_action",
"target": "hero",
"tier": 25,
"type": "damage",
"value": 1
},
{
"display_name": "状态衰减",
"origin_cell": [
-1,
-1
],
"round_index": 1,
"source_class": "",
"type": "decay",
"value": 0
},
{
"display_name": "下一轮次",
"origin_cell": [
-1,
-1
],
"round_index": 1,
"source_class": "",
"type": "verdict",
"value": 0
},
{
"display_name": "暴走",
"effect_id": "enrage",
"origin_cell": [
0,
0
],
"pool_value": 25,
"round_index": 2,
"source_class": "mine_hit",
"type": "effect",
"value": 25
},
{
"ac": 1,
"display_name": "主角攻击",
"killed": true,
"normal": 2,
"origin_cell": [
-1,
-1
],
"roll": 2,
"round_index": 2,
"source_class": "hero_attack",
"target": "地精一伙",
"target_id": "goblin",
"tier": 100,
"type": "damage",
"value": 2
},
{
"ac": 10,
"attack_type": "normal",
"baseline": 1,
"display_name": "地精一伙 普通攻击(被压制)",
"origin_cell": [
-1,
-1
],
"roll": 1,
"round_index": 2,
"skill_id": "attack",
"source_class": "enemy_action",
"target": "hero",
"tier": 25,
"type": "damage",
"value": 1
},
{
"display_name": "状态衰减",
"origin_cell": [
-1,
-1
],
"round_index": 2,
"source_class": "",
"type": "decay",
"value": 1
},
{
"display_name": "下一轮次",
"origin_cell": [
-1,
-1
],
"round_index": 2,
"source_class": "",
"type": "verdict",
"value": 0
},
{
"ac": 1,
"display_name": "主角攻击",
"killed": false,
"normal": 2,
"origin_cell": [
-1,
-1
],
"roll": 8,
"round_index": 3,
"source_class": "hero_attack",
"target": "地精一伙",
"target_id": "goblin",
"tier": 100,
"type": "damage",
"value": 2
},
{
"ac": 10,
"attack_type": "normal",
"baseline": 1,
"display_name": "地精一伙 普通攻击(被压制)",
"origin_cell": [
-1,
-1
],
"roll": 6,
"round_index": 3,
"skill_id": "attack",
"source_class": "enemy_action",
"target": "hero",
"tier": 50,
"type": "damage",
"value": 1
},
{
"display_name": "状态衰减",
"origin_cell": [
-1,
-1
],
"round_index": 3,
"source_class": "",
"type": "decay",
"value": 0
},
{
"display_name": "下一轮次",
"origin_cell": [
-1,
-1
],
"round_index": 3,
"source_class": "",
"type": "verdict",
"value": 0
}
],
"snaps": [
{
"board": {
"h": 10,
"revealed": 42,
"seals": 1,
"seq": 1,
"w": 10
},
"buffs": [],
"depth": 1,
"economy": {
"ap": 0,
"phase": "Waiting",
"rap": 26,
"refresh_used": 0
},
"enemies": [
{
"armor": "light",
"armor_class": 1,
"atk": 1,
"hp": 2,
"id": "goblin",
"max_hp": 4,
"pending_skill": ""
},
{
"armor": "light",
"armor_class": 1,
"atk": 1,
"hp": 4,
"id": "goblin",
"max_hp": 4,
"pending_skill": ""
}
],
"hero": {
"armor_class": 10,
"atk": 2,
"hp": 23,
"max_hp": 26
},
"kind": "settlement",
"level": 1,
"round": 1,
"round_facts": {
"mines": 1,
"path": "stopped",
"safe": 0,
"seal_events": 1
},
"spawn_count": 2,
"unit_count": 2,
"verdict": "continue"
},
{
"board": {
"h": 10,
"revealed": 43,
"seals": 0,
"seq": 2,
"w": 10
},
"buffs": [],
"depth": 1,
"economy": {
"ap": 0,
"phase": "Waiting",
"rap": 25,
"refresh_used": 1
},
"enemies": [
{
"armor": "light",
"armor_class": 1,
"atk": 1,
"hp": 4,
"id": "goblin",
"max_hp": 4,
"pending_skill": ""
}
],
"hero": {
"armor_class": 10,
"atk": 2,
"hp": 22,
"max_hp": 26
},
"kind": "settlement",
"level": 1,
"round": 2,
"round_facts": {
"mines": 1,
"path": "stopped",
"safe": 42,
"seal_events": 0
},
"spawn_count": 2,
"unit_count": 1,
"verdict": "continue"
},
{
"board": {
"h": 10,
"revealed": 43,
"seals": 0,
"seq": 2,
"w": 10
},
"buffs": [],
"depth": 1,
"economy": {
"ap": 0,
"phase": "Waiting",
"rap": 33,
"refresh_used": 0
},
"enemies": [
{
"armor": "light",
"armor_class": 1,
"atk": 1,
"hp": 2,
"id": "goblin",
"max_hp": 4,
"pending_skill": ""
}
],
"hero": {
"armor_class": 10,
"atk": 2,
"hp": 21,
"max_hp": 26
},
"kind": "settlement",
"level": 1,
"round": 3,
"round_facts": {
"mines": 0,
"path": "stopped",
"safe": 0,
"seal_events": 0
},
"spawn_count": 2,
"unit_count": 1,
"verdict": "continue"
}
]
}
"""
var _db
func _init() -> void:
# 判据出处: design/gdd/systems/战斗循环/战斗结算.md:362 (结算接线同种子逐项 golden 对拍组)
_db = ConfigDBScript.new()
var load_ok: bool = _db.load_tables("res://data/tables", true)
Kit.check("前置:表加载零 error", load_ok and _db.diagnostics_count("error") == 0)
var orch = load("res://core/battle_orchestrator.gd").new(_db, _make_rng())
var rows: Array = []
var snaps: Array = []
var finished: Array = []
orch.step_row.connect(func(row: Dictionary) -> void: rows.append(row))
orch.settlement_snapshot.connect(func(s: Dictionary) -> void: snaps.append(s))
orch.battle_finished.connect(func(p: Dictionary) -> void: finished.append(p))
var accepted: bool = orch.begin_battle({"level_index": 1, "node_depth": 1,
"hero_hp": int(_db.combat.hero.max_hp), "voucher_rap": 20})
Kit.check("前置:begin_battle 受理", accepted)
if accepted:
_drive(orch)
var final_snap: Dictionary = orch.presentation_snapshot()
var captured := {"rows": rows, "snaps": snaps, "finished": finished,
"final": {"round": int(final_snap.get("round", -1)),
"battle_result": String(final_snap.get("battle_result", "")),
"hero_hp": int((final_snap.get("hero", {}) as Dictionary).get("hp", -1)),
"enemies_n": (final_snap.get("enemies", []) as Array).size()}}
if GOLDEN == "":
print("[golden-dump] begin")
print(JSON.stringify(captured, " "))
print("[golden-dump] end")
quit(DUMP_EXIT)
return
# GOLDEN 三引号字面量首尾各带一个换行(多行字符串语法载体)——对拍两侧 strip 首尾空白后逐位比
var captured_json := JSON.stringify(captured, " ").strip_edges()
var golden_json := GOLDEN.strip_edges()
if captured_json != golden_json:
print("[captured-dump] begin")
print(JSON.stringify(captured, " "))
print("[captured-dump] end")
Kit.check("封装5.1 V1 同种子逐项对拍:行流/快照/终局载荷/presentation 逐位同 golden",
captured_json == golden_json)
quit(Kit.finish("settlement_wiring_snapshot_test"))
func _make_rng():
var rng = load("res://core/rng_service.gd").new()
rng.set_master_seed(TEST_SEED)
return rng
# 驱动序(卡面定死):轮1 挖+封+停 → 刷新 → 轮2 一挖一停 → 轮3 停手
func _drive(orch) -> void:
var ms = orch.minesweeper
# 轮 1:choose_dig 非空则 move→dig→封首个未揭示主角邻格→stop
var pick: Dictionary = AutoPlayer.choose_dig(ms)
if not pick.is_empty() and pick.get("dig_cell", null) != null:
ms.settle_batch([{"action": "move", "cell": pick["hero_cell"]}])
ms.settle_batch([{"action": "dig", "cell": pick["dig_cell"]}])
var c := _first_unrevealed_adjacent(orch)
if c.x >= 0:
ms.settle_batch([{"action": "seal_place", "cell": c}])
ms.settle_batch([{"action": "stop"}])
# 轮 1 结算后刷新(voucher_rap=20 保证 RAP 抵折后价——覆盖换图刷新链)
orch.player_request_refresh()
# 轮 2:一挖一停
var pick2: Dictionary = AutoPlayer.choose_dig(ms)
if not pick2.is_empty() and pick2.get("dig_cell", null) != null:
ms.settle_batch([{"action": "move", "cell": pick2["hero_cell"]}])
ms.settle_batch([{"action": "dig", "cell": pick2["dig_cell"]}])
ms.settle_batch([{"action": "stop"}])
# 轮 3:径直停手
ms.settle_batch([{"action": "stop"}])
# 首个未揭示且与主角 8 邻接的格(卡面定死封印目标选取;无候选=返回 (-1,-1) 跳过封印)
func _first_unrevealed_adjacent(orch) -> Vector2i:
var ms = orch.minesweeper
var hero: Vector2i = ms.hero_pos()
var board = ms.current_board()
for y in board.height:
for x in board.width:
var c := Vector2i(x, y)
if not ms.is_revealed(c) and board.neighbors8(hero).has(c):
return c
return Vector2i(-1, -1)