iter54-卡2:营地交互修复批(开局隐藏点收一处 _start_adventure+确认弹窗按钮位次镜像确认→次→取消+营地顶栏可见栏体 camp_hud_band_surface 单参出口 CAMP_HUD_BAND×0.80)——对照表 R1-R8 全绿(探针 R1/R2/R4 落点断言/confirm_dialog_test 七断言/系统测试 R5 五钉 76 检查 0 失败/R6 WCAG 4.29:1),S13 ui_kit 基线 23→24 随卡预申报修订

This commit is contained in:
DKChan committed 2026-11-07 03:05:11 +08:00
1 parent 96123978c1
commit 4c424af358
12 files changed
+278 -17

No files matched your search

+3 -2
View File
@@ -190,8 +190,8 @@ func _start_adventure_from_menu() -> void:
# 主菜单「开始冒险」钮已随三入口化撤出(卡② mn6——主菜单侧断点互斥确认链同步摘除,
# 弹窗本体与两分支逻辑归卡④大门承接)。本函数为探针直调中间态保留(mn10——冒险段探针
# 直调位点逐个登记实现报告「已知中间态」;卡④到位后由大门两分支换代)。无激活态写盘拒绝
# 由卡① sv3 承接(结算不落盘)。
ui.screens.start_overlay.visible = false
# 由卡① sv3 承接(结算不落盘)。开始层隐藏已收进 _start_adventure()(营地交互修复批契约 1
# ——开局路径隐藏点唯一出口)。
_start_adventure()
@@ -290,6 +290,7 @@ static func build_adventure_params(save_manager_ref: SaveManagerCore, meta_ref:
# 冒险模式(三关连探——AdventureRunner 编排;转发信号签名与 runner 一致,接线照原样对接;
# 信号连接只在组合根)
func _start_adventure() -> void:
ui.screens.start_overlay.visible = false # 开局路径开始层隐藏唯一出口(营地交互修复批契约 1——三处调用方经此收口;失败分支开始层终态=隐藏,致命屏接管)
ui.flow.close(ui.modals.menu.debug_overlay) # 开冒险路径显式关浮窗(调试浮窗默认开态下开局不带窗;对局中经角标再开)
begin_session(true)
_wire_adventure()
+8 -3
View File
@@ -11,10 +11,15 @@ func _init() -> void:
set_anchors_preset(Control.PRESET_TOP_WIDE)
custom_minimum_size = Vector2(0, 48) # 高 48px 浮层覆盖、不挤压画面(camp.md §顶栏)
mouse_filter = Control.MOUSE_FILTER_IGNORE # HUD 栏不挡场景命中区(浮层覆盖语义)
UiKit.camp_hud_band_surface(self) # 栏体色面——整件外观单一出口(色值与 α、铺满、不吃点击、挂载全在出口内);先入=绘制序底,徽章后入不被盖
_badge = UiKit.keystone_badge("钥石 0")
_badge.set_anchors_preset(Control.PRESET_CENTER_RIGHT)
_badge.position = Vector2(-160, 8) # 顶栏右位次(UiKit 现行件域内——代拍板 3)
add_child(_badge)
var dock := HBoxContainer.new() # 右对齐容器承载徽章(Q2 已裁乙——位数增宽自然向左展;摆位机制先例=confirm_dialog 钮行同款容器 alignment)
dock.set_anchors_preset(Control.PRESET_FULL_RECT)
dock.offset_right = -12.0 # 右缘内缩 12px(UiKit 钮间距同档——白名单 3)
dock.alignment = BoxContainer.ALIGNMENT_END
dock.mouse_filter = Control.MOUSE_FILTER_IGNORE # 容器自身默认 STOP——与出口内 IGNORE 是两个点,必须同批落(漏一处顶排物件点不动)
dock.add_child(_badge)
add_child(dock)
## 余额写入(格式串=既有消费面同款「钥石 %d」——badge 工厂约定单点,禁第二格式串)
+4 -2
View File
@@ -30,13 +30,15 @@ func _init() -> void:
box.add_child(btn_row)
_cancel_btn = UiKit.button("取消")
_cancel_btn.pressed.connect(_emit_cancelled)
btn_row.add_child(_cancel_btn)
_secondary_btn = UiKit.button("次选项")
_secondary_btn.pressed.connect(_emit_secondary)
btn_row.add_child(_secondary_btn)
_confirm_btn = UiKit.button("确认")
_confirm_btn.pressed.connect(_emit_confirmed)
# 位次定值(HUD框架.md:90 三条纪律·其三——推进钮在左、次推进居中、维持在右;四处消费方
# 共用本件同一序。信号按按钮对象路由、不按位次——换序不改路由〔营地交互修复批契约 3〕)
btn_row.add_child(_confirm_btn)
btn_row.add_child(_secondary_btn)
btn_row.add_child(_cancel_btn)
visible = false
+14
View File
@@ -193,6 +193,7 @@ const TIP_BG := Color(0.16, 0.12, 0.09, 0.96) # tooltip 深底
const STAND_STONE := Color8(150, 130, 100) # 站桩石板地台·板身(standee_body 方向稿取色)
const PLAZA_EDGE := Color8(76, 64, 56) # 小石台描边(art-bible §4.1 换色合并②合并目标——贴图已落此值,缺位回退同值)
const CAMP_HEADER_BAND := Color8(110, 69, 38) # 营地柜台头带深棕 #6E4526(4-camp.png 方向稿取色;对齐 demo --wood-deep)
const CAMP_HUD_BAND := Color(CAMP_HEADER_BAND, 0.80) # 营地顶栏栏体=头带深棕×α0.80(camp.md §顶栏定值——派生收口单点,非色板新增;同文件 TOAST_BG/TIP_BG 带 α 先例)
const CAMP_ROW_PLATE := Color8(240, 230, 206) # 营地行板暖米(同上)
const CAMP_BADGE_PANEL := Color8(40, 32, 24) # 营地钥石徽暗底(同上)
const HP_BAR_BACK := Color8(40, 34, 32) # 迷你血条深底 #282220(兼血数垫牌;站桩名字行素底同源——§6.1/§7.2 文字禁压地台)
@@ -656,6 +657,19 @@ static func keystone_badge(text: String, icon_path := TEX_ICON_KEYSTONE, icon_px
return KeystoneBadge.new(text, icon_path, icon_px)
## 营地顶栏栏体面(整件外观单一出口——营地交互修复批契约 4/5):色值与 α、铺满、不吃点击、
## 挂上宿主五件事全写死在体内;形参只有 host——调用方没有可以忘记的形参。返回句柄供调用方
## 读取与断言;徽章摆位不由本出口负责(归顶栏件内右对齐容器)。须在宿主入树前调用
## (set_anchors_preset 是入树前动作——_modal_dim/overlay_bg 同形)。
static func camp_hud_band_surface(host: Control) -> ColorRect:
var rect := ColorRect.new()
rect.color = CAMP_HUD_BAND
rect.set_anchors_preset(Control.PRESET_FULL_RECT) # 四边 offset 归零铺满宿主
rect.mouse_filter = Control.MOUSE_FILTER_IGNORE # 栏体不吃点击(顶排物件命中区不受阻——契约 5)
host.add_child(rect)
return rect
# 按钮皮·五态矩阵(逐实例 override 不动主题层)
# 悬停:常态图+modulate 提亮(0.06 档与 primary_button 现行 lightened(0.06) 同纪律,零新素材);
# 禁用:本族底+α0.45 幽灵化(与 font_disabled_color 同纪律);
+33 -2
View File
@@ -744,10 +744,30 @@ func _ready() -> void:
_problems.append("大门取消:未无变更退回营地(写口零调用/营地根 visible 失守)〔营地图批 V3〕")
ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"gate").get_center())
await get_tree().process_frame
ProbeUiUtil.push_click(main, main.ui.modals.menu.confirm_dialog._confirm_btn) # 真点「确认」→开始新冒险
await get_tree().process_frame
# R4 位次真点(营地交互修复批——位次与语义绑定钉):真点前消费方前置读(gate_new 在册)→
# 按类型定位按钮行 child(0) 全局矩形中心真点——位次若错接,此点命中「否」,下方主证(翻转)即红。
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:90 确认弹窗位次定值:推进在左、维持居右(R4 位次真点)
if String(main._confirm_consumer) != "gate_new":
_problems.append("R4 前置:确认消费方非 gate_new(分派面漂移)〔营地交互批 V3〕")
var gate_row := _first_hbox(main.ui.modals.menu.confirm_dialog)
if gate_row == null or gate_row.get_child_count() != 3:
_problems.append("R4:按钮行缺失或子节点数≠3(位次判据面塌)〔营地交互批 V3〕")
else:
ProbeUiUtil.push_click_at(main, (gate_row.get_child(0) as Control).get_global_rect().get_center()) # 真点推进钮(左位)
for i in 3:
await get_tree().process_frame # 停点成形帧口径(先例 3 帧——screen_flow_probe 选路停点成形窗)
if not main.run_scope.is_adventure or main.ui.modals.camp.scene.visible:
_problems.append("大门确认:冒险未发起或营地场景屏未隐藏〔营地图批 V3〕〔营地图批 B1〕")
if String(main._confirm_consumer) != "":
_problems.append("R4 主证:确认后消费方未清零〔营地交互批 V3〕")
# 开局终态两钉(营地交互修复批——开始层隐藏收一处后的落点断言):
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:78 关卡开始 → 呈现节点图选路停点(开局终态两钉)
# R1=确认后开始层不可见(被测改动特有提交面——修复前该句恒红=「开局被开始层盖住」缺陷本体);
# R2=关卡画面(选路停点)在位(开局终态呈现面,与既有 is_adventure 断言同批缺一即红)
if main.ui.screens.start_overlay.visible:
_problems.append("大门无断点确认后:开始层滞留可见(开局被开始层盖住缺陷回潮)〔营地交互批 V1〕")
if not main.ui.modals.run.level_screen.visible:
_problems.append("大门无断点确认后:关卡画面(选路停点)未在位〔营地交互批 V2〕")
# 有断点三出口(选路停点形注入——同开场行③ set_breakpoint 先例)
main.save_manager.set_breakpoint({
"master_seed": 42, "rng_counter": 0, "hero_hp": 30, "gp": 10, "keystone": 0,
@@ -1215,6 +1235,17 @@ func _stoptest_assert_hit() -> void:
_problems.append("停手定型化段:停手钮真点零命中登记(驱动空转假绿)〔QA收口 V2〕")
func _first_hbox(node: Node) -> HBoxContainer:
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:90 (位次真点定位——R4 判据面辅助)
if node is HBoxContainer:
return node
for ch in node.get_children():
var found := _first_hbox(ch)
if found != null:
return found
return null
func _report_stall(st: Dictionary) -> void:
# oracle: design/engineering/test-conventions.md:30 (判定上报通道——验证函数组)
var why := ""
+1
View File
@@ -200,6 +200,7 @@ func _suites() -> Array:
{"path": "res://tests/unit/presentation/tune_sim_batch_parity_test.gd", "expect": 0}, # 测速循环批双实现对拍(帧索引/运行态/进度串/stats/报告五面——信号步进 vs await 基线)
{"path": "res://tests/unit/presentation/standee_present_values_test.gd", "expect": 0}, # 战斗一屏样张批 V5–V12(血条危险红/名字行素底不压地台与提档 FS_HINT/徽标/护甲 PNG 绑定/素材入包/地台色合并退役)
{"path": "res://tests/unit/presentation/reward_overlay_test.gd", "expect": 0}, # 奖励节点 UI 直验(盲区补漏)
{"path": "res://tests/unit/presentation/confirm_dialog_test.gd", "expect": 0}, # 确认弹窗按钮位次与语义同钉(推进/次推/维持子节点序——营地交互修复批 R3)
{"path": "res://tests/unit/presentation/level_map_info_fog_test.gd", "expect": 0}, # 信息迷雾——呈现显隐/Boss 豁免/详情零显示/大图拟合
{"path": "res://tests/unit/presentation/forge_workbench_test.gd", "expect": 0}, # 工作间两区六路线 UI 直验(V13 自动化前置面)
{"path": "res://tests/unit/presentation/camp_scene_layout_test.gd", "expect": 0}, # 营地场景布局数据件校验(V14——fail-fast 判据+七命中区几何红线)
+47 -1
View File
@@ -44,6 +44,7 @@ func _split_tail() -> void:
await _smoke_run_lifecycle()
await _smoke_sim_guards()
await _smoke_lifecycle_matrix()
await _smoke_camp_hud_band()
else:
check(false, "V2/V3 前置:autoload 链未就绪(ConfigDB/SaveManager)")
print("[system_test] 摘要:%d 项检查,%d 项失败" % [_checks, _failures])
@@ -603,4 +604,49 @@ func _smoke_lifecycle_matrix() -> void:
for p in ["user://smoke_lifecycle.json", "user://smoke_lifecycle.json.tmp",
"user://smoke_lifecycle_resume.json", "user://smoke_lifecycle_resume.json.tmp"]:
if FileAccess.file_exists(p):
DirAccess.remove_absolute(p)
DirAccess.remove_absolute(p)
func _smoke_camp_hud_band() -> void:
# oracle: design/gdd/ux/camp.md:41 (营地顶栏栏体定值与落座——几何/色值/吃点击五钉组)
print("--- 营地交互修复批 T3 顶栏栏体(R5:色值/铺满/不吃点击/首子件/徽章落座)---")
var main: Node = load("res://presentation/main.tscn").instantiate()
root.add_child(main)
await process_frame
await process_frame
main._camp_pres.open_camp() # 营地屏 boot 态为隐——先开营地(装配链 main_ui_build→camp_scene_view 落座)
for i in range(3):
await process_frame # 排版帧(容器重排在途读数不可信)
var band: Control = main.ui.modals.camp.scene.hud_band
check(band != null and band.is_inside_tree(), "R5 前置:顶栏件经装配链在树(camp_scene_view.hud_band 公开句柄)")
if band == null:
main.queue_free()
await process_frame
return
var badge_min: float = band.get_child(band.get_child_count() - 1).get_child(0).get_combined_minimum_size().y if band.get_child_count() > 0 else -1.0
print("[system_test] 徽章 get_combined_minimum_size().y 实测 = %s(T3.0 前置读数——48=顶格落座前提)" % str(badge_min))
check(band.get_child_count() >= 2 and band.get_child(0) is ColorRect,
"R5④ 栏体色面是顶栏件第一个子节点(子节点序=绘制序——色面先入不盖徽章)")
if band.get_child_count() >= 2 and band.get_child(0) is ColorRect:
var surface: ColorRect = band.get_child(0)
check(String(surface.color.to_html(true)) == "6e4526cc",
"R5① 栏体色=#6E4526×α0.80(to_html 字面编码——禁浮点分量直等)〔营地交互批 V4〕")
var band_rect: Rect2 = band.get_global_rect()
var surf_rect: Rect2 = surface.get_global_rect()
print("[system_test] 顶栏件实测 get_global_rect=%s、栏体=%s(R5② 承重=ui_registry FULL_RECT 注册)" % [band_rect, surf_rect])
check(absf(surf_rect.size.x - band_rect.size.x) < 0.51 and absf(surf_rect.size.y - band_rect.size.y) < 0.51
and absf(surf_rect.size.y - 48.0) < 0.51,
"R5② 栏体铺满宿主且高 48(全局矩形等值)〔营地交互批 V4〕")
check(surface.mouse_filter == Control.MOUSE_FILTER_IGNORE,
"R5③ 栏体不吃点击(MOUSE_FILTER_IGNORE——顶排物件命中区不受阻)〔营地交互批 V4〕")
var dock: Control = band.get_child(band.get_child_count() - 1)
var badge: Control = dock.get_child(0) if dock.get_child_count() > 0 else null
check(badge != null,
"R5⑤ 前置:徽章经右对齐容器在座(dock 尾子件)")
if badge != null:
var brect: Rect2 = badge.get_global_rect()
check(absf(brect.position.y - band_rect.position.y) < 0.51
and absf(brect.end.x - (band_rect.end.x - 12.0)) < 0.51,
"R5⑤ 徽章顶格右位(y=栏顶格、end.x=栏右缘−12——全局矩形不与局部 position 混写)〔营地交互批 V4〕")
main.queue_free()
await process_frame
@@ -11,7 +11,14 @@ const _EXPECTED_KEYS: Array[StringName] = [&"gate", &"forge", &"dummy", &"chest"
func _initialize() -> void:
# oracle: design/gdd/ux/camp.md:29 (营地场景布局数据件——纵深与地平线组)
call_deferred("_test_layout_hotspot_geometry")
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:
@@ -19,7 +26,6 @@ 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())
@@ -88,4 +94,54 @@ func _test_layout_hotspot_geometry() -> void:
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"))
# 顶栏栏体规格侧对比核验(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)
@@ -0,0 +1,59 @@
extends SceneTree
# 确认弹窗按钮位次与语义同钉单元套——
# 守 ConfirmDialog 公开入口 open()/open_three()(四处消费方实际用的两个公开方法)的按钮行子节点序:
# 推进钮恒 child(0)、次推进 child(1)、维持 child(2)(HUD框架.md:90 三条纪律·其三——营地交互修复批 R3)。
# 判据面=树的公开子节点序+Button 公开 text,零私有成员直读;次钮在 open() 里只置 visible=false
# 不从子节点表移除——双钮形态下子节点仍为 3,按下标断必取 child(2)。
# 运行:godot --headless --path game --script res://tests/unit/presentation/confirm_dialog_test.gd(expect 0)
const Kit := preload("res://tests/unit/test_kit.gd")
const ConfirmDialogScript := preload("res://presentation/views/confirm_dialog.gd")
func _initialize() -> void:
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:90 (确认弹窗按钮位次定值——位次与语义同钉组)
call_deferred("_run")
# 按类型向下定位第一个 HBoxContainer(该弹窗树内唯一横排——modal_shell 产 CenterContainer+
# PanelContainer+VBox 不产横排;HSeparator 与 UiKit.button 产件也不是 HBoxContainer)
func _find_btn_row(node: Node) -> HBoxContainer:
if node is HBoxContainer:
return node
for ch in node.get_children():
var found := _find_btn_row(ch)
if found != null:
return found
return null
func _test_two_button_row_order() -> void:
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:90 双钮形态位次=推进在左、维持在右(两钮同钉组)
var ov: Control = ConfirmDialogScript.new()
root.add_child(ov)
ov.open("t", "推进", "维持")
var row := _find_btn_row(ov)
Kit.check("双钮形态按钮行子节点数=3(次钮只隐藏不移除——非空前置)", row != null and row.get_child_count() == 3)
if row != null and row.get_child_count() == 3:
Kit.check("双钮形态位次 child(0)=推进", String((row.get_child(0) as Button).text) == "推进")
Kit.check("双钮形态位次 child(2)=维持(次钮占 child(1) 位——按下标断必取 child(2))", String((row.get_child(2) as Button).text) == "维持")
ov.queue_free()
func _test_three_button_row_order() -> void:
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:90 三钮形态位次=推进/次推/维持(三钮同钉组)
var ov: Control = ConfirmDialogScript.new()
root.add_child(ov)
ov.open_three("t", "推进", "次推", "维持")
var row := _find_btn_row(ov)
Kit.check("三钮形态按钮行子节点数=3(非空前置)", row != null and row.get_child_count() == 3)
if row != null and row.get_child_count() == 3:
Kit.check("三钮形态位次 child(0)=推进", String((row.get_child(0) as Button).text) == "推进")
Kit.check("三钮形态位次 child(1)=次推", String((row.get_child(1) as Button).text) == "次推")
Kit.check("三钮形态位次 child(2)=维持", String((row.get_child(2) as Button).text) == "维持")
ov.queue_free()
func _run() -> void:
_test_two_button_row_order()
_test_three_button_row_order()
quit(Kit.finish("confirm_dialog_test", "PASS — 双钮/三钮形态位次与语义同钉 全过"))