Files
gplan-2/game/probe/ui_contrast_probe.gd
T
DKChan 5c9c5f8083 refactor(iter37③): boardview 拆件——瓦片调色板/英雄精灵两族出根(行为等价,golden 逐像素零变)
r2-1 瓦片族(排版+素材装载+墨族判据+查表)→ views/tile_palette.gd;
r2-2 英雄族(贴图/位移/影/朝向/缩放几何)→ views/hero_sprite.gd(Node2D);
r2-3 board_view 改引+直引面换指(terrain_tile_sets_test/ui_contrast_probe),
探针与 main 的组合根直读面经转发属性/常量别名保持零改动。

S13 计数守恒:基线含类变量数/公有方法数两条计数型键,故 board_view 用 13 个转发属性
+2 个组件引用压回原计数(45 变量/27 公有方法);baselined 的 wall_tile_for/
wall_center_tile/digit_ink_mode 以查表/辅助函数重构压回阈值内(非改名保违例),
旧文件级键随函数迁出自然失效——基线文件零改动(用户令:只改代码不改基线)。

验收:terrain_tile_sets_test 19 检查绿(判据读数与迁移前同值);gdstyle 基线外新违规 0;
S9/S10 绿;probe_gate --click 全绿(含 ui_input_probe 十八断言/click 走查 0 问题);
render_gate 棋盘三图+菜单 golden changed=0.00%;撤销证明:测试改引回退旧址符号
→ SCRIPT ERROR(Parse Error: Static function "_normalize_floor_luma()" not found)+
error_scan exit 1,还原后 19 检查复绿、error_scan exit 0。
2026-10-25 06:43:03 +08:00

