Merge branch 'master' into iter38

# Conflicts:
#	game/README.md
#	game/tests/验证集清单.md
This commit is contained in:
DKChan committed 2026-10-26 15:29:55 +08:00
commit 416ca84aeb
85 files changed
+2939 -2054

No files matched your search

@@ -22,6 +22,7 @@ func _init() -> void:
_test_end_text_differs_by_result()
_test_phase_text_nonempty()
_test_boss_name_uses_boss_rowset()
_test_level_clear_wording()
quit(Kit.finish("battle_narrator_test"))
@@ -36,9 +37,9 @@ func _test_numeric_lines_carry_values() -> void:
func _test_end_text_differs_by_result() -> void:
# 关卡收束(level_end=true)恒为「关卡通关!」——单场胜负文案只在非收束态出现
# 关卡收束(level_end=true)恒为「关卡完成!」——单场胜负文案只在非收束态出现
var level: String = NarratorScript.battle_end_text("lose", true)
Kit.check("关卡收束态恒为「关卡通关!」(实际「%s」)" % level, level == "关卡通关!")
Kit.check("关卡收束态恒为「关卡完成!」(实际「%s」)" % level, level == "关卡完成!")
var win: String = NarratorScript.battle_end_text("victory", false)
var lose: String = NarratorScript.battle_end_text("lose", false)
Kit.check("单场胜负文案互异(胜「%s」/负「%s」)" % [win, lose], win != lose)
@@ -80,3 +81,21 @@ func _test_boss_name_uses_boss_rowset() -> void:
var got: String = NarratorScript.boss_name(runner, db)
Kit.check("Boss 名取自 Boss 行集首行(期望「%s」实际「%s」)〔fv24〕" % [boss_expect, got], got == boss_expect)
Kit.check("Boss 名与同深度战斗行集名不同(期望「%s」≠「%s」——行类路由可判别)〔fv24〕" % [boss_expect, battle_name], boss_expect != battle_name)
# 关卡完成停点文案族措辞钉子(口径换代批):回血行/满血合法态/渠道句/摘要关数行——
# 旧措辞退役后玩家可见面由本组断言钉在新措辞上(撤销证明:回退 battle_narrator 对应行→断言红→复原复绿)
func _test_level_clear_wording() -> void:
var healed: String = NarratorScript.curio_pick_header({"level_index": 1, "heal_gained": 5, "hp_before": 10, "hp_after": 15})
Kit.check("关卡完成回血行带入回血量与生命区间(实际「%s」)〔口径换代 V1〕" % healed,
healed.contains("关卡完成回血 +5(生命 10 → 15)"))
var full_hp: String = NarratorScript.curio_pick_header({"level_index": 1, "heal_gained": 0, "hp_before": 12, "hp_after": 12})
Kit.check("满血零回血合法态文案(实际「%s」)〔口径换代 V1〕" % full_hp,
full_hp.contains("生命已满——关卡完成回血 0(合法态)"))
var node_header: String = NarratorScript.node_curio_header()
Kit.check("奖励节点头部行渠道句点明关卡完成三选一(实际「%s」)〔口径换代 V2〕" % node_header,
node_header.contains("与关卡完成专长三选一同库分流"))
var summary: String = NarratorScript.adventure_summary_text(
{"cleared": false, "result": "failed", "levels_cleared": 1, "stopped_at": 2, "keystone_total": 4, "master_seed": 42})
Kit.check("冒险摘要完成关数行(实际「%s」)〔口径换代 V3〕" % summary,
summary.contains("完成 1 关 · 止步第 2 层") and not summary.contains("通关"))
@@ -1,6 +1,6 @@
extends SceneTree
# 三选一覆盖层 UI 直验套件——
# 守 node_overlay.show_reward_items 的继任共用面;奖励节点三选一与通关三选一共用 CurioPickOverlay,
# 守 node_overlay.show_reward_items 的继任共用面;奖励节点三选一与关卡完成三选一共用 CurioPickOverlay,
# 本套件守共用面的「功能存在 ≠ 功能可达」盲区:选择外发/强制确认两段式/结果态。
# 〔无替换子面板路段——装配不可变契约 1,overlay 无 replace 态。〕
# 运行:godot --headless --path game --script res://tests/unit/presentation/reward_overlay_test.gd(expect 0)
@@ -52,6 +52,7 @@ func _init() -> void:
_smoke_diag_button_retired()
_status_panel_projection()
_career_overlay_projection()
_camp_recent_projection()
quit(Kit.finish("theme_projection_test"))
@@ -806,6 +807,20 @@ func _all_labels(root: Node) -> Array:
return out
# ---- 营地最近冒险行投影(口径换代 V6:完成关数措辞钉子——open_camp 全量重建路径直调;
# 最小档 data=recent 单条 completed 局,其余键缺省走 .get 兜底——与组合根 _camp_data 投影同键面)----
func _camp_recent_projection() -> void:
print("--- 营地·最近冒险行投影(open_camp 最小档)---")
var camp = load("res://presentation/views/camp_overlay_view.gd").new()
camp.open_camp({"balance": 0, "recent": [
{"result": "completed", "levels_cleared": 2, "keystone_total": 5, "master_seed": 42}]})
var recent_text: String = String(camp._recent_list.text)
Kit.check("营地最近冒险行完成关数措辞(实际「%s」)〔口径换代 V6〕" % recent_text,
recent_text.contains("完成 2 关") and not recent_text.contains("通关"))
camp.visible = false
camp.free()
# ---- 生涯页只读投影一致性(汇总段六类九项逐行 == SaveManager 计数访问器/
# MetaProgression 访问器读数;投影拼装与 HudPresenter.career_data 同构(同访问器族同序)。
# 只读性连接巡检与零动效断言归 ui_struct_probe 呈现侧,本段只管数据面+旧档载入显示
@@ -837,17 +852,27 @@ func _career_overlay_projection() -> void:
Kit.check("生涯页开层可见(open_career 直开——零动效路径)", co.visible)
Kit.check("总局数投影 = adventure_total() 读数(%s)" % co._total_label.text,
String(co._total_label.text) == "2 局")
Kit.check("通关局数投影 = adventure_cleared() 读数(%s)" % co._cleared_label.text,
Kit.check("冒险成功局数投影 = adventure_cleared() 读数(%s)" % co._cleared_label.text,
String(co._cleared_label.text) == "1 局(三关全通)")
var expect_clears: Array[String] = []
for row in (data["clears"] as Array):
expect_clears.append("%s ×%d" % [String(row.get("display_name", "")), int(row.get("count", 0))])
Kit.check("各关通关次数投影 = clear_count() 逐关读数(%s)" % co._clears_label.text,
Kit.check("各关完成次数投影 = clear_count() 逐关读数(%s)" % co._clears_label.text,
String(co._clears_label.text) == " · ".join(expect_clears))
Kit.check("Boss 首杀/总杀投影 = 首杀标记计数 + boss_kill_count()(%s / %s)" % [co._boss_first_label.text, co._boss_kills_label.text],
String(co._boss_first_label.text) == "3/3 关" and String(co._boss_kills_label.text) == "4 次")
Kit.check("钥石余额徽投影 = keystone_balance() 读数(%s)" % co._keystone_badge.value_label.text,
String(co._keystone_badge.value_label.text) == "钥石 %d" % mp.keystone_balance())
# 口径换代(2026-10-26):行标签换代钉子+生涯页零「通关」残留(负断言兜全页)
var career_label_texts: Array[String] = []
for career_lbl: Label in _all_labels(co):
career_label_texts.append(String(career_lbl.text))
var career_labels_joined: String = "|".join(career_label_texts)
Kit.check("生涯页行标签换代(冒险成功局数/各关完成次数)〔口径换代 V4〕",
career_labels_joined.contains("冒险成功局数") and career_labels_joined.contains("各关完成次数"))
Kit.check("生涯页 label 全集零「通关」残留〔口径换代 V4〕", not career_labels_joined.contains("通关"))
Kit.check("秘宝解锁条件标签冒险成功口径(UiKit 单点常量)〔口径换代 V5〕",
UiKit.TREASURE_REQ_LABELS["adventure_cleared"] == "冒险成功")
co.visible = false
co.free()
# 旧档载入显示(完成定义 6):v5 现役档形态(计数域两键不存在)升 v6 → 生涯页正常呈现缺省值
@@ -866,7 +891,7 @@ func _career_overlay_projection() -> void:
"count": v5.clear_count(i + 1)})
var co_v5 = load("res://presentation/views/career_overlay.gd").new()
co_v5.open_career(data_v5)
Kit.check("旧档载入后生涯页正常显示(v5 升 v6:总局数/通关局数显示 0,既有钥石余额原样呈现)",
Kit.check("旧档载入后生涯页正常显示(v5 升 v6:总局数/冒险成功局数显示 0,既有钥石余额原样呈现)",
co_v5.visible and String(co_v5._total_label.text) == "0 局" \
and String(co_v5._cleared_label.text) == "0 局(三关全通)" \
and String(co_v5._keystone_badge.value_label.text) == "钥石 3")