卡④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)
This commit is contained in:
DKChan committed 2026-11-01 17:27:42 +08:00
1 parent aa4aa1dd20
commit 4b5e3f27e8
12 files changed
+566 -2

No files matched your search

@@ -0,0 +1,89 @@
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"))
@@ -0,0 +1 @@
uid://ckyd57l11408