Files
gplan-2/game/smoke/layout_probe.gd
T
DKChan 5af6c8a8b4 feat(布局闸): layout_probe 补回分辨率几何断言(两档·一份断言函数据)
背景:孤儿探针 ui_layout_probe 退役后1920 档布局几何无机器承载。重建但改形态——
「档位表(数据)+ 一份不变量断言(代码)+ 循环代入」,测试代码量与档数无关,
增删档位只改 VIEWPORTS 数组一行。这与业界 Playwright parametrize/projects 同构:
同一个 test body 跑多档,变的只是输入表与基线图张数(数据非代码)。

档位按 battle.md §验收现登记的两档:基座 1280×800、16:9 全屏 1920×1080。不新增档。

关键修正——旧探针那条「1920 档几何恒定」断言是假的:
  设get_window().size = 1920×1080 → 逻辑视口仍是 1280×800(headless 下不生效)
  设 content_scale_size = 1422×800 → 逻辑视口 = 1422×800(真生效)
Godot 文档指定的运行时改基座接口是 content_scale_size;旧探针用错了 API,
其断言永远为真、当年报 PASS 是假绿。本探针改用正确接口。

断言面(每档跑一遍):三区非零/不越视口/两两不重叠/board_view ⊆ board_zone/
格子边长 ≥36px(battle.md §棋盘格尺寸公式 1280 档 12×12 → 36px)。

变异验证(CLAUDE.md 第9条)两发:
  ① battle_stage_view B区 252→700 → 5 条红(两档都中)✓ 还原后 PASS
  ② 注入全幅 ColorRect → 未见红 ✗ —— 判据只点名三区不覆盖第四个兄弟,
    该缺陷类不在拦截面内。已在代码注释标明边界,未过度设计补递归遍历
    (递归全子树查重叠极易误报容器父子关系)。

挂 probe_gate 默认必跑段(轻闸即跑)。S10 文件头闸本批零新增。
2026-10-15 23:46:39 +08:00

94 lines
4.6 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.
# VERTICAL SLICE - NOT FOR PRODUCTION
# 布局几何断言探针(正主=tools/probe_gate.sh 默认段;呈现层三区布局的分辨率维度验收)
# 用途:battle.md §验收「1280×800 与 16:9 全屏下三区无遮挡/无重叠/无裁切」的可执行化
# GDD 锚:design/gdd/ux/battle.md §Layout Zones(B/C/D 三区几何)
# 硬约束:档位表是数据、断言是一份函数据——加档只改表不动断言(禁每档建一个探针)
extends Node
const ProbeUiUtil := preload("res://smoke/probe_ui_util.gd") # 隔离样板单点
# 分辨率档位表(逻辑视口尺寸;数据侧——增删档位只改这里,断言代码零改动)
const VIEWPORTS := [
{"label": "基座 1280x800", "size": Vector2i(1280, 800)},
{"label": "16:9 全屏 1920x1080", "size": Vector2i(1920, 1080)},
]
const MIN_CELL := 36.0 # 格子可读门槛(battle.md §棋盘格尺寸公式 1280 档 12×12 → 36px)
var _problems: PackedStringArray = []
func _ready() -> void:
await get_tree().process_frame
var main: Node = load("res://presentation/main.tscn").instantiate()
add_child(main)
await get_tree().process_frame
ProbeUiUtil.isolate_save(main, "smoke_layout_probe", true, false, true)
main.debug_overlay.seed_edit.text = "42" # 种子钉死(确定性禁依赖默认随机分支)
main._start_adventure_from_menu()
for i in range(8):
await get_tree().process_frame
# 进战斗屏——三区(B 战斗画面/C 角色信息/D 扫雷图)同屏常驻,菜单屏不构成断言面
main.level_screen.press_candidate(0)
for i in range(20):
await get_tree().process_frame
for vp in VIEWPORTS:
# content_scale_size 是headless 下改逻辑视口的唯一有效入口(get_window().size
# 在 headless 下不改变视口——用旧 API 会得到永远为真的假断言)
get_tree().root.content_scale_size = vp["size"]
for i in range(5):
await get_tree().process_frame
_check_one(vp["label"], main)
# 复原本项目基座(探针不该把进程留在末档尺寸)
get_tree().root.content_scale_size = Vector2i(1280, 800)
if _problems.is_empty():
print("[layout_probe] PASS — %d 档几何不变量全过(三区非零/不越视口/互不重叠/view 含于 D 区/格子 ≥%.0fpx)"
% [VIEWPORTS.size(), MIN_CELL])
get_tree().quit(0)
else:
for p in _problems:
print("[layout_probe] FAIL — " + p)
print("[layout_probe] FAIL — %d 项断言未过" % _problems.size())
get_tree().quit(1)
# 一份不变量断言,逐档代入——代码量与档数无关
func _check_one(label: String, main: Node) -> void:
var vr: Rect2 = get_viewport().get_visible_rect()
if vr.size != Vector2(VIEWPORTS[0]["size"]) and vr.size.y < 1.0:
_problems.append("[%s] 逻辑视口退化 %s" % [label, vr.size])
var rects := {}
for key in ["stage", "info_panel", "board_zone"]:
var ctl: Control = main.get(key)
if ctl == null:
_problems.append("[%s] %s = null(三区未装配)" % [label, key])
return
var r := Rect2(ctl.global_position, ctl.size)
rects[key] = r
if r.size.x <= 0.0 or r.size.y <= 0.0:
_problems.append("[%s] %s 零尺寸 %s" % [label, key, r.size])
elif not vr.encloses(r):
_problems.append("[%s] %s 越视口 %s ⊄ %s" % [label, key, r, vr])
# 判据覆盖边界(如实记录,勿当已拦截):本探针按名点名三区做几何判定,
# 不遍历战斗屏宿主的全部后代。变异验证第二发证——向装配器注入一个全幅 ColorRect
# (挂在 battle_root 的内层根下)时探针仍全绿:「未登记兄弟控件」这一缺陷类不在本探针
# 拦截面内。要拦它得改为递归遍历全子树找重叠,那是另一套判据(且极易误报容器父子关系),
# 归后续批按需立,不在本批范围。
var bv: Control = main.board_view
if bv == null:
_problems.append("[%s] board_view = null" % label)
return
var br := Rect2(bv.global_position, bv.size)
if not vr.encloses(br):
_problems.append("[%s] board_view 越视口 %s ⊄ %s" % [label, br, vr])
if rects.has("board_zone") and not rects["board_zone"].encloses(br):
_problems.append("[%s] board_view 越出 board_zone(%s ⊄ %s)"
% [label, br, rects["board_zone"]])
var keys := rects.keys()
for i in range(keys.size()):
for j in range(i + 1, keys.size()):
if rects[keys[i]].intersects(rects[keys[j]]):
_problems.append("[%s] 顶层区重叠 %s×%s(%s ∩ %s)"
% [label, keys[i], keys[j], rects[keys[i]], rects[keys[j]]])
var cell: float = main.board_view.get("cell_size")
if cell < MIN_CELL:
_problems.append("[%s] 格子边长 %.1fpx < 可读门槛 %.0fpx" % [label, cell, MIN_CELL])