test(断言三件): 未开格类型微光机检面(迭代 30 卡① T2-①②③)——①冒烟 smoke_all.gd 新增 _smoke_type_glow 八相 24 项(相0 作用集恒等+默认开+已开/r0 零微光/相1 八邻派八型五组逐格映射/相2 清单外"keystone"显式登记+零微光/相3 封锁格零微光零登记/相4 解封复位/相5 off/on 两态+关前快照逐位复现+零玩法写入三面/相6 换图 rebuild 重算/相7 契约 1 同板同序列 on/off 双实例结算逐位一致 12 步 digest 对照);夹具 = seed 4242 L1 板+novice 驱动(初始展开覆盖 8 邻故必驱动)+八邻派型+哈希兜底九型全集;冒烟 1127→1151 项。②单测 type_glow_test.gd 五段 51 检查(归组封闭含清单外 6 负样本+两带并存锚定/冻结色相面 HSV 稽核 ΔH≤0.2°·ΔS≤0.01+群伤 V=0.65 专项/呈现参数锚定 α170·band 0.125·组号域 0-5/伽马合成空间阶梯单调+相邻组模型 CR≥1.20/probe API 形状),登记 run_all.gd(33→34 套件);extends SceneTree+Kit/UiKitScript preload(--script 模式禁 class_name 裸引用)。③ui_input_probe case⑰ 活板面走查(生产身份面:作用集==diggable 键集/真实点击挖开即灭/移动复亮出圈灭/相位门控两态/开关两态零扰动),十八断言全绿。批末变异验证五组反例实证:M1 归组错映(单测+冒烟红)/M2 清单外静默(冒烟红)/M3 相位切换不重算(冒烟+探针红)/M4 α=0 不绘(渲染闸红)/M5 派生路径误触玩法 API(冒烟相7 分叉红)——红输出与还原结论实录见 reports/iter30-实现报告-2026-10-17.md §四。

This commit is contained in:
DKChan committed 2026-10-17 10:18:35 +08:00
1 parent bd44e658e8
commit 80ac80ef1a
5 files changed
+544 -1

No files matched your search

