Files
gplan-2/game/tests/unit/presentation/camp_scene_layout_test.gd
T

148 lines
7.7 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.
extends SceneTree
# 营地场景布局数据件校验套件(V14——camp_scene_layout.tres 加载期 fail-fast 判据+七命中区几何红线机检)
# 覆盖:校验函数通过(键唯一/贴图在画布/命中成对/围墙带包络)· 七可点件键集 · 两两不相交 ·
# 不越画面 · 不侵顶栏禁区 · 篝火命中只取下半(关系谓词)· 门扇对齐门洞缺口 · 布告板挂围墙带。
# 断言全部为表内关系谓词——禁坐标字面量(数值单一事实源 = 数据行本表)。
# 运行:godot --headless --path game --script res://tests/unit/presentation/camp_scene_layout_test.gd(expect 0)
const Kit := preload("res://tests/unit/test_kit.gd")
const _EXPECTED_KEYS: Array[StringName] = [&"gate", &"forge", &"dummy", &"chest", &"notebook", &"board", &"campfire"]
func _initialize() -> void:
# oracle: design/gdd/ux/camp.md:29 (营地场景布局数据件——纵深与地平线组)
call_deferred("_run_all")
func _run_all() -> void:
# oracle: design/gdd/ux/camp.md:29 (套件统一收尾——双测试函数单进程单 quit)
await _test_layout_hotspot_geometry()
await _test_hud_band_contrast_over_bg()
quit(Kit.finish("camp_scene_layout_test", "PASS — 布局几何+顶栏栏体对比 全过"))
func _test_layout_hotspot_geometry() -> void:
# oracle: design/gdd/ux/camp.md:16 (七物件命中区几何红线组)
var table: CampSceneLayoutTable = load("res://data/tables/camp_scene_layout.tres")
Kit.check("V14 布局表加载且 schema 类在位〔营地图批 V14〕", table is CampSceneLayoutTable and table != null)
if table == null:
return
var problems: Array[String] = table.validate()
Kit.check("V14 校验函数通过(问题清单空,实得 %s)〔营地图批 V14〕" % [problems], problems.is_empty())
var clickables: Array[CampSceneLayerSpec] = []
for spec in table.layers:
if spec.is_clickable():
clickables.append(spec)
Kit.check("V14 可点件数=7(实得 %d)〔营地图批 V14〕" % clickables.size(), clickables.size() == 7)
var got_keys: Array[StringName] = []
for spec in clickables:
got_keys.append(spec.hotspot_key)
got_keys.sort()
var want_keys: Array[StringName] = []
for k in _EXPECTED_KEYS:
want_keys.append(k)
want_keys.sort()
Kit.check("V14 命中键集=七物件绑定表(实得 %s)〔营地图批 V14〕" % [got_keys], got_keys == want_keys)
# 两两不相交(Rect2.intersects——贴边相触不算相交)
var disjoint := true
var pair := ""
for i in clickables.size():
for j in range(i + 1, clickables.size()):
if clickables[i].hotspot.intersects(clickables[j].hotspot):
disjoint = false
pair = "%s×%s" % [clickables[i].key, clickables[j].key]
Kit.check("V14 七命中区两两不相交(首违例 %s)〔营地图批 V14〕" % pair, disjoint)
# 不越画面+不侵顶栏禁区
var in_bounds := true
var offender := ""
for spec in clickables:
if spec.hotspot.position.x < 0.0 or spec.hotspot.position.y < table.hud_band_h \
or spec.hotspot.end.x > table.canvas.x or spec.hotspot.end.y > table.canvas.y:
in_bounds = false
offender = String(spec.key)
Kit.check("V14 命中区不越画面且不侵顶栏禁区(y≥%d,违例 %s)〔营地图批 V14〕" % [table.hud_band_h, offender], in_bounds)
# 篝火命中只取下半:命中区贴贴图底、严格小于贴图、起点在贴图内(火焰上段不可点)
var fire: CampSceneLayerSpec = null
var gate: CampSceneLayerSpec = null
var board: CampSceneLayerSpec = null
var wall: CampSceneLayerSpec = null
for spec in table.layers:
match String(spec.key):
"campfire": fire = spec
"gate": gate = spec
"board": board = spec
"wall": wall = spec
var fire_ok := fire != null and fire.hotspot.end.y == fire.texture_rect.end.y \
and fire.hotspot.size.y < fire.texture_rect.size.y \
and fire.hotspot.position.y > fire.texture_rect.position.y
Kit.check("V14 篝火命中只取下半(火堆贴底、火焰不点)〔营地图批 V14〕", fire_ok)
# 门扇命中对齐门洞缺口(表内 gate_gap 字段关系谓词)
var gate_ok := gate != null and gate.hotspot.position.x == float(table.gate_gap.x) \
and gate.hotspot.end.x == float(table.gate_gap.y)
Kit.check("V14 门扇命中对齐门洞缺口 %s〔营地图批 V14〕" % [table.gate_gap], gate_ok)
# 布告板挂围墙带:命中顶落在围墙贴图带内
var board_ok := board != null and wall != null \
and board.hotspot.position.y >= wall.texture_rect.position.y \
and board.hotspot.position.y <= wall.texture_rect.end.y
Kit.check("V14 布告板命中挂围墙带〔营地图批 V14〕", board_ok)
# 层序结构:10 层且 bg/wall 压前两位(z0/z1——子节点序=表序先验)
var order_ok := table.layers.size() == 10 \
and String(table.layers[0].key) == "bg" and String(table.layers[1].key) == "wall"
Kit.check("V14 层数=10 且 bg/wall 占 z0/z1〔营地图批 V14〕", order_ok)
# ConfigDB 装载链:表经单点注册进表现域容器且装载期校验过(fail-fast 钩子在位)
var db = load("res://core/config_db.gd").new()
var load_ok: bool = db.load_tables("res://data/tables", true)
Kit.check("V14 ConfigDB 装载含营地布局表且校验过(is_ready=%s)〔营地图批 V14〕" % db.is_ready,
load_ok and db.is_ready and db.presentation.camp_scene_layout != null)
# 顶栏栏体规格侧对比核验(R6——与 R5 分工:R5 核实装等值,本行核规格值本身达标):
# 按规格句在测试内写死 #6E4526 与 α0.80,与背景贴图顶带实测均色做 α 合成后算 WCAG 对比 ≥3.0。
# 禁把视图件的 color 直接当预期值喂进算式(oracle 镜像——test-conventions §8.7 判红)。
func _test_hud_band_contrast_over_bg() -> void:
# oracle: design/gdd/ux/camp.md:41 (顶栏栏体定值——对落位带底色对比 ≥3:1 组)
var table: CampSceneLayoutTable = load("res://data/tables/camp_scene_layout.tres")
var band_h := 48
if table != null:
band_h = int(table.hud_band_h) # 带高取数据出口(禁字面量)
var tex = load("res://assets/art/scene/scene_camp_bg.png")
var img: Image = null
if tex != null and tex.get_image() != null:
img = tex.get_image()
var scan_n := 0
var r_sum := 0.0
var g_sum := 0.0
var b_sum := 0.0
if img != null:
for y in range(0, mini(band_h, img.get_height())):
for x in range(img.get_width()): # 双层全像素扫描(非步长抽样——terrain_tile_sets_test 同款)
var c := img.get_pixel(x, y)
r_sum += c.r
g_sum += c.g
b_sum += c.b
scan_n += 1
Kit.check("R6 扫描面非空前置(顶带像素 %d > 0——贴图装载且带高在界)〔营地交互批 V5〕" % scan_n, scan_n > 0)
if scan_n == 0:
return
var bg := Color(r_sum / float(scan_n), g_sum / float(scan_n), b_sum / float(scan_n))
# 规格侧算式(camp.md:41 写死——#6E4526 × α0.80 对落位带背景合成)
var fg := Color(0x6e / 255.0, 0x45 / 255.0, 0x26 / 255.0, 0.80)
var mixed := Color(
fg.r * fg.a + bg.r * (1.0 - fg.a),
fg.g * fg.a + bg.g * (1.0 - fg.a),
fg.b * fg.a + bg.b * (1.0 - fg.a))
var lum_fg: float = _wcag_lum(mixed)
var lum_bg: float = _wcag_lum(bg)
var ratio: float = (maxf(lum_fg, lum_bg) + 0.05) / (minf(lum_fg, lum_bg) + 0.05)
print("[camp_scene_layout_test] 顶带均色=%s 合成后=%s WCAG 对比=%.2f:1(规格 ≥3:1)" % [bg, mixed, ratio])
Kit.check("R6 栏体合成色对落位带底色对比 ≥3.0(实测 %.2f)〔营地交互批 V5〕" % ratio, ratio >= 3.0)
## WCAG 相对亮度(sRGB 线性化——命名函数形态,避多行 lambda 嵌套三元解析坑)
func _wcag_lum(c: Color) -> float:
return 0.2126 * _srgb_lin(c.r) + 0.7152 * _srgb_lin(c.g) + 0.0722 * _srgb_lin(c.b)
func _srgb_lin(v: float) -> float:
return v / 12.92 if v <= 0.03928 else pow((v + 0.055) / 1.055, 2.4)