- 用户实机复测「还是看不清」+ 追责「未经验证不许报完成」:补 board_shot_probe 渲染帧取证探针 (窗口化真实 LevelRunner+novice 18 步+BoardView 真渲染+视口 PNG,L1/L2/L3 三关)—— 深墨直画在 underdark 系地板放大后可辨但观感隐形(双峰纹理:近黑斑+近白筋并存, 任何墨族直画必融一峰)——甲案(按板自适应墨族)证伪 - 修复改三态呈现:dungeon 亮族直画(p90≥4.5/均值≥2.5)/中暗板深墨直画(均值门收紧 3.0)/ 双峰板色牌垫底(素牌+深墨 12:1 纹理免疫——v2 色牌形制复役) - 目视验证(本会话亲验渲染帧):L2/L3 数字素牌深墨清晰可读,取证图 artwork/qa-board-shots-20261001/(board_L1/L2/L3 + _zoom_L2 证伪对照) - 机检同步:terrain_tile_sets_test 三态断言 19/19;art-bible v3.4(三态+目视验证面入流程); 冒烟断言随新语义更新(雷当量 6/8/8 钉值/大奖励与 L3 上限允许模拟档购血叠加——v3.4 购物 改变 max_hp 序列语义,非功能回归) - 轻闸:冒烟 596/0+四探针 PASS;剂量-响应点(slip 半档 N=100)L1 36%/战役 1%——悬崖三点齐
176 lines
8.0 KiB
GDScript
176 lines
8.0 KiB
GDScript
# tests/unit/presentation/terrain_tile_sets_test.gd — 瓦片换色机检(T6 卡交付:色相域抽检+套完整性)
|
||
# 运行:godot --headless --path game --script res://tests/unit/presentation/terrain_tile_sets_test.gd
|
||
# 事实源:board_gen.tres board_type 词表 v1.1(dungeon/underdark/deep_underdark——从表读值,禁写死);
|
||
# 色相域方向 = art-bible §4.3 三关方向(underdark 灰白蓝紫 / deep_underdark 蓝黑紫黑);
|
||
# 图底分离 = 圣经 §8.5(已开地面明度低于未开墙体)。
|
||
# 前置守卫:素材不入 git(用户裁定)——fresh checkout 无瓦片文件时本套件跳过(打印注记,不红);
|
||
# 源 dungeon 套在盘而换色套缺位 = 回归(换色脚本在库可复现)。
|
||
extends SceneTree
|
||
|
||
const ConfigDBScript := preload("res://core/config_db.gd")
|
||
const BoardViewScript := preload("res://presentation/views/board_view.gd")
|
||
|
||
var _failures := 0
|
||
var _checks := 0
|
||
|
||
|
||
func _init() -> void:
|
||
var board_dir := ProjectSettings.globalize_path("res://assets/art/board/")
|
||
var probe_path := board_dir.path_join("terrain_wall_center_dungeon.png")
|
||
if not FileAccess.file_exists(probe_path):
|
||
print("[terrain_tile_sets_test] SKIP——素材工作区缺位(瓦片不入 git 纪律,fresh checkout 预期态)")
|
||
quit(0)
|
||
return
|
||
var db = ConfigDBScript.new()
|
||
var load_ok: bool = db.load_tables("res://data/tables", true)
|
||
_check("前置:表加载零 error", load_ok and db.diagnostics_count("error") == 0)
|
||
if not load_ok:
|
||
quit(1)
|
||
return
|
||
# board_type 词表自 board_gen 行集推导(禁测试侧写死词表)
|
||
var types: Array = []
|
||
for row in db.board_gen.rows:
|
||
var bt := String(row.get("board_type", ""))
|
||
if bt != "" and not types.has(bt):
|
||
types.append(bt)
|
||
_check("前置:board_gen 词表非空(%d 型)" % types.size(), types.size() > 0)
|
||
_test_set_completeness(board_dir, types)
|
||
_test_hue_domains(board_dir)
|
||
_test_digit_ink_modes(board_dir, types)
|
||
if _failures > 0:
|
||
push_error("[terrain_tile_sets_test] %d/%d 项失败" % [_failures, _checks])
|
||
quit(1)
|
||
return
|
||
print("[terrain_tile_sets_test] all green (%d checks)" % _checks)
|
||
quit(0)
|
||
|
||
|
||
func _check(desc: String, ok: bool) -> void:
|
||
_checks += 1
|
||
if ok:
|
||
print(" [PASS] " + desc)
|
||
else:
|
||
_failures += 1
|
||
push_error(" [FAIL] " + desc)
|
||
|
||
|
||
# 套完整性:词表每个 board_type 的 16 墙 + 5 地面变体全部在盘(缺位 → BoardView 整批回落 dungeon 的
|
||
# 缺位回退断言由 ui_input_probe 覆盖;本断言管「入包完整」面)
|
||
func _test_set_completeness(board_dir: String, types: Array) -> void:
|
||
var wall_names: Array = BoardViewScript.TILE_WALL_NAMES
|
||
var variants: int = int(BoardViewScript.TILE_FLOOR_VARIANTS)
|
||
for bt in types:
|
||
var missing: Array = []
|
||
for w in wall_names:
|
||
var p := board_dir.path_join("terrain_%s_%s.png" % [String(w), String(bt)])
|
||
if not FileAccess.file_exists(p):
|
||
missing.append(String(w))
|
||
for i in range(1, variants + 1):
|
||
var pf := board_dir.path_join("terrain_floor_%s_%d.png" % [String(bt), i])
|
||
if not FileAccess.file_exists(pf):
|
||
missing.append("floor_%d" % i)
|
||
_check("图型 %s 瓦片套完整(16 墙 + %d 地面,缺=%s)" % [String(bt), variants, str(missing)], missing.is_empty())
|
||
|
||
|
||
# 色相域抽检:wall_center 上半幅(墙顶面)与 floor_1 全幅的均值色相/明度
|
||
func _test_hue_domains(board_dir: String) -> void:
|
||
var bands := {
|
||
"dungeon": [0.02, 0.16], # 暖石棕 ≈15°–58°
|
||
"underdark": [0.50, 0.72], # 灰白蓝紫 ≈180°–260°
|
||
"deep_underdark": [0.55, 0.78], # 蓝黑紫黑 ≈200°–280°
|
||
}
|
||
var wall_v := {}
|
||
var floor_v := {}
|
||
for bt in bands.keys():
|
||
var wall := _load_stats(board_dir, "terrain_wall_center_%s.png" % String(bt), true)
|
||
var floor_img := _load_stats(board_dir, "terrain_floor_%s_1.png" % String(bt), false)
|
||
if wall.is_empty() or floor_img.is_empty():
|
||
_check("图型 %s 抽检样本可读" % String(bt), false)
|
||
continue
|
||
wall_v[bt] = float(wall["v"])
|
||
floor_v[bt] = float(floor_img["v"])
|
||
var band: Array = bands[bt]
|
||
_check("图型 %s 墙顶均值色相在带 %s(实测 %.3f)" % [String(bt), str(band), float(wall["h"])],
|
||
float(wall["h"]) >= float(band[0]) and float(wall["h"]) <= float(band[1]))
|
||
_check("图型 %s 图底分离(墙顶明度 %.3f > 地面 %.3f)" % [String(bt), wall_v[bt], floor_v[bt]],
|
||
wall_v[bt] > floor_v[bt])
|
||
if wall_v.has("underdark") and wall_v.has("deep_underdark"):
|
||
_check("deep_underdark 墙顶更暗于 underdark(更深蓝黑方向)", float(wall_v["deep_underdark"]) < float(wall_v["underdark"]))
|
||
|
||
|
||
# 均值读数:hue 取饱和度 > 0.05 的像素(墨线/灰黑不影响色相均值);v 取全幅均值
|
||
func _load_stats(board_dir: String, fname: String, top_half: bool) -> Dictionary:
|
||
var p := board_dir.path_join(fname)
|
||
if not FileAccess.file_exists(p):
|
||
return {}
|
||
var img := Image.load_from_file(p)
|
||
if img == null:
|
||
return {}
|
||
if img.is_compressed():
|
||
img.decompress()
|
||
var w := img.get_width()
|
||
var h := img.get_height()
|
||
var y0 := 0
|
||
var y1 := h
|
||
if top_half:
|
||
y1 = h / 2 # 墙顶面 = 上半幅(立面暗带不参与色相均值)
|
||
var h_sum := 0.0
|
||
var v_sum := 0.0
|
||
var n_h := 0
|
||
var n_v := 0
|
||
for y in range(y0, y1):
|
||
for x in range(w):
|
||
var c := img.get_pixel(x, y)
|
||
v_sum += c.v
|
||
n_v += 1
|
||
if c.s > 0.05:
|
||
h_sum += c.h
|
||
n_h += 1
|
||
if n_v == 0 or n_h == 0:
|
||
return {}
|
||
return {"h": h_sum / n_h, "v": v_sum / n_v}
|
||
|
||
|
||
# 数字呈现模式对比断言(L2/L3 亮瓦融字缺陷修复 2026-10-01——甲案机检面,v3.4 扩三态):
|
||
# 每个图型走 BoardView 同一归一+测光+选模式管线(禁测试侧另写算法),断言:
|
||
# ①dungeon=亮墨族直画(对 p90 ≥4.5 且对均值 ≥2.5);②underdark/deep_underdark=色牌垫底
|
||
# (双峰纹理直画不可救——渲染帧取证深墨隐形实证,牌上深墨 12:1);③直画模式的双门槛值照抄
|
||
# BoardView 判据常量(防判据漂移)
|
||
func _test_digit_ink_modes(board_dir: String, types: Array) -> void:
|
||
var expect_plate := {"dungeon": false, "underdark": true, "deep_underdark": true}
|
||
var variants: int = int(BoardViewScript.TILE_FLOOR_VARIANTS)
|
||
for bt in types:
|
||
var lumas: Array = []
|
||
var readable := true
|
||
for i in range(1, variants + 1):
|
||
var p := board_dir.path_join("terrain_floor_%s_%d.png" % [String(bt), i])
|
||
if not FileAccess.file_exists(p):
|
||
readable = false
|
||
break
|
||
var tex: Texture2D = ImageTexture.create_from_image(Image.load_from_file(p))
|
||
BoardViewScript._normalize_floor_luma(tex, 0.10, lumas)
|
||
if not readable:
|
||
_check("图型 %s 数字墨族断言样本可读" % String(bt), false)
|
||
continue
|
||
var mode: Dictionary = BoardViewScript._digit_ink_mode(lumas)
|
||
var plate: bool = bool(mode["plate"])
|
||
var dark: bool = bool(mode["dark"])
|
||
var mode_name := "色牌垫底" if plate else ("深墨族直画" if dark else "亮墨族直画")
|
||
if plate:
|
||
_check("图型 %s 双峰纹理落牌(亮族 p90 %.2f/均值 %.2f,深族 p90 %.2f/均值 %.2f 均未过门)" % [
|
||
String(bt), float(mode["cr_p90_bright"]), float(mode["cr_mean_bright"]),
|
||
float(mode["cr_p90_dark"]), float(mode["cr_mean_dark"])],
|
||
float(mode["cr_mean_dark"]) < float(BoardViewScript.DIGIT_INK_CR_MEAN_DARK))
|
||
else:
|
||
var cr_p90 := float(mode["cr_p90_bright"]) if not dark else float(mode["cr_p90_dark"])
|
||
var cr_mean := float(mode["cr_mean_bright"]) if not dark else float(mode["cr_mean_dark"])
|
||
_check("图型 %s 所选%s 对 p90 ≥%.1f(实测 %.2f)" % [String(bt), mode_name,
|
||
float(BoardViewScript.DIGIT_INK_CR_P90), cr_p90],
|
||
cr_p90 >= float(BoardViewScript.DIGIT_INK_CR_P90))
|
||
var mean_gate := float(BoardViewScript.DIGIT_INK_CR_MEAN_BRIGHT) if not dark else float(BoardViewScript.DIGIT_INK_CR_MEAN_DARK)
|
||
_check("图型 %s 所选%s 对均值 ≥%.1f(实测 %.2f)" % [String(bt), mode_name, mean_gate, cr_mean],
|
||
cr_mean >= mean_gate)
|
||
if expect_plate.has(bt):
|
||
_check("图型 %s 呈现模式期望=%s(实测=%s)" % [String(bt),
|
||
"色牌" if bool(expect_plate[bt]) else "直画", mode_name], plate == bool(expect_plate[bt]))
|