# tests/unit/presentation/presentation_encapsulation_sweep_test.gd — 架构审查 presentation 层封装修复·四族结构性零命中扫描宿主 # 断言面(全部带「扫描面非空前置+正锚」——非空防扫描面为空静默绿,正锚证新面在用): # V1 A1 三内部数据类(level_map_screen/_MapState、battle_end_overlay/_EndOverlayState、debug_overlay/_DebugState): # 非空+class 正锚+实例私取模式零命中(_map\._/st\._)+42 公开字段逐个正锚+类名文件外零消费者 # V2 A2 拆件子件(standee_body→vitals/motion、board_view→board_hover): # 非空+子件私取模式零命中(st\._/mo\._/_hover_comp\._)+新公开面正锚 # V3 B 组合根交接面(main+五分文件): # 非空+root\._ 与 MainXxxFlow\._ 私取零命中+束消费/新公开面正锚 # (C 断言组随同批 T5 落地——本件四组齐后为「presentation 私取 sweep 恒零」结构性主证) # 手法沿 encapsulation_sweep_test(5.1 卡)源 token 扫描先例,不并件——扫描面与尾缀语义本批特有(卡 §12 同款论证)。 # 运行:godot --headless --path game --script res://tests/unit/presentation/presentation_encapsulation_sweep_test.gd extends SceneTree const Kit := preload("res://tests/unit/test_kit.gd") const A1_FILES := { "res://presentation/views/level_map_screen.gd": { "cls": "_MapState", "inst": "_map", "fields": ["abandon_timer", "attr_value", "bonus_row", "bonus_value", "canvas", "drawn_disp", "feat_value", "gp_value", "hp_bar", "hp_value", "layer_decor", "level_label", "runner", "title"], }, "res://presentation/views/battle_end_overlay.gd": { "cls": "_EndOverlayState", "inst": "st", "fields": ["board_rect", "bonus_name", "bonus_row_l", "bonus_val", "cele", "crest_bricks", "dim", "fx_counts", "ks_name", "ks_row", "ks_val", "rows_box", "seal", "verdict"], }, "res://presentation/views/debug_overlay.gd": { "cls": "_DebugState", "inst": "st", "fields": ["atk", "atk_val", "base_atk", "base_hp", "ctx_locked", "db", "eff", "hp", "hp_val", "run_data", "run_labels", "sim_count", "sim_out", "sim_running"], }, } func _init() -> void: _sweep_a1_data_classes() _sweep_a2_subparts() _sweep_b_combo_root() _sweep_c_misc() quit(Kit.finish("presentation_encapsulation_sweep_test")) # 源文件读取+非空前置(返 "" 即已判红——调用侧跳过后续正则断言) func _src(path: String) -> String: var src := FileAccess.get_file_as_string(path) Kit.check("扫描面非空前置:%s" % path, src.length() > 0) return src # 正则零命中断言(-pattern 意图写入断言文案——可 grep 定位;suffix 区分族组) func _zero(pattern: String, src: String, label: String, suffix: String = "pres封装 V1") -> void: var re := RegEx.create_from_string(pattern) Kit.check("%s:%s 零命中〔%s〕" % [label, pattern, suffix], re.search(src) == null) # 正锚在案断言(正则命中 ≥1——新公开面/新形态在用;suffix 区分族组) func _anchor(pattern: String, src: String, label: String, suffix: String = "pres封装 V1") -> void: var re := RegEx.create_from_string(pattern) Kit.check("%s:%s 正锚在案〔%s〕" % [label, pattern, suffix], re.search(src) != null) # ② V2 A2 拆件子件(行为归位子件公开方法、读走公开读法、身份构造交付) func _sweep_a2_subparts() -> void: var body := _src("res://presentation/views/standee_body.gd") if body.length() > 0: _zero("\\bst\\._|\\bmo\\._", body, "子件私取模式零命中(standee_body)", "pres封装 V2") _anchor("\\.configure_identity\\(", body, "身份构造交付正锚(standee_body)", "pres封装 V2") _anchor("\\.play_hp_flash\\(", body, "闪白归位正锚(standee_body)", "pres封装 V2") _anchor("\\.show_intent\\(|\\.hide_intent\\(", body, "意图行为正锚(standee_body)", "pres封装 V2") _anchor("mo\\.portrait_sprite\\(\\)", body, "绘制读口正锚(standee_body)", "pres封装 V2") var board := _src("res://presentation/views/board_view.gd") if board.length() > 0: _zero("_hover_comp\\._", board, "子件私取模式零命中(board_view)", "pres封装 V2") _anchor("_hover_comp\\.(rebind|reset|hover_at|detect_chord|refresh_after_paint|on_shell_mouse_exited|hover_pos)", board, "悬停七口正锚(board_view)", "pres封装 V2") # ③ V3 B 组合根交接面(main+五分文件——成对单行转发梯换代为公开入口+Callable 束) func _sweep_b_combo_root() -> void: var b_files := { "res://presentation/main.gd": false, "res://presentation/main_ui_build.gd": true, "res://presentation/main_adventure_flow.gd": true, "res://presentation/main_overlay_flow.gd": true, "res://presentation/main_debug_flow.gd": true, "res://presentation/main_esc_flow.gd": true, } for path in b_files: var src := _src(path) if src.length() == 0: continue if b_files[path]: _zero("root\\._[a-zA-Z]", src, "root 私取零命中(%s)" % path.get_file(), "pres封装 V3") _zero("Main(Overlay|Adventure|Debug|Esc)Flow\\._[a-zA-Z]", src, "分文件私取零命中(%s)" % path.get_file(), "pres封装 V3") var main_src := FileAccess.get_file_as_string("res://presentation/main.gd") if main_src.length() > 0: for fn in ["set_treasure_ui", "treasure_ui", "set_bonus", "bonus", "set_camp_pres", "camp_pres", "set_debug", "debug", "set_tune_sim", "tune_sim", "set_confirm_consumer", "refresh_presentations", "quit_game", "begin_session", "open_save_select", "seed_value"]: _anchor("func %s\\(" % fn, main_src, "main 公开口正锚(%s)" % fn, "pres封装 V3") var wiring := FileAccess.get_file_as_string("res://presentation/main_ui_build.gd") if wiring.length() > 0: _anchor("handlers\\[\"board_action\"\\]", wiring, "束消费正锚(ui_build)", "pres封装 V3") _anchor("root\\.set_treasure_ui\\(", wiring, "句柄设置口正锚(ui_build)", "pres封装 V3") _anchor("root\\.camp_pres\\(\\)\\.", wiring, "句柄读投影正锚(ui_build)", "pres封装 V3") var adv := FileAccess.get_file_as_string("res://presentation/main_adventure_flow.gd") if adv.length() > 0: _anchor("handlers\\[\"adventure_finished\"\\]", adv, "束消费正锚(adventure)", "pres封装 V3") _anchor("root\\.bonus\\(\\)\\.", adv, "句柄读投影正锚(adventure)", "pres封装 V3") # ④ V4 C 杂项(UiKit 私有工厂公开化+cell_mark 构造交付) func _sweep_c_misc() -> void: var offenders: PackedStringArray = [] var scanned := 0 _walk_gd_for("res://", "UiKit\\._[a-zA-Z]", "res://presentation/views/ui_kit.gd", offenders) scanned = _scanned Kit.check("UiKit 私有工厂全库零外调(扫描面 %d 件非空前置;越界=%s)〔pres封装 V4〕" % [scanned, str(offenders)], offenders.is_empty()) var cm := _src("res://presentation/views/cell_mark_overlay.gd") if cm.length() > 0: _zero("_owner\\._", cm, "宿主私有回引零命中(cell_mark_overlay)", "pres封装 V4") _anchor("_mask_set\\.is_valid\\(\\)", cm, "构造交付正锚(cell_mark_overlay)", "pres封装 V4") var uk := _src("res://presentation/views/ui_kit.gd") if uk.length() > 0: _anchor("func (gradient_base|beams_rect|switch_toggle)\\(", uk, "三工厂公开正锚(ui_kit)", "pres封装 V4") func _walk_gd_for(dir_path: String, pattern: String, exclude: String, offenders: PackedStringArray) -> void: var re := RegEx.create_from_string(pattern) 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.path_join(name) if dir.current_is_dir(): if not name.begins_with("."): _walk_gd_for(full, pattern, exclude, offenders) elif name.ends_with(".gd") and full != exclude: _scanned += 1 if re.search(FileAccess.get_file_as_string(full)) != null: offenders.append(full) name = dir.get_next() dir.list_dir_end() # ① V1 A1 三内部数据类(字段公开化——宿主直读写内类私有字段清零) func _sweep_a1_data_classes() -> void: for path in A1_FILES: var spec: Dictionary = A1_FILES[path] var src := _src(path) if src.length() == 0: continue var cls: String = spec["cls"] var inst: String = spec["inst"] _anchor("class %s" % cls, src, "class 正锚(%s)" % cls) _zero("%s\\._[a-zA-Z]" % inst, src, "实例私取模式零命中(%s.%s)" % [path.get_file(), inst]) for field in spec["fields"]: _anchor("\\.%s\\b" % field, src, "公开字段正锚(%s.%s)" % [cls, field]) # 类名文件外零消费者:全 game/ .gd 递归扫描(排除三类宿主文件与本测试件自身——本件源文本含类名 token) var hosts := ["res://presentation/views/level_map_screen.gd", "res://presentation/views/battle_end_overlay.gd", "res://presentation/views/debug_overlay.gd"] var offenders: PackedStringArray = [] _walk_gd("res://", hosts, offenders) Kit.check("A1 三类文件外零消费者(全 game/ .gd 扫描面 %d 件非空前置;越界=%s)" % [_scanned, str(offenders)], offenders.is_empty()) var _scanned := 0 func _walk_gd(dir_path: String, exclude: Array, offenders: PackedStringArray) -> 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.path_join(name) if dir.current_is_dir(): if not name.begins_with("."): _walk_gd(full, exclude, offenders) elif name.ends_with(".gd") and not exclude.has(full) and not full.contains("presentation_encapsulation_sweep_test"): _scanned += 1 var src := FileAccess.get_file_as_string(full) for cls in ["_MapState", "_EndOverlayState", "_DebugState"]: if src.contains(cls): offenders.append("%s:%s" % [full, cls]) name = dir.get_next() dir.list_dir_end()