@@ -0,0 +1,173 @@
# tests/unit/presentation/type_glow_test.gd — 未开格类型微光数据面直验(迭代 30 卡① T2-②)
# 断言面 = 纯函数/常量域(无渲染):① 九 cell_type → 五组封闭判定(含清单外显式失败、
# 禁子串匹配、与卡顶带 tg_band 的归组差异锚定——Q3 甲案两带并存);② 五组色值冻结面
# (HSV 对照 art-bible §4.2 冻结值只准动明度档——2026-10-17 群伤组一案停机呈报后用户裁
# #A61D37,本测试即该裁定的守门件);③ 呈现参数锚定(α170/带宽 cell÷8);④ 合成空间
# 阶梯单调 + 相邻组模型 CR ≥1.20(色弱灰度互辨的数据面预判,终审在渲染取证探针实测);
# ⑤ probe API 形状(默认开/空态键集)。
# 运行:godot --headless --path game --script res://tests/unit/presentation/type_glow_test.gd
extends SceneTree
const Kit := preload("res://tests/unit/test_kit.gd")
# --script 模式无全局标识符(class_name 不可用)——使用方 preload
const UiKitScript := preload("res://presentation/views/ui_kit.gd")
const BoardViewScript := preload("res://presentation/views/board_view.gd")
# art-bible §4.2 样张 v2 冻结五组(F3 抄录表——本测试的冻结面基准)
const FROZEN := {
"BUFF": Color8(0xD6, 0xC1, 0x3A), "HEAL": Color8(0x5C, 0xB8, 0x3A),
"PIERCE": Color8(0x2E, 0x9E, 0x9E), "MAGIC": Color8(0x7B, 0x5B, 0xD6),
"AOE": Color8(0xFF, 0x2D, 0x55),
}
# 九 cell_type 全值域 → 期望组号(art-bible §4.2 成员集合逐字照抄)
const TYPE_EXPECT := {
"attack": UiKitScript.GLOW_BUFF, "defense": UiKitScript.GLOW_BUFF,
"weaken": UiKitScript.GLOW_BUFF, "action": UiKitScript.GLOW_BUFF,
"gp": UiKitScript.GLOW_BUFF, "heal": UiKitScript.GLOW_HEAL,
"pierce": UiKitScript.GLOW_PIERCE, "blast": UiKitScript.GLOW_AOE,
"magic": UiKitScript.GLOW_MAGIC,
}
# provisional 明度档终值(2026-10-17 用户裁后)——逐位锚定,谁改色值这里红
const IMPL := {
"BUFF": Color8(247, 223, 67), "HEAL": Color8(104, 208, 66),
"PIERCE": Color8(48, 166, 166), "MAGIC": Color8(123, 91, 214),
"AOE": Color8(166, 29, 55),
}
# 实现常量直引(禁 class get() 动态取——parse 层就不许)
const IMPL_CONSTS := {
"BUFF": UiKitScript.TG_GLOW_BUFF, "HEAL": UiKitScript.TG_GLOW_HEAL,
"PIERCE": UiKitScript.TG_GLOW_PIERCE, "MAGIC": UiKitScript.TG_GLOW_MAGIC,
"AOE": UiKitScript.TG_GLOW_AOE,
}
const CONST_NAME := {
"BUFF": "TG_GLOW_BUFF", "HEAL": "TG_GLOW_HEAL", "PIERCE": "TG_GLOW_PIERCE",
"MAGIC": "TG_GLOW_MAGIC", "AOE": "TG_GLOW_AOE",
}
func _init() -> void:
_test_grouping_closed()
_test_frozen_hue_face()
_test_render_params()
_test_gamma_ladder()
_test_probe_api_shape()
quit(Kit.finish("type_glow_test"))
# ① 归组封闭:九值全域映射+清单外显式失败+禁子串+两带差异锚定
func _test_grouping_closed() -> void:
for t in TYPE_EXPECT:
var got: int = UiKitScript.glow_group_for_cell_type(t)
Kit.check("九型封闭:%s → 组%d(期望组%d)" % [t, got, int(TYPE_EXPECT[t])], got == int(TYPE_EXPECT[t]))
# 清单外值 = GLOW_NONE(调用方显式登记,禁静默空光;大宝箱未来第十型落数据时必须同步加归组,
# 否则本通道报红——这是该纪律的机检防线)
for bad in ["keystone", "", "ATTACK", "aoe", "blast_mine", " heal"]:
Kit.check("清单外值「%s」→ GLOW_NONE(禁静默空光)" % bad,
UiKitScript.glow_group_for_cell_type(bad) == UiKitScript.GLOW_NONE)
# 与卡顶带 tg_band 的归组差异锚定(Q3 甲案两带并存、禁互引):blast 在格面带归群伤、
# 在卡顶带归魔法(E5 回滚既存行为)——若将来误把两带统一,本断言红
var band_blast: Color = UiKitScript.tg_band("blast_charge")
Kit.check("两带并存锚定:卡顶带 blast→TG_MAGIC(E5 回滚既存行为未动)", band_blast == UiKitScript.TG_MAGIC)
Kit.check("两带并存锚定:格面微光 blast→群伤组(与卡顶带刻意不同)",
UiKitScript.glow_group_for_cell_type("blast") == UiKitScript.GLOW_AOE)
# 色值出口:五组各取本组常量;无组/越界组号 → 全透明(不画)
Kit.check("glow_color_for_group 群伤组 = TG_GLOW_AOE",
UiKitScript.glow_color_for_group(UiKitScript.GLOW_AOE) == UiKitScript.TG_GLOW_AOE)
Kit.check("glow_color_for_group 无组(GLOW_NONE)→ 全透明",
UiKitScript.glow_color_for_group(UiKitScript.GLOW_NONE) == Color(0, 0, 0, 0))
Kit.check("glow_color_for_group 越界组号 → 全透明",
UiKitScript.glow_color_for_group(99) == Color(0, 0, 0, 0))
# ② 冻结色相面:逐组 HSV 对照 art-bible §4.2 冻结值——只准动明度(ΔH≤0.2°/ΔS≤0.01)
func _test_frozen_hue_face() -> void:
for g in FROZEN:
var f: Color = FROZEN[g]
var m: Color = IMPL[g]
var impl_const: Color = IMPL_CONSTS[g]
Kit.check("实现值锚定:%s = #%02X%02X%02X(色值单点 ui_kit,逐位钉死)" % [CONST_NAME[g],
int(m.r8), int(m.g8), int(m.b8)], impl_const == m)
var dh: float = absf(m.h - f.h)
dh = minf(dh, 1.0 - dh) # 色相环回绕
var ds: float = absf(m.s - f.s)
Kit.check("冻结色相保持:%s ΔH=%.3f° ≤0.2° 且 ΔS=%.4f ≤0.01(只动明度档)" % [g, dh * 360.0, ds],
dh <= 0.2 / 360.0 and ds <= 0.01)
# 群伤组专锚(2026-10-17 停机呈报→用户裁 #A61D37):H/S 与冻结逐位一致、V=0.65 档
var aoe_f: Color = FROZEN["AOE"]
var aoe_m: Color = UiKitScript.TG_GLOW_AOE
Kit.check("群伤组专项:H 与冻结 #FF2D55 差 %.4f°(零移动)" % (absf(aoe_m.h - aoe_f.h) * 360.0),
absf(aoe_m.h - aoe_f.h) < 0.001)
Kit.check("群伤组专项:S 与冻结差 %.4f ≤0.01(8bit 量化噪声级)" % absf(aoe_m.s - aoe_f.s),
absf(aoe_m.s - aoe_f.s) <= 0.01)
Kit.check("群伤组专项:V=0.65 明度档( provisional 域内)", absf(aoe_m.v - 166.0 / 255.0) < 0.002)
# ③ 呈现参数锚定(art-bible §4.2 v2 冻结参数)
func _test_render_params() -> void:
Kit.check("泛光强度 α = 170/255(v2 呈现参数冻结)",
is_equal_approx(UiKitScript.TYPE_GLOW_ALPHA, 170.0 / 255.0))
Kit.check("带宽 = cell÷8(TYPE_GLOW_BAND_FRAC=0.125)",
is_equal_approx(UiKitScript.TYPE_GLOW_BAND_FRAC, 0.125))
Kit.check("组号常量域连续(GLOW_NONE=0…GLOW_MAGIC=5)",
UiKitScript.GLOW_NONE == 0 and UiKitScript.GLOW_BUFF == 1 and UiKitScript.GLOW_HEAL == 2
and UiKitScript.GLOW_PIERCE == 3 and UiKitScript.GLOW_AOE == 4 and UiKitScript.GLOW_MAGIC == 5)
# ④ 合成空间阶梯:模型域单调 + 相邻组 CR ≥1.20(色弱互辨数据面预判;终审=探针实测)
func _test_gamma_ladder() -> void:
# 合成模型(ui_kit 头注登记口径):带内均值 ≈ 0.49×色值 + 0.51×瓦片(BRICK 未开掩尘地砖)
var k := 0.49
var tile: Color = UiKitScript.BRICK
var comp := func(c: Color) -> Color:
return Color(k * c.r + (1.0 - k) * tile.r, k * c.g + (1.0 - k) * tile.g, k * c.b + (1.0 - k) * tile.b)
var order := ["BUFF", "HEAL", "PIERCE", "MAGIC", "AOE"]
var prev_lum := 2.0
for g in order:
var cc: Color = comp.call(IMPL[g])
var lum: float = _rel_lum709(cc)
Kit.check("阶梯单调:%s 合成亮度 %.4f < 上一档 %.4f(伽马合成空间)" % [g, lum, prev_lum], lum < prev_lum)
prev_lum = lum
for i in range(order.size() - 1):
var a: Color = comp.call(IMPL[order[i]])
var b: Color = comp.call(IMPL[order[i + 1]])
var cr: float = _wcag_cr(a, b)
Kit.check("相邻组模型 CR:%s/%s = %.3f ≥1.20(色弱互辨预判)" % [order[i], order[i + 1], cr], cr >= 1.20)
# 首版排反的那一对专项:群伤线性亮度必须低于魔法(线性域序 = WCAG 判据域)
var aoe_lin: float = _rel_lum_lin(comp.call(IMPL["AOE"]))
var magic_lin: float = _rel_lum_lin(comp.call(IMPL["MAGIC"]))
Kit.check("群伤/魔法线性亮度序:群伤 %.4f < 魔法 %.4f(首版 CIE L* 排反事故的守门)" % [aoe_lin, magic_lin],
aoe_lin < magic_lin)
# ⑤ probe API 形状:默认开+空态键集(无板直取——不依赖棋盘装配)
func _test_probe_api_shape() -> void:
var view = BoardViewScript.new()
var snap: Dictionary = view.type_glow_probe()
Kit.check("probe 键集 = {enabled, cells, errors}",
snap.has("enabled") and snap.has("cells") and snap.has("errors") and snap.size() == 3)
Kit.check("probe 默认态:enabled=true(Q4 丙案生产默认开)", bool(snap.get("enabled", false)))
Kit.check("probe 默认态:作用集空(无板未派生)", (snap.get("cells", {}) as Dictionary).is_empty())
Kit.check("probe 默认态:清单外登记空", (snap.get("errors", []) as Array).is_empty())
view.set_type_glow_enabled(false)
Kit.check("set_type_glow_enabled(false) → probe enabled=false(无板不崩)",
not bool(view.type_glow_probe().get("enabled", true)))
view.free()
# Rec.709 相对亮度(sRGB 值域——灰度互辨通道读数,与 shot_checker 同口径)
static func _rel_lum709(c: Color) -> float:
return 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b
# WCAG 线性域相对亮度(反伽马)
static func _rel_lum_lin(c: Color) -> float:
var lin := func(ch: float) -> float:
return ch / 12.92 if ch <= 0.04045 else pow((ch + 0.055) / 1.055, 2.4)
return 0.2126 * lin.call(c.r) + 0.7152 * lin.call(c.g) + 0.0722 * lin.call(c.b)
# WCAG 对比对(线性域 +0.05 偏移)
static func _wcag_cr(a: Color, b: Color) -> float:
var la: float = _rel_lum_lin(a)
var lb: float = _rel_lum_lin(b)
return (maxf(la, lb) + 0.05) / (minf(la, lb) + 0.05)
@@ -0,0 +1 @@
uid://bykt02pgt6mbf