From f9f2cb2d27a63f98d55a61dfd2407ea290b79014 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 4 Nov 2026 05:08:32 +0800 Subject: [PATCH] =?UTF-8?q?iter51-=E5=8D=A13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/core/node_map_generator.gd | 3 +- game/tests/run_all.gd | 1 + .../unit/core/encapsulation_p2_sweep_test.gd | 76 +++++++++++++++++++ .../core/encapsulation_p2_sweep_test.gd.uid | 1 + 4 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 game/tests/unit/core/encapsulation_p2_sweep_test.gd create mode 100644 game/tests/unit/core/encapsulation_p2_sweep_test.gd.uid diff --git a/game/core/node_map_generator.gd b/game/core/node_map_generator.gd index bcf3044e..56505126 100644 --- a/game/core/node_map_generator.gd +++ b/game/core/node_map_generator.gd @@ -7,7 +7,6 @@ class_name NodeMapGenerator extends RefCounted -const NODE_TYPES := ["battle", "boss", "reward", "rest", "shop"] const FILLER_TYPES := ["battle", "reward", "rest"] # 多终点图的支路互斥段必须各有独有类型(契约 5③),而商店与 Boss 是每条支路共有件, # 独有类型只能来自 battle/reward/rest 三件 ⇒ 归属大岔口各区域的填充节点取互斥签名类型。 @@ -133,7 +132,7 @@ func _vo_node_fields(nodes: Array, errs: Array, by_id: Dictionary, indeg: Dictio if by_id.has(id): errs.append({"category": "dup_key", "message": "节点句柄重复: %d" % id}) by_id[id] = n - if not NODE_TYPES.has(String(n["type"])): + if not LevelTable.NODE_TYPES.has(String(n["type"])): errs.append({"category": "enum", "message": "节点类型出域: %s" % n["type"]}) if int(n["depth"]) < 0: errs.append({"category": "domain", "message": "节点深度为负: %d" % n["depth"]}) diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index 53cf5d8e..92a10f55 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -228,6 +228,7 @@ func _suites() -> Array: {"path": "res://tests/unit/core/feat_vouchers_test.gd", "expect": 0}, # 凭证五件+留任二件(V9 路由逐件/F-1 存量账/V10 留任路由+AP 属性轴) {"path": "res://tests/unit/core/settle_segments_test.gd", "expect": 0}, # 结算五段 Segment(V4:束构造/编排器接线/真实一轮走段序) {"path": "res://tests/unit/core/encapsulation_sweep_test.gd", "expect": 0}, # 五处结构性零命中扫描〔封装5.1 V2–V7〕(架构审查 5.1 封装修复——七段对编排器零引用/五方法零残留/断点存档公开面/效果累积面/配置挂载面,非空前置+正锚) + {"path": "res://tests/unit/core/encapsulation_p2_sweep_test.gd", "expect": 0}, # NODE_TYPES 词表单点化结构性扫描〔架构P2 V1/V2〕(架构审查 P2 代码面——生成器本地副本归零+schema 唯一定点,非空前置+正锚) {"path": "res://tests/unit/core/save_slot_isolation_test.gd", "expect": 0}, # 卡① 三槽位间隔离/进档失败恢复/宿主链引用同一(sv1/sv5/sv9/sv10) {"path": "res://tests/unit/core/save_boot_no_activate_test.gd", "expect": 0}, # 卡① 启动态类级契约:无激活态/拒写盘面零触碰/越界拒绝(sv2/sv3) {"path": "res://tests/unit/core/save_slot_summary_test.gd", "expect": 0}, # 卡① 摘要三项按槽窥视:异值/空槽/坏槽三态/零副作用(sv7/sv8) diff --git a/game/tests/unit/core/encapsulation_p2_sweep_test.gd b/game/tests/unit/core/encapsulation_p2_sweep_test.gd new file mode 100644 index 00000000..5bfa81e5 --- /dev/null +++ b/game/tests/unit/core/encapsulation_p2_sweep_test.gd @@ -0,0 +1,76 @@ +# tests/unit/core/encapsulation_p2_sweep_test.gd — 架构审查 P2·NODE_TYPES 词表单点化结构性扫描宿主 +# 断言面 = 两组(均带「扫描面非空前置+正锚」): +# V1 node_map_generator:非空+本地 const NODE_TYPES 定义零命中+LevelTable.NODE_TYPES.has( 正锚 +# (词表唯一落点归 schema owner——表行校验 LevelTable;生成器 class_name 直引=S3 豁免面在案形态) +# V2 生产三目录(core/data/presentation)`NODE_TYPES :=` 定义恰好 1 处且落 data/schemas/level_table.gd +# 手法沿 encapsulation_sweep_test 源 token 扫描先例(两卡独立 worktree 独立批——不并件)。 +# 运行:godot --headless --path game --script res://tests/unit/core/encapsulation_p2_sweep_test.gd +extends SceneTree + +const Kit := preload("res://tests/unit/test_kit.gd") + + +func _init() -> void: + _sweep_generator() + _sweep_production_face() + quit(Kit.finish("encapsulation_p2_sweep_test")) + + +func _src(path: String) -> String: + var src := FileAccess.get_file_as_string(path) + Kit.check("扫描面非空前置:%s" % path, src.length() > 0) + return src + + +func _zero(pattern: String, src: String, label: String) -> void: + var re := RegEx.create_from_string(pattern) + Kit.check("%s:%s 零命中〔架构P2〕" % [label, pattern], re.search(src) == null) + + +func _anchor(pattern: String, src: String, label: String) -> void: + var re := RegEx.create_from_string(pattern) + Kit.check("%s:%s 正锚在案〔架构P2〕" % [label, pattern], re.search(src) != null) + + +# ① V1 生成器词表定义删除与改指 +func _sweep_generator() -> void: + var src := _src("res://core/node_map_generator.gd") + if src.length() == 0: + return + _zero("(?m)^const NODE_TYPES", src, "生成器本地词表副本归零") + _anchor("LevelTable\\.NODE_TYPES\\.has\\(", src, "schema 词表直引在用") + + +# ② V2 生产三目录定义唯一性(递归扫 .gd,恰好 1 处且落 level_table.gd) +func _sweep_production_face() -> void: + var files: Array[String] = [] + for dir_path in ["res://core", "res://data", "res://presentation"]: + _collect_gd(dir_path, files) + Kit.check("扫描面非空前置:生产三目录 .gd ≥ 1 件", files.size() > 0) + var re := RegEx.create_from_string("NODE_TYPES\\s*:?=") + var hits: Array[String] = [] + for f in files: + var src := FileAccess.get_file_as_string(f) + if src.length() > 0 and re.search(src) != null: + hits.append(f) + Kit.check("生产面 NODE_TYPES 定义恰好 1 处(实得 %d)〔架构P2〕" % hits.size(), hits.size() == 1) + if hits.size() == 1: + Kit.check("唯一定义落 data/schemas/level_table.gd〔架构P2〕", hits[0] == "res://data/schemas/level_table.gd") + + +# 递归收集 .gd(跳过 .godot 缓存与 .uid 边车) +func _collect_gd(dir_path: String, out: Array[String]) -> void: + var dir := DirAccess.open(dir_path) + if dir == null: + return + dir.list_dir_begin() + var name := dir.get_next() + while name != "": + var full := dir_path + "/" + name + if dir.current_is_dir(): + if not name.begins_with("."): + _collect_gd(full, out) + elif name.ends_with(".gd"): + out.append(full) + name = dir.get_next() + dir.list_dir_end() diff --git a/game/tests/unit/core/encapsulation_p2_sweep_test.gd.uid b/game/tests/unit/core/encapsulation_p2_sweep_test.gd.uid new file mode 100644 index 00000000..dfa41c1c --- /dev/null +++ b/game/tests/unit/core/encapsulation_p2_sweep_test.gd.uid @@ -0,0 +1 @@ +uid://50nvccb1clcg