feat(迭代28 T4): 渲染面内容断言三件(探针自检+墨色判据+启动态菜单 golden)
①shot_probe.gd 两条自检进 _problems:boot 态顶层可见层=start_overlay(内容层树遍历——屏壳容器不当候选,camp 壳透明实证);菜单标题/副标题 label is_visible_in_tree 且 rect 落屏内;隔离档 refresh_resume 开=菜单截图不依赖真实档断点态。②shot_checker.py 增 F 字在判据:菜单文字框与中位灰度差>60 像素占比带 [2%,55%](极性无关——亮字暗底同判;实测标题 13.07%/副标题 11.47% 定 provisional 阈值;对比度只判反差不判字在的缺口由此补)。③启动态菜单 golden:帧差前置验 0.0000%/max_diff=0(光柱静态贴片零动画);golden_baseline_refresh.sh 扩 --menu 模式(shot_probe 取材→game/tests/golden/menu_boot.png,字体+按钮石纹素材守卫);render_gate.sh 补菜单 golden 对比段(素材缺位 SKIP 同 Q5 口径)。闸=render_gate 全绿(探针问题=0+checker A-D/F 全过+棋盘 golden 0.00%×3+菜单 golden 0.00%)。注:本机 render_gate 需 gamedev venv Python 前置 PATH(闸机脚本调 python(PIL),本机默认 python 无 PIL——用户侧环境自带)
This commit is contained in:
1 parent
77075e6e7a
commit
67342c690e
5 files changed
+127
-11
No files matched your search
@@ -36,7 +36,9 @@ func _ready() -> void:
|
||||
add_child(main)
|
||||
await get_tree().process_frame
|
||||
# ── 存档/元进度/设置三面隔离(ui_click_probe 同款——真档零污染;历史语义 = contains 寻址) ──
|
||||
ProbeUiUtil.isolate_save(main, "smoke_shot_probe", false, true, false)
|
||||
# 迭代 28 T4:refresh_resume 开——续玩钮按隔离档(无断点)定基,菜单截图/golden 基线不依赖
|
||||
# 真实档断点态(boot 关系由冒烟 _smoke_boot_chain V2d 断,此处只保截图确定性)
|
||||
ProbeUiUtil.isolate_save(main, "smoke_shot_probe", false, true, true)
|
||||
# 隔离档即时应用(迭代 19 卡① T1——main 装配已按真实设置恢复过窗口态〔本机实测:真实档
|
||||
# fullscreen=true 时窗口进全屏,视口纹理 2304x1440≠1280 基准档,checker 尺寸/取样全盘失真〕;
|
||||
# apply 隔离档出厂默认=窗口化 1280x800,恢复本探针设计前提。探针侧环境修正,零运行时改动)
|
||||
@@ -44,8 +46,40 @@ func _ready() -> void:
|
||||
await get_tree().process_frame
|
||||
# ① 菜单(启动态)——meta 锚经 S1 裁后换代(2026-10-08 用户裁:副标题锚改认现行文案;
|
||||
# 帮助文腿整个退役——载体玩法速览牌 2026-10-06 用户令退役,checker 对应断言段同撤)
|
||||
_meta["menu_sub"] = ProbeUiUtil.label_rect(main._start_overlay, "扫雷的因")
|
||||
_meta["menu_title"] = ProbeUiUtil.label_rect(main._start_overlay, "Dungeon Sweeper")
|
||||
# 自检一(迭代 28 T4):boot 态顶层可见全屏层=start_overlay(内容层树遍历——屏壳是容器不当候选:
|
||||
# camp 壳 boot 态 vis=true 但成员恒隐,裸树遍历自顶向下会先命中透明壳)
|
||||
var top: Node = null
|
||||
var top_rx := -1
|
||||
var top_ry := -1
|
||||
for c: Node in [main._start_overlay, main._battle_root, main._inter_overlay,
|
||||
main.legend_overlay, main.node_overlay, main.level_screen, main.curio_overlay,
|
||||
main._end_overlay, main.shop_overlay, main.camp_overlay, main.settings_overlay,
|
||||
main.status_panel, main.career_overlay, main.confirm_dialog, main.debug_overlay]:
|
||||
if c == null or not (c is CanvasItem) or not (c as CanvasItem).visible:
|
||||
continue
|
||||
var rx: int = c.get_parent().get_index()
|
||||
var ry: int = c.get_index()
|
||||
if top == null or rx > top_rx or (rx == top_rx and ry > top_ry):
|
||||
top = c
|
||||
top_rx = rx
|
||||
top_ry = ry
|
||||
if top != main._start_overlay:
|
||||
_problems.append("boot 态顶层可见层≠start_overlay(实测 %s——菜单被埋)〔装配生命批 V4〕"
|
||||
% (top.name if top != null else "null"))
|
||||
# 自检二(迭代 28 T4):菜单标题/副标题 label 必须 is_visible_in_tree 且 rect 落屏内
|
||||
#(对比度只判反差不判「字在」——本条+checker 墨色占比共同补内容判据)
|
||||
var vp := get_viewport().get_visible_rect()
|
||||
for pair in [["menu_title", "Dungeon Sweeper"], ["menu_sub", "扫雷的因"]]:
|
||||
var lbl: Label = ProbeUiUtil.find_label(main._start_overlay, String(pair[1]))
|
||||
if lbl == null:
|
||||
_problems.append("菜单文字缺失: %s(find_label 零命中)〔装配生命批 V4〕" % String(pair[1]))
|
||||
continue
|
||||
if not lbl.is_visible_in_tree():
|
||||
_problems.append("菜单文字不可见: %s(is_visible_in_tree=false——层被埋或隐藏)〔装配生命批 V4〕" % String(pair[1]))
|
||||
var gr: Rect2 = lbl.get_global_rect()
|
||||
if not vp.encloses(gr):
|
||||
_problems.append("菜单文字 rect 越出视口: %s(%s ⊄ %s)〔装配生命批 V4〕" % [String(pair[1]), str(gr), str(vp)])
|
||||
_meta[String(pair[0])] = ProbeUiUtil.label_rect(main._start_overlay, String(pair[1]))
|
||||
await _snap("menu")
|
||||
# ①b 设置面(迭代 19 卡① T1 扩屏——settingsbtn 真实信号路径,开合同款真实钮)
|
||||
var setbtn: Button = ProbeUiUtil.find_button(main._start_overlay, "设置", "prefix")
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 313 KiB |
@@ -1,20 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
# 棋盘 golden 基线换代辅助(迭代 19 卡① T2 2026-10-08——呈现类改动批专用)。
|
||||
# golden 基线换代辅助(迭代 19 卡① T2 2026-10-08——呈现类改动批专用;迭代 28 T4 扩菜单全屏基线路径)。
|
||||
# 权限口径(Q6):呈现类改动批内 agent 自行换代+提交说明**强制写明触发原因**;数值/逻辑批禁碰。
|
||||
# 前置条件(Q5/S3):素材必须齐盘(字体/棋盘瓦片/英雄/旗纹理——本脚本逐项守卫,
|
||||
# 缺位即拒跑退出 1=基线禁锚字形回退态;缺位时先拷主区素材+导入边车再跑)。
|
||||
# 流程:窗口化 board_shot_probe 抓 L1-L3 整屏帧+board_meta.json → golden_board_crop.py
|
||||
# 按 meta 取样坐标裁棋盘区(禁硬编码像素)→ 覆盖 game/tests/golden/ 三张基线。
|
||||
# 过程截图落 mktemp 临时区即弃(Q2),只有裁剪产物入库。
|
||||
# 用法: tools/golden_baseline_refresh.sh [game目录] 触发原因写进当批提交说明(强制)。
|
||||
# 前置条件(Q5/S3):素材必须齐盘(本脚本逐项守卫,缺位即拒跑退出 1=基线禁锚回退态;
|
||||
# 缺位时先拷主区素材+导入边车再跑)。
|
||||
# 模式:
|
||||
# --menu(迭代 28 T4 新增):shot_probe 启动态菜单整屏帧 → game/tests/golden/menu_boot.png。
|
||||
# 全屏基线可行性前置已验证:菜单连续帧差 0.0000%/max_diff=0(2026-10-15 帧差诊断——光柱为
|
||||
# 程序生成静态贴片、零动画着色器,全屏基线不抖动);采集确定性=探针隔离档(无断点+出厂
|
||||
# 设置 1280×800),与 render_gate 的 shot_menu 对比段同源同帧。
|
||||
# 缺省(棋盘,原口径):board_shot_probe 抓 L1-L3 整屏帧+board_meta.json → golden_board_crop.py
|
||||
# 按 meta 取样坐标裁棋盘区(禁硬编码像素)→ 覆盖 game/tests/golden/ 三张基线。
|
||||
# 过程截图落 mktemp 临时区即弃(Q2),只有基线产物入库。
|
||||
# 用法: tools/golden_baseline_refresh.sh [game目录] [--menu] 触发原因写进当批提交说明(强制)。
|
||||
set -u
|
||||
G="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}"
|
||||
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
GAME="${1:-$REPO/game}"
|
||||
GAME=""
|
||||
MODE="board"
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
--menu) MODE="menu" ;;
|
||||
*) if [ -z "$GAME" ]; then GAME="$a"; fi ;;
|
||||
esac
|
||||
done
|
||||
GAME="${GAME:-$REPO/game}"
|
||||
GAME_WIN="$(cygpath -m "$GAME")"
|
||||
TMP="$(mktemp -d /tmp/goldenrefresh_XXXXXX)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
if [ "$MODE" = "menu" ]; then
|
||||
for a in assets/fonts/ui_font_fusion_pixel_zh_hans.ttf \
|
||||
assets/art/ui/button_stone.png; do
|
||||
if [ ! -f "$GAME/$a" ]; then
|
||||
echo "[golden_refresh] 拒跑 — 菜单素材缺位: $a(S3 停机点口径:基线禁锚回退态,先补素材+导入边车)"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
timeout 240 "$G" --path "$GAME_WIN" res://smoke/shot_probe.tscn -- "$TMP/shots" >"$TMP/run.log" 2>&1
|
||||
st=$?
|
||||
grep -E "\[shot_probe\]" "$TMP/run.log" | tail -3
|
||||
if [ "$st" -ne 0 ] || [ ! -s "$TMP/shots/shot_menu.png" ]; then
|
||||
echo "[golden_refresh] FAIL — shot_probe 退出码=$st(菜单截图缺失/探针自检红,见上)"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$REPO/game/tests/golden"
|
||||
cp "$TMP/shots/shot_menu.png" "$REPO/game/tests/golden/menu_boot.png" || exit 1
|
||||
echo "[golden_refresh] 完成 — 菜单全屏基线已代入 game/tests/golden/menu_boot.png(提交说明须写明触发原因)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for a in assets/fonts/ui_font_fusion_pixel_zh_hans.ttf \
|
||||
assets/art/board/flag_seal.png \
|
||||
assets/art/character/character_hero_idle.png \
|
||||
|
||||
@@ -135,6 +135,23 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "======== 菜单 golden 逐像素(迭代 28 T4——启动态全屏基线)========"
|
||||
if [ ! -s "$REPO/game/tests/golden/menu_boot.png" ]; then
|
||||
echo "[render_gate] FAIL — game/tests/golden/menu_boot.png 基线缺失(菜单 golden 未建——tools/golden_baseline_refresh.sh --menu)"; _rc=1
|
||||
elif [ ! -s "$SHOT/shot_menu.png" ]; then
|
||||
echo "[render_gate] SKIP — shot_menu.png 缺失(shot_probe 段已红),菜单 golden 不判"
|
||||
elif [ ! -f "$GAME/assets/fonts/ui_font_fusion_pixel_zh_hans.ttf" ] || [ ! -f "$GAME/assets/art/ui/button_stone.png" ]; then
|
||||
echo "[render_gate] SKIP — 菜单素材缺位(字体/按钮石纹——Q5 口径:基线禁锚回退态,运行时 SKIP)"
|
||||
else
|
||||
MENUG="$TMP/menugolden"
|
||||
mkdir -p "$MENUG"
|
||||
cp "$SHOT/shot_menu.png" "$MENUG/menu_boot.png"
|
||||
python tools/golden_compare.py "$REPO/game/tests/golden" "$MENUG" menu_boot.png | sed 's/^/[render_gate] /'
|
||||
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
|
||||
echo "[render_gate] FAIL — 菜单 golden 超容差(S2 停机点口径:核对热区——按钮换墨/布局变更属预期面=显式重制基线+呈报;超预期面=L3)"; _rc=1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "======== 残留检查(批末闸) ========"
|
||||
left="$(orphans | wc -l | tr -d ' ')"
|
||||
total="$(tasklist 2>/dev/null | grep -ci '^Godot' || true)"
|
||||
|
||||
@@ -13,6 +13,9 @@ r"""shot_checker.py — 整屏样张闸机检(迭代 9 卡⑤/T8 2026-09-30;
|
||||
C 明度三档带: 战斗屏 信息面(C 区面板均值) > 世界中(棋盘区均值) > 世界底(舞台无光柱区均值)
|
||||
(§2 明度分层——层间灰度可分);
|
||||
D 文字禁压纹理: 各文字框背景主簇纯度 ≥0.5(±12 灰度带占比——文字下垫平滑容器非 busy 纹理);
|
||||
F 文字在判据: 菜单文字框内与中位灰度差 >60 的像素占比在 [2%, 55%] 带内(极性无关——亮字暗底/
|
||||
暗字亮底同判;对比度只判反差不判「字在」,被埋菜单坐标处有反差即过线的缺口由本判据补,
|
||||
provisional 阈值由 2026-10-15 实测定);
|
||||
E 焦点预算〔首焦唯一〕: 语义面无法机检 → 编入段闸实机目检清单(T15 人闸判定)。
|
||||
退出码: 0 = 全过 / 1 = 有失败。
|
||||
"""
|
||||
@@ -77,6 +80,32 @@ def check_text_box(img: Image.Image, box: tuple, name: str, failures: list) -> N
|
||||
failures.append("%s: 背景纹理幅度 %.1f > 18(文字压 busy 纹理)" % (name, mid_std))
|
||||
|
||||
|
||||
def check_text_presence(img: Image.Image, box: tuple, name: str, failures: list,
|
||||
lo: float = 0.02, hi: float = 0.55, dev: int = 60) -> None:
|
||||
"""文字在判据(迭代 28 T4——对比度只判反差不判「字在」)。
|
||||
|
||||
框内与中位灰度差 >dev 的像素占比须落在 [lo, hi] 带内:极性无关(亮字暗底/暗字亮底同判),
|
||||
文字簇量级带(实测菜单标题 13.1%/副标题 11.5%——2026-10-15 窗口化实测定 provisional)。
|
||||
出带=疑似空框/纯色块/文字被移出框(结构性移位)。
|
||||
"""
|
||||
if not box:
|
||||
failures.append("%s: meta 几何缺失" % name)
|
||||
return
|
||||
px = sorted(box_pixels(img, box))
|
||||
n = len(px)
|
||||
if n == 0:
|
||||
failures.append("%s: 空框(零像素)" % name)
|
||||
return
|
||||
med = px[n // 2]
|
||||
ratio = sum(1 for v in px if abs(v - med) > dev) / n
|
||||
ok = lo <= ratio <= hi
|
||||
print("[shot_checker] %s — 文字像素占比=%.2f%%(带 [%.0f%%, %.0f%%] 极性无关)%s" % (
|
||||
name, ratio * 100, lo * 100, hi * 100, "I-OK" if ok else "I-FAIL"))
|
||||
if not ok:
|
||||
failures.append("%s: 文字像素占比 %.2f%% 出带 [%.0f%%, %.0f%%](字在判据——疑似空框/纯色/文字移位)" % (
|
||||
name, ratio * 100, lo * 100, hi * 100))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) < 2:
|
||||
print("用法: python tools/shot_checker.py <截图目录> [屏清单...]")
|
||||
@@ -132,6 +161,8 @@ def main() -> int:
|
||||
if "shot_menu.png" in imgs:
|
||||
m = imgs["shot_menu.png"]
|
||||
check_text_box(m, box_of(meta, "menu_sub"), "菜单·副标题", failures)
|
||||
check_text_presence(m, box_of(meta, "menu_title"), "菜单·标题", failures)
|
||||
check_text_presence(m, box_of(meta, "menu_sub"), "菜单·副标题", failures)
|
||||
# 菜单·帮助文断言腿退役(迭代 19 卡① S1 裁 2026-10-08——载体玩法速览牌 2026-10-06 用户令
|
||||
# 退役「界面禁玩法文字墙」,锚/取样键/断言段三处同撤,不重锚)
|
||||
print("[shot_checker] 焦点预算〔首焦唯一〕= 目检项(T15 段闸人闸清单判定——语义面不机检)")
|
||||
|
||||
Reference in new issue
Block a user