# 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() 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() 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("属性值提升与补给")) Kit.check("奖励点头部含「同库分流」(u3 机制事实②措辞)〔wa4〕", head.contains("同库分流")) Kit.check("奖励点头部零「同权重」旧话(u3 清零)〔wa4〕", not head.contains("同权重")) Kit.check("奖励点头部零「同上限」旧话(u3 清零)〔wa4〕", not head.contains("同上限")) Kit.check("奖励点头部零「提升池」旧话(u3 清零)〔wa4〕", not head.contains("提升池")) 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("图例封印行存在(wa5 夹具前提)〔wa5〕", not seal_row.is_empty()) 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("受挖掘惩罚")) Kit.check("图例封印行零「插错」(u4 旗族遗留词干清零)〔wa5〕", not String(seal_row[2] if seal_row.size() > 2 else "").contains("插错")) Kit.check("图例封印行零「挖雷」(u4 雷字清零)〔wa5〕", not String(seal_row[2] if seal_row.size() > 2 else "").contains("挖雷")) # ⑤⑥ 来源名双源(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.settlement.core.source_label_map.get("mine_hit", "")) == "危险格") var log_view = load("res://presentation/views/battle_log_view.gd").new(_db) 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) 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