195 lines
10 KiB
GDScript
195 lines
10 KiB
GDScript
# tests/unit/presentation/player_copy_sweep_test.gd — 玩家面文案清扫直建断言宿主(迭代36 卡A)
|
||
# 断言面 = 玩家面字符串字面量换代钉值(u1–u13 钉值表——分类词/渠道旧话/「雷」族禁字清零):
|
||
# ① hero 面板两段头(专长/属性值提升——同屏分类撞车消除)
|
||
# ② 奖励节点三选一头部(冒险牌库/渠道分流三机制事实,零「同权重/同上限/提升池」旧话)
|
||
# ③ 专长卡兜底(feat_card_text 空行兜底「专长\n???」)
|
||
# ④ 图例封印行(标空/受挖掘惩罚,零「插错/挖雷」)
|
||
# ⑤ 状态槽来源名(mine_hit→危险格——tooltip 消费半)
|
||
# ⑥ 结算来源名(settlement 表值经 ConfigDB 真装载+battle_log 行消费半)
|
||
# ⑦ 已开危险格字形(glyph「危」+plate+RED_MINE——BoardView rebuild 真实刷新路径)
|
||
# ⑧ 源 token 扫描一:board_view.gd 零 "雷" 字面量(哨兵族统一钉住)
|
||
# ⑩ 源 token 扫描二:battle_orchestrator.gd 玩家面字符串字面量零「挖雷」(注释剥离=行首 # 判)
|
||
# 扫域口径:玩家面字符串字面量(契约 2 保留面——注释/内部常量名与注记/测试消息串不入域)。
|
||
# 协议照单元侧惯例:extends SceneTree+Kit.check 主通道;--script 模式无 autoload,
|
||
# ConfigDB 显式实例化构造注入(统一流 master 种子 42 沿源)。
|
||
# 运行:godot --headless --path game --script res://tests/unit/presentation/player_copy_sweep_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") # 自动玩家策略单一实现(夹具开域驱动)
|
||
|
||
var _db
|
||
|
||
|
||
func _init() -> void:
|
||
_db = ConfigDBScript.new()
|
||
var load_ok: bool = _db.load_tables("res://data/tables", true)
|
||
if not load_ok:
|
||
push_error("[player_copy_sweep_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])
|
||
_sweep_hero_panel()
|
||
_sweep_narrator()
|
||
_sweep_legend()
|
||
_sweep_source_names()
|
||
_sweep_board_glyph()
|
||
_sweep_token_scans()
|
||
_sweep_refresh_toast_no_literals()
|
||
quit(Kit.finish("player_copy_sweep_test"))
|
||
|
||
|
||
# ① C 区两段头(u1/u2——直建 hero 面板,adventure_feats_test ⑦c 族同构)
|
||
func _sweep_hero_panel() -> void:
|
||
var panel = load("res://presentation/views/hero_info_panel_view.gd").new(_db.presentation.battle_layout)
|
||
var feat_head := _first_label(panel._feat_section)
|
||
var curio_head := _first_label(panel._curio_section)
|
||
Kit.check("C 区专长段头==「专长」(u1 换代——段头文案钉值)〔wa1〕",
|
||
feat_head != null and feat_head.text == "专长")
|
||
Kit.check("C 区属性值提升段头==「属性值提升」(u2 换代——同屏两段同标撞车消除)〔wa1〕",
|
||
curio_head != null and curio_head.text == "属性值提升")
|
||
|
||
|
||
# ②③ 奖励节点头部+专长卡兜底(u3/u11——静态直调)
|
||
func _sweep_narrator() -> void:
|
||
var narrator = load("res://presentation/battle_narrator.gd")
|
||
var head := String(narrator.node_curio_header())
|
||
Kit.check("奖励点头部含「冒险牌库」(u3 机制事实①同库)〔wa4〕", head.contains("冒险牌库"))
|
||
Kit.check("奖励点头部含「属性值提升与补给」(u3 机制事实②渠道分流)〔wa4〕", head.contains("属性值提升与补给"))
|
||
for w in ["同权重", "同上限", "提升池"]:
|
||
Kit.check("奖励点头部零「%s」旧话(u3 清零)〔wa4〕" % w, not head.contains(w))
|
||
Kit.check("专长卡兜底==「专长\n???」(u11 换代——数据缺行真实兜底路径)〔wa3〕",
|
||
String(narrator.feat_card_text({})) == "专长\n???")
|
||
|
||
|
||
# ④ 图例封印行(u4——直建 LegendOverlay 读 ENTRIES 常量)
|
||
func _sweep_legend() -> void:
|
||
load("res://presentation/views/legend_overlay.gd").new() # 直建(_init(rap_cost) 现签名)
|
||
var seal_row: Array = []
|
||
for e in load("res://presentation/views/legend_overlay.gd").ENTRIES:
|
||
if String(e[1]) == "封印":
|
||
seal_row = e
|
||
break
|
||
Kit.check("图例封印行含「标空」(u4——与「标中」成对)〔wa5〕", String(seal_row[2] if seal_row.size() > 2 else "").contains("标空"))
|
||
Kit.check("图例封印行含「受挖掘惩罚」(u4 雷字清零换代)〔wa5〕", String(seal_row[2] if seal_row.size() > 2 else "").contains("受挖掘惩罚"))
|
||
for w in ["插错", "挖雷"]:
|
||
Kit.check("图例封印行零「%s」〔wa5〕" % w,
|
||
not String(seal_row[2] if seal_row.size() > 2 else "").contains(w))
|
||
|
||
|
||
# ⑤⑥ 来源名双源(u5/u6——状态槽消费半+表值 ConfigDB 真装载+battle_log 行消费半)
|
||
func _sweep_source_names() -> void:
|
||
var row_view = load("res://presentation/views/status_slot_row.gd").new()
|
||
row_view.set_effects([{"effect_id": "alert", "source_class": "mine_hit",
|
||
"display_name": "危险格受击", "remaining_settlements": 2, "round_index": 1}])
|
||
var slot: PanelContainer = null
|
||
for child in row_view.get_children():
|
||
if child is PanelContainer and child.has_meta("eff"):
|
||
slot = child
|
||
break
|
||
Kit.check("状态槽 mine_hit 效果行在座(wa6 夹具前提)〔wa6〕", slot != null)
|
||
if slot != null:
|
||
row_view._on_slot_hover(slot)
|
||
Kit.check("状态槽来源注记==「危险格」(u5 换代——meta tooltip 消费半)〔wa6〕",
|
||
row_view._tip_label.text.contains("来源:危险格"))
|
||
row_view._hide_tip()
|
||
Kit.check("结算表 source_label_map[mine_hit]==「危险格」(u6 换代——ConfigDB 真装载)〔wa7〕",
|
||
String(_db.combat.settlement.core.source_label_map.get("mine_hit", "")) == "危险格")
|
||
var log_view = load("res://presentation/views/battle_log_view.gd").new(_db, _db.presentation.battle_layout)
|
||
var row_txt := String(log_view._format_row({"type": "effect", "source_class": "mine_hit",
|
||
"display_name": "挖掘反噬", "value": -5, "pool_value": -5, "round_index": 1}))
|
||
Kit.check("战斗日志 mine_hit 来源行含「(危险格)」(u6 消费半——_format_row 生产路径)〔wa7〕",
|
||
row_txt.contains("(危险格)"))
|
||
|
||
|
||
# ⑦⑧ 已开危险格字形族(u7/u8/u9——BoardView rebuild 真实刷新路径+源 token 扫描)
|
||
func _sweep_board_glyph() -> void:
|
||
var board_generator = load("res://core/board_generator.gd").new()
|
||
var b = board_generator.generate(1, 0, _mk_stream("board_gen", 4242), _db)
|
||
var ae = load("res://core/action_economy.gd").new(_db)
|
||
var ms = load("res://core/minesweeper_rules.gd").new(_db, ae)
|
||
ms.install_board(b)
|
||
ae.notify_round_start()
|
||
# 邻接雷定位(挖掘面规则=须邻接英雄——novice 驱动开域至危险格入挖掘射程;上限 60 步防冻态)
|
||
var mine_cell := Vector2i(-1, -1)
|
||
var glow_steps := 0
|
||
while glow_steps < 60:
|
||
for c in b.neighbors8(ms.hero_pos()):
|
||
if b.is_mine(c) and not ms.is_revealed(c):
|
||
mine_cell = c
|
||
break
|
||
if mine_cell.x >= 0:
|
||
break
|
||
var act: Dictionary = AutoPlayer.novice_action(ms)
|
||
if String(act.get("kind", "stop")) == "stop":
|
||
break
|
||
var op := String(act.get("kind", ""))
|
||
if op == "seal":
|
||
op = "seal_place"
|
||
ms.settle_batch([{"action": op, "cell": act.get("cell")}])
|
||
glow_steps += 1
|
||
Kit.check("棋盘夹具含挖掘射程内未开危险格(wa8 前提——实测 %d 步)〔wa8〕" % glow_steps, mine_cell.x >= 0)
|
||
if mine_cell.x >= 0:
|
||
ms.settle_batch([{"action": "dig", "cell": mine_cell}]) # 挖开危险格(真实规则路径——受击开格)
|
||
var view = load("res://presentation/views/board_view.gd").new()
|
||
view.rebuild(b, ms) # theme_projection_test.gd:200 先例式直建(缺素材程序化回退态——fresh checkout 冒烟同纪律)
|
||
var u: Node2D = view._cells[mine_cell]
|
||
Kit.check("已开危险格 glyph==「危」(u7 换代)〔wa8〕", u.glyph == "危")
|
||
Kit.check("已开危险格 plate==true(u9 牌载哨兵包含项随动)〔wa8〕", u.plate == true)
|
||
Kit.check("已开危险格 glyph_color==RED_MINE(u7 墨色不随字形换代漂移)〔wa8〕",
|
||
u.glyph_color == UiKit.RED_MINE)
|
||
|
||
|
||
# ⑧⑩ 源 token 扫描(注释剥离=行首 # 判——契约 2 保留面不入域)
|
||
func _sweep_token_scans() -> void:
|
||
var board_src := FileAccess.get_file_as_string("res://presentation/views/board_view.gd")
|
||
Kit.check("board_view.gd 源零 \"雷\" 字面量(u7/u8/u9 哨兵族统一钉住)〔wa8〕",
|
||
board_src.count("\"雷\"") == 0)
|
||
var orch_src := FileAccess.get_file_as_string("res://core/battle_orchestrator.gd")
|
||
var orch_hits := 0
|
||
for line in orch_src.split("\n"):
|
||
if line.strip_edges().begins_with("#"):
|
||
continue
|
||
if line.contains("挖雷"):
|
||
orch_hits += 1
|
||
Kit.check("battle_orchestrator.gd 玩家面字符串字面量零「挖雷」(u13——注释保留面不入域)〔wa12〕",
|
||
orch_hits == 0)
|
||
|
||
|
||
# 刷新两 toast 文案零数字字面量(架构整改 R4——格式串零数字、插值位与表字段一一对应;
|
||
# 静态判据+评审注记观测面局限:真机文案随表值由实机目检兜——T10 清单两项)
|
||
func _sweep_refresh_toast_no_literals() -> void:
|
||
var main_src := FileAccess.get_file_as_string("res://presentation/main.gd")
|
||
var rap_toast := ""
|
||
var cap_toast := ""
|
||
for line in main_src.split("
|
||
"):
|
||
if "弃图刷新需" in line:
|
||
rap_toast = line
|
||
if "本轮刷新次数已用完" in line:
|
||
cap_toast = line
|
||
Kit.check("V4 前置:两 toast 行在场(源锚)", rap_toast != "" and cap_toast != "")
|
||
# RAP 不足 toast:格式串零数字字面量(「需 %d」插值读 rap_refresh_cost 表值)
|
||
var rap_fmt_bad := "(弃图刷新需 8)" in rap_toast or "%d 点(弃图刷新需 %d)" not in rap_toast
|
||
Kit.check("RAP 不足 toast:格式串零数字字面量、插值位=cost 表值拼接〔架构整改 V4〕", not rap_fmt_bad)
|
||
# 轮上限 toast:文案零数字字面量(「每轮 %d 次」插值读 refresh_cap_per_round 表值)
|
||
Kit.check("轮上限 toast:零数字字面量、插值位=refresh_cap_per_round 表值拼接〔架构整改 V4〕",
|
||
"(每轮 2 次)" not in cap_toast and "每轮 %d 次" in cap_toast)
|
||
|
||
|
||
func _first_label(node: Node) -> Label:
|
||
for child in node.get_children():
|
||
if child is Label:
|
||
return child
|
||
return null
|
||
|
||
|
||
func _mk_stream(stream_name: String, scope: int) -> RandomNumberGenerator:
|
||
# stream_name = 调用点自述标签(不参与派生),保留参数零改调用方(theme_projection_test 同构)
|
||
var rg := RandomNumberGenerator.new()
|
||
var svc = load("res://core/rng_service.gd").new()
|
||
svc.set_master_seed(42)
|
||
rg.seed = svc.derive_at(scope)
|
||
return rg
|