Files
gplan-2/game/tests/unit/presentation/camp_scene_layout_test.gd
T
DKChan 4b5e3f27e8 卡④T2(迭代47·营地场景图批): CampSceneView场景视图+camp_scene_layout.tres布局表+V14单测——场景视图/布局坐标/V14三面落地
- schema两件: CampSceneLayerSpec(层键+贴图矩形+命中矩形+命中键)+CampSceneLayoutTable(canvas/hud_band_h/gate_gap/wall_band/layers+validate()按面拆四查:身份贴图/命中边界/命中互斥/围墙带包络)
- 数据表camp_scene_layout.tres: 10层坐标唯一事实源(布局草案值+§5已裁:布告板(845,268,130,140)/门扇(520,250,240,220)门洞对齐/帐篷300x170;贴图矩形=contain终稿实测尺寸锚定:底中心/顶锚/底锚按物件语义,campfire命中(560,640,160,80)只取下半)
- ConfigDB接线: 表注册+表现域容器camp_scene_layout字段+装载期validate()逐问题_fatal(fail-fast红线:命中区越画面/相交/侵顶栏=数据致命)
- CampSceneView: setup_layers/bind_hotspots/get_hotspot_rect/set_feedback_state/set_hud_band五公开口+object_pressed信号; Control根+TextureRect层子节点序=z序(禁CanvasLayer/z_index/Sprite2D); 根单点_gui_input命中判定(层全IGNORE); 四态反馈:悬停=Candle轮廓+名牌(FS_HINT/INK_ON_DEEP/BG_ROOT板,画面内钳制)+Tween逐级整像素浮动/按下=位移1px/禁用=ShaderMaterial去饱和+降明度(零新素材,modulate只有明度轴); 素材缺位程序化占位冒烟安全; 命中键=七物件绑定(gate/forge/dummy/chest/notebook/board/campfire)
- V14单测camp_scene_layout_test.gd(11断言): 校验函数通过/七键集/两两不相交/不越画面不侵顶栏/篝火只下半/门扇对齐缺口/布告板挂墙带/10层序/ConfigDB装载链——全表关系谓词零坐标字面量; run_all manifest登记expect=0
- 验证: --editor --quit刷类缓存后V14孤跑绿(11项)+视图孤检绿(9项:子节点序/合成点击信号/悬停名牌/拖出取消/禁用不发/HUD槽——临时件不入库,T5探针承载正式验收); S9/S13归基线(gdstyle新违规4条已按职责轴拆函数清偿:validate按面四拆/_gui_input三拆/setup_layers构建段三拆/循环内分配提_build_layer_node)
2026-11-01 17:27:42 +08:00

90 lines
4.9 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:
call_deferred("_test_layout_hotspot_geometry")
func _test_layout_hotspot_geometry() -> void:
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:
quit(Kit.finish("camp_scene_layout_test"))
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)
quit(Kit.finish("camp_scene_layout_test"))