195 lines
12 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
# UI 文字对比度机检(probe_gate 默认必跑)
# 断言面 = 文字墨对直背景 ≥4.5:1(art-bible §7.2 无区域例外;文字禁直压纹理底——垫牌后按牌色计)。
# 测点表:血数垫牌 / 意图徽标四墨(PANEL_DATA 垫牌)/ C区槽位两态墨 / RAP 标签 + 全站锚点组。
# 测点对一律取 UiKit 常量实时求值——代码侧换色即本闸红,禁只改注释不动值;α<1 墨先对背景混成再算。
# 运行:Godot_console.exe --headless --path . res://probe/ui_contrast_probe.tscn
extends Node
const MIN_RATIO := 4.5
# R1 过渡白名单:呈现层(ui_kit.gd 之外)允许的裸 Color8( 在册处——file→恰计数。
# 产品码裸取色已全部收进 ui_kit——本表应为空表;
# 新增裸取色 = 本闸红(先立常量再消费)。
const BARE_COLOR8_WL := {}
# 测点表:[名称, 墨色, 直背景, 墨α](语义见行尾;新增文字位入表,白名单豁免须注理由)
func _pairs() -> Array:
return [
["站桩血数·垫牌", Color(1, 1, 1), UiKit.HP_BAR_BACK, 0.95], # N1-1/2 白 α.95 压 HP_BAR_BACK 素牌
["意图atk·垫牌", UiKit.RED_MINE, UiKit.PANEL_DATA, 1.0], # N1-3 ⚔ 攻击预告(零换墨)
["意图buf·垫牌", UiKit.HERO_BLUE_DEEP, UiKit.PANEL_DATA, 1.0], # N1-4 ✦ 增益(同族深档)
["意图debuff·垫牌", UiKit.DEBUFF_PURPLE, UiKit.PANEL_DATA, 1.0], # ☠ 妨害(零换墨随牌达标)
["意图charge·垫牌", UiKit.GOLD_INK, UiKit.PANEL_DATA, 1.0], # 蓄力重击(深金墨)
["C区槽·已用", UiKit.STONE_DEEP, UiKit.PANEL_DATA, 1.0], # N1-5 占位暗字(灰石深化)
["C区槽·不可用", UiKit.INK_LABEL, UiKit.PANEL_DATA, 1.0], # N1-6 置灰(标签深墨)
["RAP标签", UiKit.INK_LABEL, UiKit.PANEL_DATA, 1.0], # N1-7(T0 实测已合规,显式化)
["锚·正文墨/数据板", UiKit.INK, UiKit.PANEL_DATA, 1.0], # 全站锚点:正文墨对数据板
["锚·暖白/屏根", UiKit.WARM, UiKit.BG_ROOT, 1.0], # 全站锚点:暗底正文
["锚·暖白弱/舞台底", UiKit.WARM_FAINT, UiKit.STAGE_BG, 1.0], # 全站锚点:B区辅助墨
["钥石徽·深墨/烛光黄", UiKit.INK_ON_GOLD, UiKit.CANDLE, 1.0], # badge 工厂件文字(营地徽+状态面板余额行同形制)
["生涯页·行名墨/暖亮页底", UiKit.WARM, UiKit.BG_BRIGHT, 1.0], # 生涯页行名列/提示文(WARM_SOFT 同族亮墨)
["生涯页·行值墨/暖亮页底", UiKit.CANDLE, UiKit.BG_BRIGHT, 1.0], # 生涯页数值列(烛光黄 bold)
["生涯页·组帽墨/暖亮页底", UiKit.INK_L_GOLD, UiKit.BG_BRIGHT, 1.0],# 生涯页分组小节头(金亮 bold)
# ---- 旧屏文字位追溯收测;石皮纹面板
# (panel_skinned)缺素材回退牌色 PANEL_DATA,机检按回退牌色计(素材不入 git 冒烟安全惯例)。
# 存量违例对首跑红=登记呈报不修(处置权归用户)。
# 已回绿对:随代码侧换墨同步改指新常量(比率见各行尾注)。
["结算·判定墨/结算板", UiKit.INK_TITLE, UiKit.PANEL_DATA, 1.0], # battle_end 判定行(INK_TITLE 亮底标题墨)
["结算·GP账行墨/结算板", UiKit.GOLD_INK, UiKit.PANEL_DATA, 1.0], # battle_end 战利品 GP 行名/值(深金墨 5.21:1)
["结算·账行墨/结算板", UiKit.INK_LABEL, UiKit.PANEL_DATA, 1.0], # battle_end 钥石/加成账行(标签深墨)
["商店·行卡名墨/暗底行卡", UiKit.WARM_SOFT, UiKit.BG_OVERLAY, 1.0], # legacy 行卡商品名(显式传暖白次级 9.53:1)
["商店·行卡现价墨/暗底行卡", UiKit.WARM_SOFT, UiKit.BG_OVERLAY, 1.0],# legacy 行卡「现价 N GP」
["商店·行卡已购墨/暗底行卡", UiKit.WARM_FAINT, UiKit.BG_OVERLAY, 1.0],# legacy 行卡「已购 N 档」
["商店·爬价规则墨/暖亮页底", UiKit.WARM_SOFT, UiKit.BG_BRIGHT, 1.0],# 货架面爬价规则行(page_shell 亮底)
["商店·货架卡名墨/货架板", UiKit.INK_TITLE, UiKit.PANEL_DATA, 1.0], # bcard 货架卡商品名(换 TG 带后未机检位)
["商店·货架价签墨/货架板", UiKit.GOLD_INK, UiKit.PANEL_DATA, 1.0], # bcard 价签「N ◈」(深金墨 5.21:1)
["营地·等级章字/头带深棕", UiKit.INK_ON_DEEP, UiKit.CAMP_HEADER_BAND, 1.0],# 锻造行 Lv 章(木底小章亮墨)
["营地·柜台头带字/头带深棕", UiKit.WARM, UiKit.CAMP_HEADER_BAND, 1.0], # 三柜台头带标题(暖白 bold)
["营地·铭牌副题墨/铭牌板", UiKit.STONE_DEEP, UiKit.PANEL_DATA, 1.0],# 营地铭牌副题「跨局成长账本…」(灰石深化 5.98:1)
["营地·行名墨/行板", UiKit.INK, UiKit.CAMP_ROW_PLATE, 1.0], # 锻造/工坊行名(墨族压暖米行板)
["营地·行价格墨/行板", UiKit.STONE_DEEP, UiKit.CAMP_ROW_PLATE, 1.0],# 锻造行「N 钥石」价签/工坊进度行(灰石深化 6.70:1)
["营地·树分组头墨/柜台板", UiKit.GOLD_INK, UiKit.PANEL_DATA, 1.0],# 天赋树脉分组头「·锻造脉·」(深金墨 5.21:1)
["营地·树行名墨/柜台板", UiKit.INK_LABEL, UiKit.PANEL_DATA, 1.0], # 天赋树行「名 Lv0/1」(标签深墨)
["营地·工坊状态墨/行板", UiKit.GOLD_INK, UiKit.CAMP_ROW_PLATE, 1.0],# 工坊状态字「已解锁」(深金墨 5.84:1·最不利现役态)
["营地·最近冒险墨/暖亮底", UiKit.WARM_SOFT, UiKit.BG_BRIGHT, 1.0], # 底栏最近冒险条带(暖白次级)
["设置·警示文/设置板", UiKit.INK_HINT, UiKit.PANEL_DATA, 1.0], # 设置警示文/后果说明(暖棕提示墨)
["设置·选项标签/设置板", UiKit.INK_LABEL, UiKit.PANEL_DATA, 1.0], # 设置行标签/状态字(标签深墨)
["三选一·停点头部墨/暗页底", UiKit.WARM_SOFT, UiKit.BG_ROOT, 1.0], # 三选一停点头部/回血反馈行(page_shell 根底档)
["三选一·卡名墨/货架板", UiKit.INK_TITLE, UiKit.PANEL_DATA, 1.0], # bcard 选项卡名(与商店货架同构卡)
# ---- 迷雾测点三关扩对(迭代36 wc5——甲·归一后基准:行值=三关归一后(真实渲染面)
# 地板均值合成底(写卡会话 PIL 复算+两轮 grilling 独立复算吻合;T0 探针派生首跑对拍
# L2/L3 修值(PIL↔Godot 导入解码系统差约 5/255,卡内 L2 条款预授权以派生为准);
# 素材更新随动——一致性由 _ready 派生校验断言钉住,行值回原图基准或素材换代未随动均红)----
["迷雾·记忆墨/合成底L1地牢", UiKit.FOG_MEMORY_INK,
Color(UiKit.FOG_MEMORY_PLATE.r * 0.66 + 111.0 / 255.0 * 0.34, # L1 归一后地板均值 #6F4D33(合成底 #4C4038 5.71:1·最差关)
UiKit.FOG_MEMORY_PLATE.g * 0.66 + 77.0 / 255.0 * 0.34,
UiKit.FOG_MEMORY_PLATE.b * 0.66 + 51.0 / 255.0 * 0.34), 1.0],
["迷雾·记忆墨/合成底L2幽暗", UiKit.FOG_MEMORY_INK,
Color(UiKit.FOG_MEMORY_PLATE.r * 0.66 + 74.0 / 255.0 * 0.34, # L2 归一后地板均值 #4A4F60(合成底 #3F4147 5.83:1)
UiKit.FOG_MEMORY_PLATE.g * 0.66 + 79.0 / 255.0 * 0.34,
UiKit.FOG_MEMORY_PLATE.b * 0.66 + 96.0 / 255.0 * 0.34), 1.0],
["迷雾·记忆墨/合成底L3深幽", UiKit.FOG_MEMORY_INK,
Color(UiKit.FOG_MEMORY_PLATE.r * 0.66 + 71.0 / 255.0 * 0.34, # L3 归一后地板均值 #474B63(合成底 #3E4048 5.93:1)
UiKit.FOG_MEMORY_PLATE.g * 0.66 + 75.0 / 255.0 * 0.34,
UiKit.FOG_MEMORY_PLATE.b * 0.66 + 99.0 / 255.0 * 0.34), 1.0],
]
func _lum(c: Color) -> float:
var f := func(x: float) -> float:
return x / 12.92 if x <= 0.03928 else pow((x + 0.055) / 1.055, 2.4)
return 0.2126 * f.call(c.r) + 0.7152 * f.call(c.g) + 0.0722 * f.call(c.b)
func _ratio(a: Color, b: Color) -> float:
var la := _lum(a)
var lb := _lum(b)
var hi := la if la >= lb else lb
var lo := lb if la >= lb else la
return (hi + 0.05) / (lo + 0.05)
func _ready() -> void:
var problems: Array = []
var pairs := _pairs()
for p in pairs:
var fg: Color = p[1]
var bg: Color = p[2]
var alpha: float = p[3]
if alpha < 1.0:
fg = Color(fg.r * alpha + bg.r * (1.0 - alpha),
fg.g * alpha + bg.g * (1.0 - alpha),
fg.b * alpha + bg.b * (1.0 - alpha))
var r := _ratio(fg, bg)
if r < MIN_RATIO:
problems.append("[contrast] %s = %.2f:1 < %.1f(测点对须垫牌或换墨)" % [p[0], r, MIN_RATIO])
problems.append_array(_scan_static())
problems.append_array(_fog_derived_check())
if problems.is_empty():
print("[ui_contrast_probe] PASS — 对比度 %d 对测点全绿(≥%.1f:1 无区域例外)+ 裸色白名单/preload 红线静态段绿 + 迷雾三关派生校验绿" % [pairs.size(), MIN_RATIO])
get_tree().quit(0)
else:
for msg in problems:
print(msg)
print("[ui_contrast_probe] FAIL — %d 项越带(含静态段)" % problems.size())
get_tree().quit(1)
# ---- 迷雾测点行值一致性派生校验(迭代36 wc5——行值 vs 运行时按生产归一函数实算的
# 三关归一后地板均值,容差 ±2/255=8-bit 量化级;行值回原图基准或素材换代未随动均红)----
func _fog_derived_check() -> Array:
var problems: Array = []
var rows := [
["L1地牢", "dungeon", Color8(111, 77, 51)],
["L2幽暗", "underdark", Color8(74, 79, 96)],
["L3深幽", "deep_underdark", Color8(71, 75, 99)],
]
for row in rows:
var derived := _norm_floor_avg(row[1])
var expect: Color = row[2]
if absi(derived.r8 - expect.r8) > 2 or absi(derived.g8 - expect.g8) > 2 or absi(derived.b8 - expect.b8) > 2:
problems.append("[contrast] 迷雾测点行值失真:%s 行值 (%d,%d,%d) vs 运行时归一派生 (%d,%d,%d)(±2/255——素材更新须随动改行值)"
% [row[0], expect.r8, expect.g8, expect.b8, derived.r8, derived.g8, derived.b8])
return problems
# 迷雾测点派生助手(迭代36 wc5 契约 3——全仓唯一「按板型算归一后均值」判据面):纹理路径经
# TilePalette.TILE_FLOOR_FMT 常量(禁复制字面量)、归一经 TilePalette.normalize_floor_luma
# 生产静态函数直呼(真渲染同源——禁探针内复制 k 公式);逐像素归一后均值再五变体平均。
static func _norm_floor_avg(board_type: String) -> Color:
var sum_r := 0.0
var sum_g := 0.0
var sum_b := 0.0
var px := 0
for i in range(1, TilePalette.TILE_FLOOR_VARIANTS + 1):
var tex := UiKit.load_tex(TilePalette.TILE_FLOOR_FMT % [board_type, i])
if tex == null:
continue
var img: Image = TilePalette.normalize_floor_luma(tex, 0.10, null).get_image()
if img.is_compressed():
img.decompress()
img.convert(Image.FORMAT_RGBA8)
for y in range(img.get_height()):
for x in range(img.get_width()):
var c := img.get_pixel(x, y)
sum_r += c.r
sum_g += c.g
sum_b += c.b
px += 1
if px == 0:
return Color(-1, -1, -1) # 素材缺位=派生校验必红(禁跳过——契约 3 齐盘条款)
return Color(sum_r / px, sum_g / px, sum_b / px)
# ---- 机检段②③(需求卡片 §7):呈现层裸 Color8( 计数 vs 白名单 + 素材 preload 计数 = 0 ----
func _scan_static() -> Array:
var problems: Array = []
var root := "res://presentation"
var dirs := [root]
while not dirs.is_empty():
var d: String = dirs.pop_front()
for f in DirAccess.get_files_at(d):
if not f.ends_with(".gd"):
continue
var path := d + "/" + f
var src := FileAccess.get_file_as_string(path)
var c8 := src.count("Color8(")
var pre := 0
for line in src.split("\n"):
if line.contains("preload(") and (line.contains("res://assets/") or line.contains(".png")):
pre += 1
if path.ends_with("/ui_kit.gd"):
if pre != 0:
problems.append("[static] ui_kit.gd 素材 preload=%d(红线=0)" % pre)
continue # ui_kit=色板工厂本体,Color8 定义面合法
var wl: int = BARE_COLOR8_WL.get(path, 0)
if c8 != wl:
problems.append("[static] %s 裸 Color8=%d 白名单=%d(R1 先立常量再消费)" % [path, c8, wl])
if pre != 0:
problems.append("[static] %s 素材 preload=%d(R2 红线=0)" % [path, pre])
for sub in DirAccess.get_directories_at(d):
dirs.append(d + "/" + sub)
return problems