Merge branch 'master' into iter38
# Conflicts: # game/README.md # game/tests/验证集清单.md
This commit is contained in:
commit
416ca84aeb
85 files changed
+2939
-2054
No files matched your search
+7
-7
@@ -2,9 +2,9 @@
|
||||
|
||||
> // VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
> // Validation Question(v2,2026-09-11):线性第一关(战×2→道具→战×2→恢复→Boss)能否承载
|
||||
> // "数值提升→通关期望改善"的 Roguelike 元进度验证——基线属性应该难到打不过、天赋数值提升把
|
||||
> // 通关期望拉回可玩带,且这条曲线能用数值调整板 + 批量模拟**测量出来**
|
||||
> // (通关攻击期望 / 败北血量数学期望);v1 的单战斗循环问题(逐行归因 P2 / Core headless 可跑)
|
||||
> // "数值提升→冒险成功期望改善"的 Roguelike 元进度验证——基线属性应该难到打不过、天赋数值提升把
|
||||
> // 冒险成功期望拉回可玩带,且这条曲线能用数值调整板 + 批量模拟**测量出来**
|
||||
> // (冒险成功攻击期望 / 败北血量数学期望);v1 的单战斗循环问题(逐行归因 P2 / Core headless 可跑)
|
||||
> // 已于 2026-09-10 验证并保持回归(初版断言面 46 项,后并迁系统测试宿主,沿革查 git log)。
|
||||
> // Date: 2026-09-10(v1)· 2026-09-11(v2 关卡扩展)· 引擎 Godot 4.7.2 · 本目录仅作设计验证参考,禁止 import 进生产代码
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
|
||||
1. 用 Godot 4.7.2 打开本目录(含 `project.godot` 的文件夹)
|
||||
2. 按 F5 运行(主场景 `presentation/main.tscn`)
|
||||
3. **主入口:`▶ 开始冒险(三关连探:地精洞窟 → 幽暗地域 → 灰矮人矿城)`**——一条命走完三关(冒险结构实现批 2026-09-21:生命/GP/加算区跨关同账、非末关通关发大奖励〔上限提升+血回满+钥石〕、任意战斗倒下冒险终、结算屏出冒险摘要五件事;选路停点可「放弃冒险」(同失败结算,2026-09-21 用户拍板补入口));首关节点链:战→战→赏→战→战→疗→王。唯一试玩入口 = 完整冒险——单关测试直入口(L2/L3)已退役(2026-09-23 用户拍板)
|
||||
4. 调试浮窗(功能面:攻击/生命百分比加成〔模拟天赋树解锁〕+ 批量模拟〔输出通关率/承伤期望/败北节点分布〕+ 两层调试入口;2026-10-04 迭代 14.5 调试整合批三散件收一)——发布态入口恒隐(2026-10-25 删写死行批:调试角标不显示、浮窗无 UI 开启口;开发期临时启用改 `_debug_entry` 缺省本地不提交),浮窗体系整体退役属发布工程批
|
||||
3. **主入口:`▶ 开始冒险(三关连探:地精洞窟 → 幽暗地域 → 灰矮人矿城)`**——一条命走完三关(冒险结构实现批 2026-09-21:生命/GP/加算区跨关同账、非末关的关卡完成发大奖励〔上限提升+血回满+钥石〕、任意战斗倒下冒险终、结算屏出冒险摘要五件事;选路停点可「放弃冒险」(同失败结算,2026-09-21 用户拍板补入口));首关节点链:战→战→赏→战→战→疗→王。唯一试玩入口 = 完整冒险——单关测试直入口(L2/L3)已退役(2026-09-23 用户拍板)
|
||||
4. 调试浮窗(功能面:攻击/生命百分比加成〔模拟天赋树解锁〕+ 批量模拟〔输出关卡完成率/承伤期望/败北节点分布〕+ 两层调试入口;2026-10-04 迭代 14.5 调试整合批三散件收一)——发布态入口恒隐(2026-10-25 删写死行批:调试角标不显示、浮窗无 UI 开启口;开发期临时启用改 `_debug_entry` 缺省本地不提交),浮窗体系整体退役属发布工程批
|
||||
|
||||
headless 冒烟(752 项断言〔计数随批增长,现值以最新批末闸读数为准〕,含关卡线性推进/满配通关/裸装败北/回放确定性;2026-10-02 性能报告实测 60.55 秒,报告在 `性能实测报告(已退役,git 历史可查)`):
|
||||
headless 冒烟(752 项断言〔计数随批增长,现值以最新批末闸读数为准〕,含关卡线性推进/满配冒险成功/裸装败北/回放确定性;2026-10-02 性能报告实测 60.55 秒,报告在 `性能实测报告(已退役,git 历史可查)`):
|
||||
|
||||
```
|
||||
Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://probe/smoke_all.gd
|
||||
@@ -35,7 +35,7 @@ Godot_v4.7.2-stable_win64_console.exe --headless --path . res://probe/ui_tune_pr
|
||||
|
||||
其余八场景探针(ui_click_probe 行为链 / ui_input_probe 输入合成 / ui_struct_probe 全屏结构 / ui_contrast_probe 对比度 / ui_probe·ui_tune_probe 调参面板 / t17_defer_probe 结算演播延迟链回归 / shot_probe·fx_probe·fx_shot_probe·board_shot_probe 画面与特效采样;全部由 `tools/probe_gate.sh` 与 `tools/render_gate.sh` 调度,闸清单见 `tools/run_all_gates.sh`)+ 板图校准工具 `sim/calibrate_boards.gd`(board_calibration.csv 重跑入口,--script 运行):
|
||||
|
||||
全冒险期望扫描(零加成三关连探批量——通关率/止步分布/败退节点;调参期难度读数一律走全冒险口径):
|
||||
全冒险期望扫描(零加成三关连探批量——关卡完成率/止步分布/败退节点;调参期难度读数一律走全冒险口径):
|
||||
|
||||
```
|
||||
Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://sim/adventure_monte_carlo.gd -- [N] [seed0]
|
||||
|
||||
@@ -7,9 +7,9 @@ class_name AdventureRunner
|
||||
extends RefCounted
|
||||
|
||||
signal level_started(level_index: int, display_name: String) ## 每关开始(含冒险首关)——呈现层重取 runner()、刷新图条与层名行
|
||||
signal curio_choice_presented(payload: Dictionary) ## 专长三选一停点(非末关通关恰一次;通道白名单后候选恒专长+回血在载荷)——{level_index, candidates, heal_gained, hp_before, hp_after, keystone_gained, keystone_total, gp, aux_cleared}
|
||||
signal curio_choice_presented(payload: Dictionary) ## 专长三选一停点(非末关的关卡完成恰一次;通道白名单后候选恒专长+回血在载荷)——{level_index, candidates, heal_gained, hp_before, hp_after, keystone_gained, keystone_total, gp, aux_cleared}
|
||||
signal curio_resolved(result: Dictionary) ## 三选一兑付完成(pick_curio/替换受理后恰一次)——{picked, value, hp_before, hp_after, level_index}
|
||||
signal adventure_finished(summary: Dictionary) ## 冒险结算(completed/failed/abandoned)——summary 五件事:打穿没有/通关几关/止步第几关/钥石合计/主种子(+additive keystone_drops)
|
||||
signal adventure_finished(summary: Dictionary) ## 冒险结算(completed/failed/abandoned)——summary 五件事:打穿没有/完成几关/止步第几关/钥石合计/主种子(+additive keystone_drops)
|
||||
# ---- 转发族(签名与 LevelRunner 同款——呈现层对接透明;S6 经 _forward_* 方法)----
|
||||
signal node_entered(node: Dictionary, info: Dictionary)
|
||||
signal choices_presented(candidates: Array)
|
||||
@@ -266,7 +266,7 @@ func _resume_pending(bp_pending: Dictionary) -> void:
|
||||
var migrated: Array[Variant] = []
|
||||
for c in (bp_pending.get("candidates", []) as Array):
|
||||
if c is Dictionary:
|
||||
# T4——feat_id 随迁移(通关渠道候选=纯专长令牌,身份键 relic|tier|feat_id 三段全要)
|
||||
# T4——feat_id 随迁移(关卡完成渠道候选=纯专长令牌,身份键 relic|tier|feat_id 三段全要)
|
||||
var mig := {"kind": String((c as Dictionary).get("kind", "")), "tier": String((c as Dictionary).get("tier", ""))}
|
||||
if String(mig["kind"]) == "relic":
|
||||
mig["feat_id"] = String((c as Dictionary).get("feat_id", ""))
|
||||
@@ -339,7 +339,7 @@ func continue_to_next_level() -> bool:
|
||||
|
||||
# 停点空候选消化(池空=三选一环节整体跳过:候选 0 件时停点视同已处理,
|
||||
# 呈现层跳开选择面直接走后续推进。仅空候选合法;非空停点拒绝——零自动推进契约不破,正常
|
||||
# 消化仍只走 pick_curio/apply_node_curio 兑付。触发源=渠道白名单过滤后池空(关卡 Boss 通关
|
||||
# 消化仍只走 pick_curio/apply_node_curio 兑付。触发源=渠道白名单过滤后池空(关卡 Boss 击破=关卡完成
|
||||
# 渠道只出专长,专长零配置/旧档 deck 无 relic 余库即空集)。)
|
||||
func skip_empty_curio() -> bool:
|
||||
if _curio_pending.is_empty():
|
||||
@@ -390,7 +390,7 @@ func _feat_payload_keys(feat_id: String) -> Dictionary:
|
||||
|
||||
# 三选一件兑付共享体(TP0——令牌消费、摘牌单点,含 relic 分支):
|
||||
# 属性件写加算区 + 件获取计数、AP 件写第四轴、GP 件走钱包、恢复件当即回血(封顶生效上限——
|
||||
# 与账本置满/通关回血同式 effective_max_hp)、relic 件入持有集(散件单体保底——效果接线面
|
||||
# 与账本置满/关卡完成回血同式 effective_max_hp)、relic 件入持有集(散件单体保底——效果接线面
|
||||
# 按挂点消费,档位质变由 FeatsEngine 求值)。兑付即从 _curio_deck 摘除该令牌(选中踢池;
|
||||
# 未选候选从未摘牌=自动回池)。道具件不入此体——未知件返回 false 由调用方按渠道名报错。
|
||||
func _apply_curio_pick(kind: String, tier: String, value: int, feat_id: String = "") -> bool:
|
||||
@@ -483,7 +483,7 @@ func _finish_curio_pick(picked: String, value: int, hp_before: int, tier: String
|
||||
_snapshot_breakpoint()
|
||||
|
||||
|
||||
# ---- 奖励节点三选一(与通关三选一共享同池同权重同次数上限)----
|
||||
# ---- 奖励节点三选一(与关卡完成三选一共享同池同权重同次数上限)----
|
||||
|
||||
# 候选呈现(节点到达时调用——恰一 reward_pick scope 洗牌 + 道具件掷定 scope;本停点断点不载,
|
||||
# 续玩重建到选路停点后重走重抽,恢复的全局计数器保证同候选逐位复现)。
|
||||
@@ -498,7 +498,7 @@ func present_node_curio(candidates: Array) -> void:
|
||||
_node_curio_pending = {"candidates": candidates.duplicate(true)}
|
||||
|
||||
|
||||
# 点选兑付(节点域——无部分回血无钥石;令牌消费与牌库摘牌与通关三选一同库;relic 同权入持有集)
|
||||
# 点选兑付(节点域——无部分回血无钥石;令牌消费与牌库摘牌与关卡完成三选一同库;relic 同权入持有集)
|
||||
func apply_node_curio(kind: String, tier: String = "", feat_id: String = "") -> bool:
|
||||
if kind == "feat_ticket":
|
||||
return _redeem_feat_ticket() # T5 拿到即开——节点停点候选替换为专长三选一(呈现层本地重绘)
|
||||
@@ -562,7 +562,7 @@ func node_curio_pending_state() -> Dictionary:
|
||||
}
|
||||
|
||||
|
||||
# 三选一刷新(免费位 X/Y——组合根唯一调用口;无停点/无余量 false 零副作用)。通关停点重抽后重发
|
||||
# 三选一刷新(免费位 X/Y——组合根唯一调用口;无停点/无余量 false 零副作用)。关卡完成停点重抽后重发
|
||||
# curio_choice_presented(呈现层重建卡片+刷新余量);节点停点由组合根读 node_curio_pending_state 重 present。
|
||||
func refresh_curio() -> bool:
|
||||
return _refresh_curio_common(false)
|
||||
@@ -651,7 +651,7 @@ func _feat_ticket_consider(rg: RandomNumberGenerator) -> bool:
|
||||
func _redeem_feat_ticket() -> bool:
|
||||
_feat_ticket_sold += 1
|
||||
_feat_ticket_pity = 0
|
||||
var cands: Array[Variant] = _draw_curio_candidates("level_clear") # 专长池(与通关渠道同白名单)
|
||||
var cands: Array[Variant] = _draw_curio_candidates("level_clear") # 专长池(与关卡完成渠道同白名单)
|
||||
if cands.is_empty():
|
||||
push_error("[冒险结构] 专长凭证兑付失败——专长池空集")
|
||||
_feat_ticket_sold -= 1
|
||||
@@ -668,7 +668,7 @@ func _redeem_feat_ticket() -> bool:
|
||||
|
||||
# Boss 钥石逐杀掉落(掉落渠道 = Boss/精英击杀;首杀/复杀两档读 meta.tres boss_keystone;
|
||||
# 首杀判据 = 存档 boss_first_kill 标记位〔断点未注入 = 恒首杀——模拟/冒烟零扰动;标记仍在收束
|
||||
# apply_adventure_summary 逐通关关次置位〕;末关 Boss 照常入账;明细随摘要 additive 键供结算面归因)
|
||||
# apply_adventure_summary 逐关卡完成关次置位〕;末关 Boss 照常入账;明细随摘要 additive 键供结算面归因)
|
||||
func _boss_keystone_drop(li: int) -> void:
|
||||
var first := true
|
||||
if _save_manager != null:
|
||||
@@ -779,7 +779,7 @@ func _on_level_finished(payload: Dictionary) -> void:
|
||||
# Boss 钥石逐杀掉落(击杀掉落渠道,末关照常入账;首杀/复杀读 meta.tres boss_keystone)
|
||||
_boss_keystone_drop(level_index)
|
||||
if level_index < levels_total:
|
||||
# 通关兑付(非末关恰一次;三层奖励框架):部分回血先行 → 加权洗牌掷 3 → 三选一停点
|
||||
# 关卡完成兑付(非末关恰一次;三层奖励框架):部分回血先行 → 加权洗牌掷 3 → 三选一停点
|
||||
#(顺序即契约——回血按当刻账本与上限结算,选择停点零自动推进)
|
||||
var hp_before := int(ledger.get("hero_hp", 0))
|
||||
var max_eff := effective_max_hp() # 回血封顶口径含调试浮窗 pct(同战斗内上限)
|
||||
@@ -791,7 +791,7 @@ func _on_level_finished(payload: Dictionary) -> void:
|
||||
_curio_paid_refreshes = 0 # 付费刷计数随停点清零(对齐商店进店清零)
|
||||
var pick_payload: Dictionary[Variant, Variant] = {
|
||||
"level_index": level_index,
|
||||
"candidates": _draw_curio_candidates("level_clear"), # T4 渠道白名单——关底 Boss 击败=通关渠道只出专长
|
||||
"candidates": _draw_curio_candidates("level_clear"), # T4 渠道白名单——关底 Boss 击败=关卡完成渠道只出专长
|
||||
"heal_gained": heal,
|
||||
"hp_before": hp_before,
|
||||
"hp_after": int(ledger.get("hero_hp", 0)),
|
||||
@@ -805,12 +805,12 @@ func _on_level_finished(payload: Dictionary) -> void:
|
||||
curio_choice_presented.emit(pick_payload)
|
||||
_snapshot_breakpoint() # 选择停点可中断(契约 5——续玩不重抽不静默丢失)
|
||||
return
|
||||
# 末关通关:不发三选一与部分回血(没有下一战可受益)、Boss 钥石照常入账——冒险完成结算
|
||||
# 末关的关卡完成:不发三选一与部分回血(没有下一战可受益)、Boss 钥石照常入账——冒险完成结算
|
||||
_settle("completed")
|
||||
|
||||
|
||||
func _settle(result: String) -> void:
|
||||
# 冒险摘要五件事:打穿没有 / 通关几关 / 止步第几关 / 钥石入账合计 / 本局主种子
|
||||
# 冒险摘要五件事:打穿没有 / 完成几关 / 止步第几关 / 钥石入账合计 / 本局主种子
|
||||
summary = {
|
||||
"result": result,
|
||||
"cleared": result == "completed",
|
||||
|
||||
@@ -254,7 +254,7 @@ func level_gen_params_row(level_index: int) -> Dictionary:
|
||||
return level_gen_params.row_by_level_index(level_index) if level_gen_params != null else {}
|
||||
|
||||
|
||||
# 序列总关数(AdventureRunner levels_total / MetaProgression 通关累加域的取数口径)
|
||||
# 序列总关数(AdventureRunner levels_total / MetaProgression 关卡完成累加域的取数口径)
|
||||
func level_count() -> int:
|
||||
return level.rows.size() if level != null else 0
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ func cross_validate(loaded: Dictionary, push_diag: Callable) -> void:
|
||||
if not treasure_live.has(treasure_eff):
|
||||
push_diag.call("error", "reward_dangling_treasure", "RewardTable %s aux 行引用无效道具 %s(retired/deferred/不存在): %s" % [row.get("reward_id"), treasure_eff, "双表校验"])
|
||||
# ①c F2 投放维度覆盖校验不启用:挖格不掉道具,秘宝
|
||||
# 唯一来路=三选一共享池〔节点/通关〕;共享池为「抽到道具位再动态掷定具体道具」,维度覆盖不可
|
||||
# 唯一来路=三选一共享池〔节点/关卡完成〕;共享池为「抽到道具位再动态掷定具体道具」,维度覆盖不可
|
||||
# 静态校验。
|
||||
# ② SettlementTable.effect_pool_map 悬空
|
||||
for eff in st_tab.effect_pool_map.keys():
|
||||
|
||||
@@ -561,7 +561,7 @@ func _on_battle_finished(payload: Dictionary) -> void:
|
||||
hero_hp = maxi(1, int(payload.get("hero_hp", 0))) # despawn 前捕获值(载荷自包含)
|
||||
_gain_wallet(int(payload.get("gp_dropped", 0))) # 战斗完成入账(clamp ≥0——破财可把 escrow 打负,钱包入账时钳制)
|
||||
if String(node.get("type", "")) == "boss" or result == "defeat":
|
||||
# Boss 胜利 = 关卡通关;任意战斗败北 = 关卡失败(漂移⑦清偿——每战结果上报冒险结构,
|
||||
# Boss 胜利 = 关卡完成;任意战斗败北 = 关卡失败(漂移⑦清偿——每战结果上报冒险结构,
|
||||
# 失败结算归 AdventureRunner;本类只发 level_finished)
|
||||
level_result = "victory" if result == "victory" else "defeat"
|
||||
var hp_left := hero_hp if result == "victory" else 0
|
||||
@@ -742,7 +742,7 @@ func _gain_wallet(amount: int) -> void:
|
||||
wallet_changed.emit("gain", gp, {"amount": amount})
|
||||
|
||||
|
||||
# 冒险收束清零(通关/倒下/放弃——局内货币生命周期终点,不入存档不入摘要;public——冒险模式由
|
||||
# 冒险收束清零(冒险成功/倒下/放弃——局内货币生命周期终点,不入存档不入摘要;public——冒险模式由
|
||||
# AdventureRunner._settle 在冒险收束时调用,单关模式 = 关卡收束即冒险收束)
|
||||
func close_wallet() -> void:
|
||||
gp = 0
|
||||
@@ -772,7 +772,7 @@ func _emit_wallet_event(event: String, gp_now: int, extra: Dictionary) -> void:
|
||||
wallet_changed.emit(event, gp_now, extra)
|
||||
|
||||
|
||||
# 购血(商店 max_hp 商品——当前生命 +Δ 并封顶新上限,与账本置满/通关回血同式)
|
||||
# 购血(商店 max_hp 商品——当前生命 +Δ 并封顶新上限,与账本置满/关卡完成回血同式)
|
||||
func _gain_max_hp_flat(flat: int) -> void:
|
||||
hero_hp = mini(hero_hp + flat, effective_max_hp())
|
||||
|
||||
|
||||
@@ -271,7 +271,7 @@ func forge_shop_status() -> Array:
|
||||
# (keystone_drops 副本随摘要入 recent_summaries)。
|
||||
# 钥石为 Boss 逐杀掉落——掉落金额已在冒险内逐杀入账(summary.keystone_total
|
||||
# 含全部掉落;首杀/复杀判定在 AdventureRunner._boss_keystone_drop,首杀标记在下方
|
||||
# apply_adventure_summary 逐通关关次置位)。本函数只做标记+入账+归档。
|
||||
# apply_adventure_summary 逐关卡完成关次置位)。本函数只做标记+入账+归档。
|
||||
func settle_adventure(summary: Dictionary) -> Dictionary:
|
||||
# 首次冒险布尔位置位(关卡节点图生成 v0.4——完成/失败/放弃三结局同经本收束挂点,写入只在收束)
|
||||
save.mark_first_adventure_done()
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
class_name SaveManagerCore
|
||||
extends Node
|
||||
|
||||
const SAVE_FORMAT_VERSION := 7 # registry schema_version:v7=锻造工坊域(meta 新域 forge_routes〔六路线等级 levels+直购解锁清单 unlocked+当前武器/甲选中标记 weapon/armor;无特效域〕,旧档缺键由 _normalize 按默认补齐=初始持有态〔sword/light 在档余四锁、选中=初始路线〕,v6→v7 空迁移=缺键补齐模式);v6=生涯终身计数域(meta 两新域 adventure_total〔冒险总局数,开局即计含放弃与失败〕/adventure_cleared〔通关局数,三关全通才计 1——判定字段 summary.cleared〕,旧档缺键由 _normalize 按默认补零,收束自增唯一入账=apply_adventure_summary);v5=天赋树域(meta 三新域 tree_nodes〔node_id→已购等级〕/fork_sides〔fork_group→a|b〕/migration_note〔三体系迁移补偿注记〕);v4=同调位域(meta 三新域 boss_kill_count/attunement_equipped/attune_refresh_level——旧档缺键 = 合法空态);v3=断点附随件扩列+摘要 keystone_drops;v2=断点域出生;v1=首版;高版本拒读 owner=存档序列化
|
||||
const SAVE_FORMAT_VERSION := 7 # registry schema_version:v7=锻造工坊域(meta 新域 forge_routes〔六路线等级 levels+直购解锁清单 unlocked+当前武器/甲选中标记 weapon/armor;无特效域〕,旧档缺键由 _normalize 按默认补齐=初始持有态〔sword/light 在档余四锁、选中=初始路线〕,v6→v7 空迁移=缺键补齐模式);v6=生涯终身计数域(meta 两新域 adventure_total〔冒险总局数,开局即计含放弃与失败〕/adventure_cleared〔冒险成功局数,三关全通才计 1——判定字段 summary.cleared〕,旧档缺键由 _normalize 按默认补零,收束自增唯一入账=apply_adventure_summary);v5=天赋树域(meta 三新域 tree_nodes〔node_id→已购等级〕/fork_sides〔fork_group→a|b〕/migration_note〔三体系迁移补偿注记〕);v4=同调位域(meta 三新域 boss_kill_count/attunement_equipped/attune_refresh_level——旧档缺键 = 合法空态);v3=断点附随件扩列+摘要 keystone_drops;v2=断点域出生;v1=首版;高版本拒读 owner=存档序列化
|
||||
const SUMMARY_CAP := 10 # 最近冒险摘要保留条数(最新在前)
|
||||
|
||||
const TREE_STRUCT_VERSION := 2 # 树结构版本标记(12 节点 v0.8 结构;加载检测缺失
|
||||
@@ -154,7 +154,7 @@ func delete_slot(slot: int) -> bool:
|
||||
return true
|
||||
|
||||
|
||||
# 摘要三项按槽窥视读器(sv7/sv8,契约 5/9):钥石余额/通关局数/冒险总局数。
|
||||
# 摘要三项按槽窥视读器(sv7/sv8,契约 5/9):钥石余额/冒险成功局数/冒险总局数。
|
||||
# 只读零副作用:不激活、不写盘、不触发 _corrupt_fallback 改名(.corrupt.bak 只在正式进档路径产生)。
|
||||
# 返回 {"readable", "has_file", "keystone", "cleared", "total"}——readable=true=可读槽(空槽=零值合法态,
|
||||
# .tmp 残留按空槽——主文件不在即空);readable=false=不可读槽(损坏/高版本/缺版本字段/打开失败,
|
||||
@@ -285,7 +285,7 @@ func _default_data() -> Dictionary[String, Variant]:
|
||||
"recent_summaries": [],
|
||||
"first_adventure_done": false,
|
||||
"adventure_total": 0, # 生涯终身计数·冒险总局数(开局即计含放弃与失败,收束唯一入账)
|
||||
"adventure_cleared": 0, # 生涯终身计数·通关局数(三关全通才计 1——判定字段 summary.cleared)
|
||||
"adventure_cleared": 0, # 生涯终身计数·冒险成功局数(三关全通才计 1——判定字段 summary.cleared)
|
||||
"vision_used": 0, # 生涯终身计数·真视类工具累计使用次数(信标网络解锁行为条件;
|
||||
# 受理成功唯一计数点=battle 层经冒险层聚合、收束入账;缺键补零不升版本)
|
||||
"forge_routes": { # 锻造工坊域(六路线等级+直购解锁清单+当前选中标记;无特效域)
|
||||
@@ -417,7 +417,7 @@ func boss_first_kill_marked(level_index: int) -> bool:
|
||||
|
||||
# ---- 同调位账本域(里程碑谓词表计数源 + 装配关系 + Y 升级态)----
|
||||
|
||||
# Boss 击杀总数(含重复——底稿裁决 4 计数源;收束入账 = apply_adventure_summary 逐通关关次 +1,
|
||||
# Boss 击杀总数(含重复——底稿裁决 4 计数源;收束入账 = apply_adventure_summary 逐关卡完成关次 +1,
|
||||
# Boss 恒唯一终点故 levels_cleared 即本次冒险 Boss 击杀数)
|
||||
func boss_kill_count() -> int:
|
||||
return int(_data["meta"].get("boss_kill_count", 0))
|
||||
@@ -431,8 +431,8 @@ func adventure_total() -> int:
|
||||
return int(_data["meta"].get("adventure_total", 0))
|
||||
|
||||
|
||||
# 通关局数(口径=三关全通才计 1——判定字段 summary.cleared,adventure_runner「completed」
|
||||
# 唯一出口=末关通关)
|
||||
# 冒险成功局数(口径=三关全通才计 1——判定字段 summary.cleared,adventure_runner「completed」
|
||||
# 唯一出口=末关的关卡完成)
|
||||
func adventure_cleared() -> int:
|
||||
return int(_data["meta"].get("adventure_cleared", 0))
|
||||
|
||||
@@ -558,7 +558,7 @@ func set_attune_refresh_level(n: int) -> void:
|
||||
_data["meta"]["attune_refresh_level"] = maxi(0, n)
|
||||
|
||||
|
||||
# 同调位计数源组装(Attunement 服务取数口——clear_count = 逐关通关次数合计〔含重复关,底稿 §2.1 口径〕;
|
||||
# 同调位计数源组装(Attunement 服务取数口——clear_count = 逐关完成次数合计〔含重复关,底稿 §2.1 口径〕;
|
||||
# clear_count(level_index) 逐关访问器零改动复用)
|
||||
func recent_summaries() -> Array:
|
||||
return _data["meta"]["recent_summaries"].duplicate(true) # 值拷贝出户(本类契约「禁碰内部结构」——消费方直改不得渗入存档内存态)
|
||||
@@ -566,10 +566,10 @@ func recent_summaries() -> Array:
|
||||
|
||||
# 域更新原语(正式冒险收束唯一入账口——组合根经本函数入账,禁直改内部结构):
|
||||
# 钥石入账(summary.keystone_total——逐杀掉落已在冒险内计入,boss_drops 参数为
|
||||
# 摘要归档副本不重复入账)+ 逐通关关次 clear_count +1 与 Boss 首杀标记 + Boss 击杀总数累计
|
||||
# 摘要归档副本不重复入账)+ 逐关卡完成关次 clear_count +1 与 Boss 首杀标记 + Boss 击杀总数累计
|
||||
# (同调位里程碑计数源)+ 摘要头部插入(cap 10,最新在前,元素附
|
||||
# keystone_drops 副本——复盘可归因)+ 生涯终身计数自增(总局数开局即计:
|
||||
# 三结局同经本口恰计一次;通关局数按 summary.cleared 判定三关全通才计 1。终身计数写入
|
||||
# 三结局同经本口恰计一次;冒险成功局数按 summary.cleared 判定三关全通才计 1。终身计数写入
|
||||
# 单口=本函数,禁第二写入点)。
|
||||
func apply_adventure_summary(summary: Dictionary, boss_drops: Array = []) -> void:
|
||||
_data["meta"]["adventure_total"] = adventure_total() + 1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 冒险大奖励表(奇物三选一令牌池)schema:档位配额/件值/已拿上限的类型化读取(单行全局;垂直切片验证:三关连探一条命走完)。
|
||||
## kind/tier 词表封闭——断点附随件与牌库剔除态依赖,扩件走本表+registry 修订,禁调用侧私扩。
|
||||
## 档位配额总量是结构锚(validate 硬断言):改投放总量=重裁非调参;全部 knob provisional,上调须重扫通关率带。
|
||||
## 档位配额总量是结构锚(validate 硬断言):改投放总量=重裁非调参;全部 knob provisional,上调须重扫关卡完成率带。
|
||||
class_name AdventureTable
|
||||
extends Resource
|
||||
|
||||
@@ -36,7 +36,7 @@ const TIER_LEGENDARY := "legendary"
|
||||
## validate() 硬断言;此后改总量=重裁非调参)。
|
||||
const TIER_QUOTAS := {"common": 70, "excellent": 25, "rare": 7, "legendary": 0}
|
||||
|
||||
# —— 通关部分回血(固定型,非末关通关兑付)——
|
||||
# —— 关卡完成部分回血(固定型,非末关兑付)——
|
||||
@export var clear_heal_pct: int = 20 # [0,100] knob——回复生效上限的百分比,封顶上限不回满
|
||||
|
||||
# —— 三选一候选数——
|
||||
|
||||
@@ -9,8 +9,8 @@ extends Resource
|
||||
@export var rows: Array[Variant] = [] # Array[Dictionary] 键 item_id
|
||||
|
||||
# 计数键封闭枚举(就地内联于本表,无独立谓词表;
|
||||
# 通关口径=C6(通关=打通整局冒险),键名 adventure_cleared
|
||||
# (生涯通关局数,save_manager 生涯计数域在役);扩 vision_used(真视类工具
|
||||
# 解锁口径=C6(冒险成功=打通整局冒险),键名 adventure_cleared
|
||||
# (生涯冒险成功局数,save_manager 生涯计数域在役);扩 vision_used(真视类工具
|
||||
# 累计使用——信标网络行为条件计数;use_treasure 受理成功处累计、冒险收束导出入档);
|
||||
# 键域以本枚举为准)
|
||||
const UNLOCK_COUNTER_KEYS := ["adventure_cleared", "boss_kill_count", "vision_used"]
|
||||
|
||||
@@ -48,6 +48,6 @@ feat_ticket_pity_step = 5
|
||||
; 洗牌改令牌 Fisher-Yates(不放回抽 3,消费 reward_pick 封闭流)。
|
||||
; 迭代 11 第②批 商店共享池注记:货架 = 令牌级抽签(同 kind 去重每 kind 至多一卡、价格公式 tier-blind
|
||||
; ceil(基价×b^(关序−1)×A^visit) 维持批②裁定、购买消费在架令牌本体)。
|
||||
; 末关通关不发三选一与回血(奖励只朝下一战生效)。
|
||||
; 末关的关卡完成不发三选一与回血(奖励只朝下一战生效)。
|
||||
; 全部 provisional-pending-曲线调参批(值锚与上调约束归 难度曲线与Boss;档位配额结构锚 70/25/7/0——
|
||||
; schema TIER_QUOTAS,validate 硬断言,改总量须重裁)。
|
||||
@@ -765,7 +765,7 @@ rows = [{
|
||||
|
||||
; 敌方攻击区间批(2026-09-14 用户拍板②第二步——"上限掺运气"敌方侧:atk 上阶/下阶,中点恒等行值;
|
||||
; roll 时机 = 遭遇骰同流〔encounter〕spawn 期整战持久,每拍抖动否决〔数字可读性锚〕;B2 零随机纪律保持
|
||||
; ——收 override 即确定值。宽度 ±3 provisional 归 C3 测量迭代;验收 = 完美策略档 0 加成通关 ≤40%。
|
||||
; ——收 override 即确定值。宽度 ±3 provisional 归 C3 测量迭代;验收 = 完美策略档 0 加成关卡完成 ≤40%。
|
||||
|
||||
; 护甲等级掷点批(2026-09-16 用户拍板):armor_class 字段入列(缺省 0 向后兼容)——防守方掷点判定阈值基数,
|
||||
; 与 armor_type(克制属性轴)两概念严格分离、不做映射。首关六行暂定 0(正式行值归怪物/敌人系统设定批——
|
||||
@@ -868,7 +868,7 @@ rows = [{
|
||||
; d6 巢群/d7 小队/d9 石君王】。清单:L2 d0 45→50 · d1 18→22 · d4 20→24 · d5 68→88+atk3 · d6 18→22 ·
|
||||
; d7 18→22 · d9 80→112+atk3;L3 d0 50→62+atk3 · d1 25→30+atk3 · d4 20→26+atk3 · d5 76→98+atk3 ·
|
||||
; d6 20→26+atk3 · d7 20→26+atk3 · d9 92→140+atk3。
|
||||
; 目标对齿(熟练玩家):S0 零成长 L2 达 boss ~20%/通 ~0%、L3 ~0;S1(≈5 次 L1 通关锻造后,hero 快照
|
||||
; 目标对齿(熟练玩家):S0 零成长 L2 达 boss ~20%/通 ~0%、L3 ~0;S1(≈5 次 L1 关卡完成锻造后,hero 快照
|
||||
; 攻4/血26/甲11)L2 达 ~70%/通 ~50%;S2(攻6/血32/甲12)L3 通 ~55%。验证 = hero.tres 阶段快照 MC
|
||||
; (设计文档 §6,快照后恢复基线)。验证出带微调旋钮序:L2 d1/d6/d7 血 22→20 → d4 24→22 → d9 112→108。
|
||||
; provisional 未提交。
|
||||
|
||||
@@ -52,7 +52,7 @@ attune_refresh_upgrade = [{
|
||||
;
|
||||
; 数值设计批·关卡成长×永久成长对齿(2026-09-22——设计权威 qa-data/数值设计_关卡成长×永久成长_v1.md,
|
||||
; game-balancer 工作流,用户三拍板 1A/2A/3A):forge base_price 10→20(三行同,ratio 150% 不变,
|
||||
; 效果档 血+3/攻+1/甲+1 不变)——S1 一档(攻+2/血+6/甲+1 ≈ 120 GP)对齐「≈5 次 L1 通关开门」节奏
|
||||
; 效果档 血+3/攻+1/甲+1 不变)——S1 一档(攻+2/血+6/甲+1 ≈ 120 GP)对齐「≈5 次 L1 关卡完成开门」节奏
|
||||
; (实测 L1 通关局收入 ≈29-35 GP)。换算率表维持全 0(墙/钥匙闭环不依赖该轴,留扩展)。
|
||||
; 配套怪表改动见 enemy.tres 同日批注。provisional 未提交。
|
||||
;
|
||||
|
||||
@@ -8,7 +8,7 @@ script = ExtResource("1")
|
||||
; 旧 12 行删除——venom_edge/seal_burst 已随迭代 20 专长批迁出删除,本批实删 12 行)。三档=普通 7/优秀 3/稀有 2
|
||||
; (传说留空=空集合法,迭代 20 追认③先例)。解锁混合制(契约 8 逐件分配锁定表,2026-10-11 用户裁 C1):
|
||||
; 钥石购买件 unlock_keystone_cost(provisional 占位);条件件 unlock_requires 谓词行——计数键
|
||||
; adventure_cleared=生涯通关局数(C6 裁:通关=打通整局冒险)/boss_kill_count=生涯 Boss 击杀/vision_used=
|
||||
; adventure_cleared=生涯冒险成功局数(C6 裁:打通整局冒险)/boss_kill_count=生涯 Boss 击杀/vision_used=
|
||||
; 真视类工具累计使用(本批新增计数域)。全部数值 provisional 归调参批(契约 7)。
|
||||
; 常时参数面件(无订阅行——效果=开局/装板参数注入,非事件订阅):阔视野带/储备罐/远程镐/信标网络/刷新练习/
|
||||
; 刷新执照六件 effect_kinds 挂参数 kind,由注入面按装配集 kind 分发读取(_attune_refresh_x_total 先例)。
|
||||
|
||||
@@ -47,7 +47,7 @@ static func debug_open_line(seed_val: int, level: int, level_mode: bool) -> Stri
|
||||
# 调试战斗终局文案(level_end = 关内两层模式的关卡末)
|
||||
static func battle_end_text(result: String, level_end: bool) -> String:
|
||||
if level_end:
|
||||
return "关卡通关!"
|
||||
return "关卡完成!"
|
||||
if result == "victory":
|
||||
return "战斗胜利!敌人全灭。"
|
||||
return "战斗失败……探险者倒下了。"
|
||||
@@ -87,9 +87,9 @@ static func curio_pick_header(payload: Dictionary) -> String:
|
||||
var lines: Array[String] = ["第 %d 层通过!" % int(payload.get("level_index", 0))]
|
||||
var heal := int(payload.get("heal_gained", 0))
|
||||
if heal > 0:
|
||||
lines.append("通关回血 +%d(生命 %d → %d)" % [heal, int(payload.get("hp_before", 0)), int(payload.get("hp_after", 0))])
|
||||
lines.append("关卡完成回血 +%d(生命 %d → %d)" % [heal, int(payload.get("hp_before", 0)), int(payload.get("hp_after", 0))])
|
||||
else:
|
||||
lines.append("生命已满——通关回血 0(合法态)")
|
||||
lines.append("生命已满——关卡完成回血 0(合法态)")
|
||||
lines.append("选择一件提升带走(另两件丢弃不可反悔):")
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -151,19 +151,19 @@ static func bonus_keystone_line(payload: Dictionary) -> String:
|
||||
return "钥石 +%d · 累计 %d" % [int(payload.get("keystone_gained", 0)), int(payload.get("keystone_total", 0))]
|
||||
|
||||
|
||||
# 奖励节点三选一头部行(与通关三选一共享同库;渠道白名单分流——node-reward.md v2)
|
||||
# 奖励节点三选一头部行(与关卡完成三选一共享同库;渠道白名单分流——node-reward.md v2)
|
||||
static func node_curio_header() -> String:
|
||||
return "关卡奖励点\n从冒险牌库抽三选一(属性值提升与补给;与通关专长三选一同库分流):\n选择一件带走:"
|
||||
return "关卡奖励点\n从冒险牌库抽三选一(属性值提升与补给;与关卡完成专长三选一同库分流):\n选择一件带走:"
|
||||
|
||||
|
||||
# 冒险结算屏文案(摘要五件事:打穿没有 / 通关几关 / 止步第几关 / 钥石合计 / 主种子)
|
||||
# 冒险结算屏文案(摘要五件事:打穿没有 / 完成几关 / 止步第几关 / 钥石合计 / 主种子)
|
||||
static func adventure_summary_text(summary: Dictionary) -> String:
|
||||
var verdict := "冒险失败 · 未打穿"
|
||||
if bool(summary.get("cleared", false)):
|
||||
verdict = "冒险完成 · 已打穿"
|
||||
elif String(summary.get("result", "")) == "abandoned":
|
||||
verdict = "冒险放弃 · 未打穿"
|
||||
return "%s\n通关 %d 关 · 止步第 %d 层 · 钥石合计 %d · 主种子 %d" % [
|
||||
return "%s\n完成 %d 关 · 止步第 %d 层 · 钥石合计 %d · 主种子 %d" % [
|
||||
verdict, int(summary.get("levels_cleared", 0)), int(summary.get("stopped_at", 0)),
|
||||
int(summary.get("keystone_total", 0)), int(summary.get("master_seed", 0))]
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ var _fatal_screen: Callable
|
||||
var _gp_summary: Callable # () → 战斗总结 GP 行文案(组合根 lambda——_last_gain_amount 取数面在根)
|
||||
|
||||
var _pending_curio := {} # 三选一停点载荷暂存(战斗总结结算确认后开三选一面;续玩停点复用)
|
||||
var _curio_context := "level_clear" # 三选一面上下文(level_clear=通关停点→推进下一关 / reward_node=奖励节点→回图选路)
|
||||
var _curio_context := "level_clear" # 三选一面上下文(level_clear=关卡完成停点→推进下一关 / reward_node=奖励节点→回图选路)
|
||||
|
||||
|
||||
func _init(ctx: CurioFlowContext) -> void:
|
||||
@@ -82,7 +82,7 @@ func _skip_empty_stop() -> bool:
|
||||
return true
|
||||
|
||||
|
||||
# 三选一停点(非末关通关恰一次):Boss 胜利先经战斗总结结算面(settlement-summary v1.1——钥石
|
||||
# 三选一停点(非末关的关卡完成恰一次):Boss 胜利先经战斗总结结算面(settlement-summary v1.1——钥石
|
||||
# 增量行在其上),确认后进三选一面(level-clear.md v2);续玩停点恢复同入口(零自动推进契约)
|
||||
func on_presented(payload: Dictionary) -> void:
|
||||
if String(payload.get("context", "")) == "feat_ticket":
|
||||
@@ -111,7 +111,7 @@ func on_presented(payload: Dictionary) -> void:
|
||||
_refresh_presentations.call()
|
||||
|
||||
|
||||
# 奖励节点三选一(与通关三选一共享同池同权重同上限;CurioPickOverlay 共用面)。
|
||||
# 奖励节点三选一(与关卡完成三选一共享同池同权重同上限;CurioPickOverlay 共用面)。
|
||||
# 无替换子交互(同调装配不可变,见类头)。
|
||||
func present_node_curio() -> void:
|
||||
var adventure = _adventure_get.call()
|
||||
@@ -177,7 +177,7 @@ func _curio_card_texts(candidates: Array) -> Array:
|
||||
# 新件写行即有名有姓)
|
||||
|
||||
# 三选一刷新(X/Y 双源 + 对齐商店 MC-1——免费位用尽转 GP 付费档:
|
||||
# 同 shop_refresh_cost_base/curve 旋钮、同冒险钱包 runner.gp;通关停点经重发 curio_choice_presented 直落
|
||||
# 同 shop_refresh_cost_base/curve 旋钮、同冒险钱包 runner.gp;关卡完成停点经重发 curio_choice_presented 直落
|
||||
# 选择面,节点停点在此读在途态重 present——零自动推进契约不破,停点仍在途)
|
||||
func on_refresh() -> void:
|
||||
var adventure = _adventure_get.call()
|
||||
@@ -228,7 +228,7 @@ func on_resolved(result: Dictionary) -> void:
|
||||
_refresh_presentations.call()
|
||||
|
||||
|
||||
# 继续钮:收三选一面 → 上下文分派(通关停点=推进下一关 / 奖励节点=兑付确认回图选路——零自动推进)
|
||||
# 继续钮:收三选一面 → 上下文分派(关卡完成停点=推进下一关 / 奖励节点=兑付确认回图选路——零自动推进)
|
||||
func on_continue() -> void:
|
||||
_close_modal.call(curio_overlay)
|
||||
curio_overlay.close_pick()
|
||||
|
||||
@@ -188,10 +188,10 @@ static func outcome_text(outcome: Dictionary) -> String:
|
||||
static func tune_sim_report(n: int, atk_pct: int, hp_pct: int, wins: int, dmg_all: Array, hp_left_win: Array, hp_boss: Array, rounds_all: Array, death_nodes: Dictionary, base_hp: int) -> String:
|
||||
var lines: Array[String] = []
|
||||
lines.append("[b]模拟 N=%d(atk+%d%% / hp+%d%%)[/b]" % [n, atk_pct, hp_pct])
|
||||
lines.append("通关率 [color=#%s]%.0f%%[/color](%d/%d)" % [UiKit.HEX_LOG_WIN, 100.0 * float(wins) / float(maxi(1, n)), wins, n])
|
||||
lines.append("关卡完成率 [color=#%s]%.0f%%[/color](%d/%d)" % [UiKit.HEX_LOG_WIN, 100.0 * float(wins) / float(maxi(1, n)), wins, n])
|
||||
lines.append("整关承伤:均 %.1f · p90 %.0f(基础 HP %d)" % [_mean(dmg_all), _p90(dmg_all), base_hp])
|
||||
if not hp_left_win.is_empty():
|
||||
lines.append("通关剩余 HP 均 %.1f · 进 Boss HP 均 %.1f" % [_mean(hp_left_win), _mean(hp_boss)])
|
||||
lines.append("关卡完成剩余 HP 均 %.1f · 进 Boss HP 均 %.1f" % [_mean(hp_left_win), _mean(hp_boss)])
|
||||
lines.append("平均轮数 %.1f" % _mean(rounds_all))
|
||||
if death_nodes.is_empty():
|
||||
lines.append("败北节点:—")
|
||||
|
||||
@@ -51,7 +51,7 @@ static func _on_runner_node(root: Variant, node: Dictionary, info: Dictionary) -
|
||||
root._show_board_fx_for_node(t)
|
||||
root.log_view.append_bb(root.Narrator.node_banner(root.run_scope.runner, node, t))
|
||||
elif t == "reward":
|
||||
# 奖励节点三选一(与通关三选一共享同池同权重同上限;CurioPickOverlay 共用面)。
|
||||
# 奖励节点三选一(与关卡完成三选一共享同池同权重同上限;CurioPickOverlay 共用面)。
|
||||
# 呈现编排归 curio_interact_controller.present_node_curio。
|
||||
root._bonus.present_node_curio()
|
||||
root.log_view.append_bb(root.Narrator.reward_line())
|
||||
|
||||
@@ -35,7 +35,7 @@ func is_running() -> bool:
|
||||
|
||||
func run(n: int) -> void:
|
||||
_running = true
|
||||
# 共享日志会话(模拟运行与真人试玩同日志合规——操作/轮次/战斗/通关全量 JSONL;
|
||||
# 共享日志会话(模拟运行与真人试玩同日志合规——操作/轮次/战斗/关卡完成全量 JSONL;
|
||||
# 一文件多局,run 行携带种子切分局次)
|
||||
var logger = PlaytestLogger.new()
|
||||
logger.ap_per_round = int(db.action_economy.ap_per_round)
|
||||
|
||||
@@ -542,7 +542,7 @@ func _rebuild_recent(data: Dictionary) -> void:
|
||||
verdict = "完成"
|
||||
elif String(s.get("result", "")) == "abandoned":
|
||||
verdict = "放弃"
|
||||
lines.append("最近冒险:%s · 通关 %d 关 · 钥石 +%d · 种子 %d" % [verdict,
|
||||
lines.append("最近冒险:%s · 完成 %d 关 · 钥石 +%d · 种子 %d" % [verdict,
|
||||
int(s.get("levels_cleared", 0)), int(s.get("keystone_total", 0)), int(s.get("master_seed", 0))])
|
||||
_recent_list.text = " | ".join(lines)
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ extends Control
|
||||
signal closed
|
||||
|
||||
var _total_label: Label # 冒险总局数(开局即计含放弃与失败)
|
||||
var _cleared_label: Label # 通关局数(三关全通才计 1)
|
||||
var _clears_label: Label # 各关通关次数(一行三关——display_name ×次数)
|
||||
var _cleared_label: Label # 冒险成功局数(三关全通才计 1)
|
||||
var _clears_label: Label # 各关完成次数(一行三关——display_name ×次数)
|
||||
var _boss_first_label: Label # Boss 首杀(已首杀关数/总关数)
|
||||
var _boss_kills_label: Label # Boss 总击杀(含重复)
|
||||
var _keystone_badge: UiKit.KeystoneBadge # 钥石余额(badge 工厂件——状态面板同形制)
|
||||
@@ -33,14 +33,14 @@ func _init() -> void:
|
||||
UiKit.FS_HINT, false, UiKit.WARM_SOFT)
|
||||
box.add_child(hint)
|
||||
|
||||
# 组一·冒险战绩(终身计数域——存档 v6:总局数开局即计、通关局数三关全通才计)
|
||||
# 组一·冒险战绩(终身计数域——存档 v6:总局数开局即计、冒险成功局数三关全通才计)
|
||||
# 对比度口径:页底=暖亮档深棕(BG_BRIGHT),文字全用亮墨族 token(WARM 9.0:1 / WARM_SOFT 8.0:1 /
|
||||
# CANDLE 7.2:1 / INK_L_GOLD 9.1:1 实测对 BG_BRIGHT 全过 §7.2 ≥4.5:1——深墨族〔INK_LABEL〕
|
||||
# 压深棕底仅 ~1.3:1 属违例,禁用)
|
||||
_section_head(box, "冒险战绩")
|
||||
_total_label = _row(box, "冒险总局数")
|
||||
_cleared_label = _row(box, "通关局数")
|
||||
_clears_label = _row(box, "各关通关次数")
|
||||
_cleared_label = _row(box, "冒险成功局数")
|
||||
_clears_label = _row(box, "各关完成次数")
|
||||
_boss_first_label = _row(box, "Boss 首杀")
|
||||
_boss_kills_label = _row(box, "Boss 总击杀")
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 提升三选一覆盖层(模态):通关停点的三卡点选→确认→兑付反馈两态呈现。
|
||||
## 提升三选一覆盖层(模态):关卡完成停点的三卡点选→确认→兑付反馈两态呈现。
|
||||
## 文案拼装归 Narrator 经组合根喂入——本类零文案装配。
|
||||
class_name CurioPickOverlay
|
||||
extends Control
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 选档界面·全屏页:3 存档位(新档/进档/删档)+ 槽摘要三项(钥石余额/通关局数/冒险总局数)。
|
||||
## 选档界面·全屏页:3 存档位(新档/进档/删档)+ 槽摘要三项(钥石余额/冒险成功局数/冒险总局数)。
|
||||
## 结构树层级:选档 → 进档 → 营地(本面零存档内功能入口——禁营地/冒险/生涯跳转与购买/查看入口);
|
||||
## 删档必经二次确认(确认弹窗与分派归组合根消费方编排——本面只发意图信号)。
|
||||
## 槽数与槽态(空/有档)唯一来自组合根喂入数据(open_save_select 每开重建——组合根经卡① 窥视 API
|
||||
@@ -15,7 +15,7 @@ signal load_slot_requested(slot: int)
|
||||
signal delete_slot_requested(slot: int)
|
||||
|
||||
var _rows_box: VBoxContainer
|
||||
var _summary_labels: Dictionary[int, Label] = {} # 槽号→摘要读数 label(通关/冒险两值——探针取值缝)
|
||||
var _summary_labels: Dictionary[int, Label] = {} # 槽号→摘要读数 label(冒险成功/冒险两值——探针取值缝)
|
||||
var _occupied_flags: Dictionary[int, bool] = {} # 槽号→有档态(操作集二分断言缝;判定源=喂入数据)
|
||||
var _new_btns: Dictionary[int, Button] = {} # 槽号→新档钮(空/坏槽——操作集断言与点击链驱动缝)
|
||||
var _load_btns: Dictionary[int, Button] = {} # 槽号→进档钮(有档槽)
|
||||
@@ -84,7 +84,7 @@ func _build_slot_row(sd: Dictionary) -> void:
|
||||
sp.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
if occupied:
|
||||
row.add_child(UiKit.keystone_badge("钥石 %d" % int(sd.get("keystone", 0))))
|
||||
var summary_l := UiKit.label("通关 %d 局 · 冒险 %d 局" % [int(sd.get("cleared", 0)), int(sd.get("total", 0))],
|
||||
var summary_l := UiKit.label("冒险成功 %d 局 · 冒险 %d 局" % [int(sd.get("cleared", 0)), int(sd.get("total", 0))],
|
||||
UiKit.FS_BODY, false, UiKit.CANDLE)
|
||||
_summary_labels[slot] = summary_l # 摘要读数缝(禁词断言枚举 Label 全集时含此件)
|
||||
row.add_child(summary_l)
|
||||
|
||||
@@ -208,7 +208,7 @@ const HEX_LOG_DMG_BODY := "e0a050" # 伤害行正文橙
|
||||
const HEX_LOG_VOIDED := "b08060" # 未生效行暗橙(⚠ 划线)
|
||||
const HEX_LOG_CAST := "b09a72" # 施放行暖石(✦ 具名技能)
|
||||
const HEX_LOG_DECAY := "8a9ab0" # 状态衰减蓝灰
|
||||
const HEX_LOG_WIN := "80d080" # 胜利判定绿(模拟报告通关率同档)
|
||||
const HEX_LOG_WIN := "80d080" # 胜利判定绿(模拟报告关卡完成率同档)
|
||||
const HEX_ROUTE_NOTE := "d0c090" # 奖励点预告暗金(narrator 行)
|
||||
const HEX_SEED_NOTE := "c8a860" # 种子/开场行暗金(narrator 行)
|
||||
|
||||
@@ -400,7 +400,7 @@ const FS_H1 := 34 # 主标题/演算暴击
|
||||
|
||||
# ── 秘宝解锁条件计数键 → 呈现标签(秘宝匣柜台条件进度文案单点;键域=
|
||||
# TreasureItemTable.UNLOCK_COUNTER_KEYS 封闭枚举,键出域走「条件」兜底不炸)──
|
||||
const TREASURE_REQ_LABELS := {"adventure_cleared": "通关", "boss_kill_count": "Boss击杀", "vision_used": "真视使用"}
|
||||
const TREASURE_REQ_LABELS := {"adventure_cleared": "冒险成功", "boss_kill_count": "Boss击杀", "vision_used": "真视使用"}
|
||||
# 秘宝档位 → 呈现名(契约 2 三档;legendary 传说空集合法不在柜)
|
||||
const TREASURE_TIER_LABELS := {"common": "普通", "excellent": "优秀", "rare": "稀有", "legendary": "传说"}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ var _tested_round_summary := false # 卡⑥ wr1-b——上轮摘要恢复断
|
||||
var _tested_map_cap := false # 迭代36 卡A——地图屏加成帽换代断言(每 run 一次)
|
||||
var _tested_status := false # 状态面板角标开合/守卫断言(每 run 一次)
|
||||
var _tested_map_status := false # 地图屏角标一次性 case(选路态零扰动)
|
||||
var _inter_confirms := 0 # 大奖励屏确认计数(冒险推进停点——首见确认推进 L2,再见 = L2 通关收口)
|
||||
var _inter_confirms := 0 # 大奖励屏确认计数(冒险推进停点——首见确认推进 L2,再见 = L2 关卡完成收口)
|
||||
var _settlement_confirms := 0 # 卡⑥ 胜利结算框确认计数(观测面——每场普通战斗胜利恰一)
|
||||
var _route_clicks := 0 # 选路覆盖层点击计数(岔路使用率观测面)
|
||||
var _locktest_state := 0 # T1 D1 锁期/重放断言状态机(0=待注入 1=等重放 2=完结)——每 run 一次
|
||||
@@ -369,7 +369,7 @@ func _ready() -> void:
|
||||
_problems.append("生涯页关闭后未恢复开始层")
|
||||
# 换回全新零档(营地购买态不泄入后续冒险段——确定性;档案残留下次段首清理兜底)
|
||||
ProbeUiUtil.isolate_save(main, "smoke_click_probe_reset", true, false, true) # 零档换注(继续上局钮已随三入口化撤出——卡② mn8)
|
||||
main.debug_overlay.set_sliders(400, 200) # 高加成保 L1 可通关——L2 岔路选路段只能经冒险推进到达(单关直入口退役)
|
||||
main.debug_overlay.set_sliders(400, 200) # 高加成保 L1 可完成——L2 岔路选路段只能经冒险推进到达(单关直入口退役)
|
||||
main.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机,行为走查确定性禁依赖默认分支)
|
||||
var t1 := Time.get_ticks_msec()
|
||||
main._start_adventure_from_menu()
|
||||
@@ -400,7 +400,7 @@ func _ready() -> void:
|
||||
_problems.append("首格未可点(cells=0)")
|
||||
print("[click_probe] 首格可点计时 %dms(进入战斗屏口径,≤500ms AC)· 场景冷启动 %dms(含 UI 构建 %dms,观测项)" % [
|
||||
first_cell_ms, t_total, t_ui_ready])
|
||||
const ACTION_BUDGET_FRAMES := 20000 # 主循环帧预算(坏态挂死兜底上限——常态通关远低于此;有挂帧证据才收紧,勿让坏态等满预算才报)
|
||||
const ACTION_BUDGET_FRAMES := 20000 # 主循环帧预算(坏态挂死兜底上限——常态跑完远低于此;有挂帧证据才收紧,勿让坏态等满预算才报)
|
||||
var action_budget := ACTION_BUDGET_FRAMES
|
||||
# P0-2 回归:本 while 循环体缩进必须整体在循环内——continue 落在
|
||||
# 循环外成解析错误、探针不可加载。bonus 分支保留为体内兄弟分支。
|
||||
@@ -412,7 +412,7 @@ func _ready() -> void:
|
||||
_seals_placed.clear()
|
||||
continue
|
||||
if main.curio_overlay.visible:
|
||||
# 三选一停点(CurioPickOverlay——奖励节点/通关兑付共用面,须与硬锁态适配,
|
||||
# 三选一停点(CurioPickOverlay——奖励节点/关卡完成兑付共用面,须与硬锁态适配,
|
||||
# 未适配则走到即卡死)。两态分派:result 态(兑付反馈——点继续推进)/
|
||||
# 待选态(点首件+确认)。真实信号路径全线。〔无替换子态——装配不可变契约。〕
|
||||
# 三选一停点输入锁定断言(停点态断言面:待选/兑付两态共用此层)。
|
||||
@@ -496,7 +496,7 @@ func _ready() -> void:
|
||||
_seals_placed.clear()
|
||||
continue
|
||||
if main._inter_overlay.visible:
|
||||
# 大奖励屏(冒险级停点——非末关通关恰一次):首见确认推进 L2(真实按钮信号路径);再见 = L2 已通关,收口
|
||||
# 大奖励屏(冒险级停点——非末关的关卡完成恰一次):首见确认推进 L2(真实按钮信号路径);再见 = L2 已关卡完成,收口
|
||||
if not main._input_locked():
|
||||
_problems.append("大奖励屏打开时输入未锁定")
|
||||
_inter_confirms += 1
|
||||
@@ -512,7 +512,7 @@ func _ready() -> void:
|
||||
_problems.append("大奖励确认后未推进到第二关(level_index != 2)")
|
||||
else:
|
||||
_done = true
|
||||
print("[click_probe] 第二关大奖励屏(L2 通关)——探针收口 · 选路口点击 %d 次" % _route_clicks)
|
||||
print("[click_probe] 第二关大奖励屏(L2 关卡完成)——探针收口 · 选路口点击 %d 次" % _route_clicks)
|
||||
break
|
||||
_seals_placed.clear()
|
||||
continue
|
||||
@@ -666,7 +666,7 @@ func _ready() -> void:
|
||||
# 驱动设计(0/0 白板局生存性不成立的实证处置——L2 记录进实现报告):
|
||||
# ① 菜单开新局·白板反向=独立短局:0/0 滑条开局断言 L1 开场行零后缀(不开驱动——
|
||||
# 断言完成后该局被 ② 的开局自然替换,白板局生存性不参与判定);
|
||||
# ② 局内加算区正向=生存局:400/200 滑条开局保可通关(:363 先例),三选一专选 atk/max_hp
|
||||
# ② 局内加算区正向=生存局:400/200 滑条开局保可完成(:363 先例),三选一专选 atk/max_hp
|
||||
# 卡(无则任选推进——不触后缀、循环重试);至大奖励屏先 reset_sliders() 再确认进下一层
|
||||
# ——L2 开场行时点 pct=0 且新档 ov=0,后缀只能归因局内加算区(变异:回退条件 hunk→红);
|
||||
# ③ 断点续玩正向:L2 入口选路停点(bonus 已入账、bonus_pending/bonus_result 皆空——选路停点形)
|
||||
|
||||
@@ -431,10 +431,10 @@ func _save_select_contract(main: Node, tpl: String) -> void:
|
||||
else:
|
||||
if not (has_new and not has_load and not has_del):
|
||||
problems.append("[savesel] 行%d 操作集失守(空/坏槽应=仅新档、零进档删档含置灰形态)〔sl4〕" % row_idx)
|
||||
# sl2 摘要三项逐槽读数:槽1 喂入值逐字(钥石 555/通关 2 局/冒险 7 局)
|
||||
# sl2 摘要三项逐槽读数:槽1 喂入值逐字(钥石 555/冒险成功 2 局/冒险 7 局)
|
||||
var sum1: Label = sso._summary_labels.get(1)
|
||||
if sum1 == null or sum1.text != "通关 2 局 · 冒险 7 局":
|
||||
problems.append("[savesel] 槽1 摘要读数失守(got=%s 应=通关 2 局 · 冒险 7 局)〔sl2〕" % (sum1.text if sum1 else "null"))
|
||||
if sum1 == null or sum1.text != "冒险成功 2 局 · 冒险 7 局":
|
||||
problems.append("[savesel] 槽1 摘要读数失守(got=%s 应=冒险成功 2 局 · 冒险 7 局)〔sl2〕" % (sum1.text if sum1 else "null"))
|
||||
var all_texts: Array[String] = []
|
||||
_collect_labels(sso, all_texts)
|
||||
var has_keystone := false
|
||||
@@ -443,7 +443,7 @@ func _save_select_contract(main: Node, tpl: String) -> void:
|
||||
has_keystone = true
|
||||
if not has_keystone:
|
||||
problems.append("[savesel] 槽1 钥石徽读数缺失(应=钥石 555)〔sl2〕")
|
||||
# sl3 摘要禁项:断点/种子/「继续」/停点/「 关」(关数形——「通关」无空格不误伤)零命中
|
||||
# sl3 摘要禁项:断点/种子/「继续」/停点/「 关」(关数形——摘要「冒险成功 X 局 · 冒险 Y 局」不含「关」字)零命中
|
||||
#(裁决 4——零未完冒险读数;词形覆盖=对照表 sl3 缺陷形态「断点关数拼进槽卡」的拦截面)
|
||||
for needle in ["断点", "种子", "继续", "停点", " 关"]:
|
||||
for t in all_texts:
|
||||
|
||||
@@ -72,8 +72,11 @@ func _ready() -> void:
|
||||
if main.debug_overlay.is_running():
|
||||
problems.append("模拟未在预算帧内完成(疑似挂死)")
|
||||
var out: String = main.debug_overlay.output_text()
|
||||
if not out.contains("通关率"):
|
||||
problems.append("模拟输出缺通关率")
|
||||
if not out.contains("关卡完成率"):
|
||||
problems.append("模拟输出缺关卡完成率")
|
||||
# 口径换代 V7:胜局统计行措辞钉子(atk+100% 加成档 10 局必有胜局——hp_left_win 非空路径恒出)
|
||||
if not out.contains("关卡完成剩余 HP 均"):
|
||||
problems.append("模拟输出缺关卡完成剩余 HP 行〔口径换代 V7〕")
|
||||
if not out.contains("败北节点"):
|
||||
problems.append("模拟输出缺败北节点")
|
||||
if "N=10" not in out:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# 刷新/行进序列/逐战上限序列——后续统计分析的取数面);
|
||||
# ② 全量操作日志 = playtest_logger 共享会话 JSONL(与真人试玩同合规口径,每策略一批一文件,
|
||||
# run 行携带种子/策略切分局次,落 user://logs/)。
|
||||
# 本工具只采集与落盘数据,不做统计判读(通关率/败因分布归分析会话)。
|
||||
# 本工具只采集与落盘数据,不做统计判读(关卡完成率/败因分布归分析会话)。
|
||||
# 运行:Godot_console.exe --headless --path . --script res://sim/adventure_monte_carlo.gd -- [N] [seed0] [normal|perfect|both] [route min|max]
|
||||
extends SceneTree
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ static func choose_dig(minesweeper) -> Dictionary:
|
||||
# 进阶级 = 联动定式(1-1 / 1-2 / 1-2-1 / 1-2-2-1 / 子集缩减)与概率分析——本切片归完美档 subset 推理承载,
|
||||
# 不入普通画像(新手不可靠掌握)。本档语义 = 只做新手级推理 + 无处可推时按既有口径盲挖边界格(新手冲动)+
|
||||
# 不主动刷新(新手无刷新策略意识)。测量定位:教会基础规则后的真实玩家下限近似
|
||||
# (乱点/最近格口径测的是无认知玩家下限,对通关率带判读失真,不作普通画像)。
|
||||
# (乱点/最近格口径测的是无认知玩家下限,对关卡完成率带判读失真,不作普通画像)。
|
||||
|
||||
# 普通画像单步决策入口(每动作一查——挖掘揭示新数字后重推理):move/dig/seal/stop 四值(无 refresh)。
|
||||
static func novice_action(minesweeper) -> Dictionary:
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 日志合规:任何模拟运行与真人试玩同日志——操作记录 / 轮次状态 / 战斗结束 / 通关结束 全量 JSONL。
|
||||
# 日志合规:任何模拟运行与真人试玩同日志——操作记录 / 轮次状态 / 战斗结束 / 关卡完成 全量 JSONL。
|
||||
# logger=null 时自建会话(每局一文件),批量调用方传共享 logger(log_run 局次标记切分)。
|
||||
# 关卡自动通关策略(理性玩家近似)——呈现层数值调整板的批量模拟与 headless 扫描
|
||||
# 关卡自动完成策略(理性玩家近似)——呈现层数值调整板的批量模拟与 headless 扫描
|
||||
# 共用同一实现,保证"面板模拟读数"与"扫描表读数"同口径。策略与 smoke_all 战斗自动玩家同族:
|
||||
# 挖最近可挖格 / 无可挖停手 / 奖励恢复点立即确认。
|
||||
# run_level = 机制夹具/单战锚点专用(smoke_all 机制断言、rhythm/defense/damage 单战扫描)——
|
||||
# 通关率/难度类测量禁用本入口(满血零上下文进关,与真实冒险条件通关率脱节),一律走 run_adventure。
|
||||
# 关卡完成率/难度类测量禁用本入口(满血零上下文进关,与真实冒险条件关卡完成率脱节),一律走 run_adventure。
|
||||
# 策略档 policy:"normal"(默认)/ "perfect"(零盲挖/全封印/卡死刷新——上界测量仪,求解器与
|
||||
# 动作语义见 auto_player.gd 完美策略档节)。
|
||||
# 选路驱动(零自动推进):兑付确认/战斗胜利后 runner 进选路态,驱动循环按 route 策略显式
|
||||
# pick_next_node;route = "min"/"max"(候选节点句柄最小/最大——确定性路径轴,零随机流自开;默认 "min")。
|
||||
# run_adventure 三关连探驱动(AdventureRunner——主种子恰一次、通关兑付停点确认、adventure_finished
|
||||
# run_adventure 三关连探驱动(AdventureRunner——主种子恰一次、关卡完成兑付停点确认、adventure_finished
|
||||
# 置 result/summary;外层每关重取 runner 实例经 _drive_level 复用单关驱动)。
|
||||
# 大奖励停点 = 三选一驱动(候选取首件/替换放弃新件)。
|
||||
# 普通档 v3 = 完美策略 + 受控失误注入(失误流走独立策略 RNG——种子派生自局种子,逐局确定性/
|
||||
@@ -303,7 +303,7 @@ static func _pick_token(candidates: Array, feat_mode: String = "off") -> Diction
|
||||
return best
|
||||
|
||||
|
||||
# 三选一兑付完成 → 上下文分派(通关停点=推进下一关 / 奖励节点=回图选路)
|
||||
# 三选一兑付完成 → 上下文分派(关卡完成停点=推进下一关 / 奖励节点=回图选路)
|
||||
static func _on_adventure_curio_resolved(result: Dictionary, st: Dictionary, adr) -> void:
|
||||
st.reward_events.append({"curio_resolved": result.duplicate(true)})
|
||||
if String(result.get("context", "")) == "reward_node":
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
# 判定端 tools/balance_verifier.py 的数据源,本文件零判定逻辑
|
||||
# 两段一次采集,stdout 以「VJSON 」前缀行出机器读 JSON(其余行为人读进度/摘要):
|
||||
# adventure 段 全冒险连探基线(run_adventure 零永久强化)× 双画像 × na——止步分布、败退节点
|
||||
# (摘要 defeat 明细 + 末场战斗敌方名)与逐关条件通关率派生源;inject 档行带
|
||||
# (摘要 defeat 明细 + 末场战斗敌方名)与逐关条件关卡完成率派生源;inject 档行带
|
||||
# inject_tag/override(flat 三键)
|
||||
# campaign 段 营地循环战役 × 双画像 × k 局——SaveManager 隔离档 + MetaProgression 真实账本:
|
||||
# 逐局 hero_override() 注入 → 冒险 → settle_adventure 入账 → 花费策略买锻造,
|
||||
# 回答「累计几次通关冒险后下一关可过」(元进度换算率标定的模拟读数源)
|
||||
# 回答「累计几次冒险成功后下一关可过」(元进度换算率标定的模拟读数源)
|
||||
# 运行:Godot_console.exe --headless --path . --script res://sim/verifier_batch.gd -- [k=v ...]
|
||||
# na=40(基线冒险局数) k=40(战役局数) seed0=8000
|
||||
# only=adventure,campaign(缺省全段) spend=hp_first|atk_first|armor_first(锻造花费优先序)
|
||||
# inject=max_hp:3,atk:1(冒险段 flat 注入档——三键 {max_hp, atk, armor_class},缺省全零白板;
|
||||
# 换算率标定用:每档与白板同种子流配对,逐关条件通关率差 = 该档的带平移量实测)
|
||||
# 换算率标定用:每档与白板同种子流配对,逐关条件关卡完成率差 = 该档的带平移量实测)
|
||||
# 种子纪律:unique 逐局独立(掷点测量修复批 A 案——seed0 + 全局游标,每掷点恰消费一次)。
|
||||
# 同 seed0 跨档复用 = 配对读数(同序种子流——分析侧按种子配对消方差)。
|
||||
# 日志合规:全段共享 playtest_logger 会话(与真人试玩同日志),分析工具可直接消费。
|
||||
@@ -108,7 +108,7 @@ func _emit(row: Dictionary) -> void:
|
||||
print("VJSON " + JSON.stringify(row))
|
||||
|
||||
|
||||
# ---- adventure 段:全冒险连探基线(零永久强化——止步分布/败退节点归因/逐关条件通关率派生源);
|
||||
# ---- adventure 段:全冒险连探基线(零永久强化——止步分布/败退节点归因/逐关条件关卡完成率派生源);
|
||||
# inject 档 = 同种子流配对的 OFAT 边际读数(行带 inject_tag/override flat 三键) ----
|
||||
|
||||
func _seg_adventure(db, autoplay, logger, opt: Dictionary, counts: Dictionary) -> void:
|
||||
|
||||
@@ -150,7 +150,7 @@ timeout 120s godot --headless --script tests/run_all.gd
|
||||
`tests/static_scan.sh` 头注**——迭代 32 两卡是代码实现批,按流水线红线禁写 `design/`,故这两段未进架构文档。
|
||||
新违规案例必须**先补扫描规则再修码**。
|
||||
|
||||
S11 代码注释禁用词:扫描 game/ 全域 .gd/.tres/.md/.json/.sh 的注释面;禁词全部由 `design/gdd/术语表.md`「退役词对照」第一列解析供给(脚本内不内联任何词面——单一事实源);解析结果再按「巢穴」「旗」「flag_*」三条整行排除,另设同形异义豁免「去重闸」(封印重判去重的机制判别器,与某闸档退役词后二字同形——行内含「去重闸」整行跳过);三层豁免(闸门自指/显式留档 4 处/非注释面天然不取);两道自检(扫描面文件数非空+词表解析非空)。
|
||||
S11 代码注释禁用词:扫描 game/ 全域 .gd/.tres/.md/.json/.sh 的注释面;禁词全部由 `design/gdd/术语表.md`「退役词对照」第一列解析供给(脚本内不内联任何词面——单一事实源);解析结果再按「巢穴」「旗」「flag_*」三条整行排除,另设同形异义豁免「去重闸」(封印重判去重的机制判别器,与某闸档退役词后二字同形——行内含「去重闸」整行跳过);三层豁免(闸门自指/显式留档 20 处〔契约原名引述与带日期历史读数注释,清单见 `tests/static_scan.sh` 头注〕/非注释面天然不取);两道自检(扫描面文件数非空+词表解析非空)。
|
||||
|
||||
S12 效果引擎无状态化旧件零残留:扫描 game/ 全域 `.gd` 与 `game/data/tables/*.tres`,禁已删符号(`counters_export`/`counters_seed`/`_reset_tree_limited_counters` 等)与带引号的旧计数器 id 字面(引号锚定,不误伤 `adv_`/`round_` 前缀新名);扫描面文件数为零即判红。
|
||||
|
||||
|
||||
@@ -116,14 +116,14 @@ func _save_summary_integration() -> void:
|
||||
var ks_run := 0
|
||||
for li in range(1, int(_db.level.rows.size()) + 1):
|
||||
ks_run += int(_db.meta_boss_keystone_row(li).get("first_kill", 0))
|
||||
Kit.check("摘要集成:冒险驱动满配通关(seed 42)", String(st9.get("result", "")) == "completed")
|
||||
Kit.check("摘要集成:冒险驱动满配冒险成功(seed 42)", String(st9.get("result", "")) == "completed")
|
||||
Kit.check("集成钥石余额 = ΣBoss 首杀行值(%d——逐杀掉落全源)" % f9.keystone_balance(), f9.keystone_balance() == ks_run)
|
||||
Kit.check("集成通关次数表全 1", f9.clear_count(1) == 1 and f9.clear_count(2) == 1 and f9.clear_count(3) == 1)
|
||||
Kit.check("集成关卡完成次数表全 1", f9.clear_count(1) == 1 and f9.clear_count(2) == 1 and f9.clear_count(3) == 1)
|
||||
Kit.check("集成三关首杀标记齐", f9.boss_first_kill_marked(1) and f9.boss_first_kill_marked(2) and f9.boss_first_kill_marked(3))
|
||||
Kit.check("集成 recent 1 条且 seed 42", f9.recent_summaries().size() == 1 and int(f9.recent_summaries()[0].get("master_seed", -1)) == 42)
|
||||
var st9b: Dictionary = LevelAutoplay.run_adventure(_db, 42, 900, 400)
|
||||
f9.apply_adventure_summary(st9b.get("summary", {}))
|
||||
Kit.check("第二局后通关次数全 2", f9.clear_count(1) == 2 and f9.clear_count(2) == 2 and f9.clear_count(3) == 2)
|
||||
Kit.check("第二局后关卡完成次数全 2", f9.clear_count(1) == 2 and f9.clear_count(2) == 2 and f9.clear_count(3) == 2)
|
||||
Kit.check("第二局后余额翻倍(2 × Σ首杀——模拟驱动恒首杀口径)", f9.keystone_balance() == 2 * ks_run)
|
||||
Kit.check("集成 recent 长度 2", f9.recent_summaries().size() == 2)
|
||||
Kit.check("集成写盘成功", f9.write_to_disk())
|
||||
|
||||
@@ -127,9 +127,13 @@ fi
|
||||
# 在册断言标签成片误红;代价=该行同时出现的真闸档用词也不报,靠文档域 08 闸兜住)
|
||||
# 扫描面:game/ 全域 .gd/.tres/.md/.json/.sh(.txt 不收=第三方许可证非本仓库文本)
|
||||
# 注释文本提取:.gd/.sh 取 # 后文本;.tres 取 ; 后文本;.md 取整行;.json 无注释语法(保留扩展名但不产生注释段)
|
||||
# 三层豁免依序:①闸门自指(行内含检查/禁令形态即跳过)②显式留档豁免单 7 处 ③非注释面天然不取
|
||||
# 三层豁免依序:①闸门自指(行内含检查/禁令形态即跳过)②显式留档豁免单 20 处 ③非注释面天然不取
|
||||
# 豁免单新增三处(2026-10-26 令牌配额口径清扫批 B4b 用户裁定保留):adventure_table.gd:26 档名映射对照注
|
||||
# +adventure_table.gd:93 与 curio_deck.gd:130 四口径契约④原名引述——合法载体非残留,改写即断链
|
||||
# 豁免单再增 13 处(2026-10-26 口径换代批用户裁「带日期历史读数注释保留原文」):
|
||||
# hero.tres:21/:36、enemy.tres:751/:891、action_economy.tres:15/:16/:26/:27、status_effects.tres:91、
|
||||
# meta.tres:48/:56/:62(.tres 十二处历史读数/带日期批记录)+level_autoplay.gd:560(完美档 41% 历史实证)——
|
||||
# 历史读数原文锚定当时实测口径,改写即失真;现行口径描述不豁免(已随批换代)
|
||||
|
||||
if [ -d game/core ]; then
|
||||
S11_TABLE="design/gdd/术语表.md"
|
||||
@@ -184,6 +188,7 @@ else
|
||||
if(FILENAME~"adventure_table"&&FNR==26)next
|
||||
if(FILENAME~"adventure_table"&&FNR==93)next
|
||||
if(FILENAME~"curio_deck"&&FNR==130)next
|
||||
if(FILENAME~"level_autoplay"&&FNR==560)next
|
||||
print FILENAME":"FNR":"words[i]
|
||||
next
|
||||
}
|
||||
@@ -201,6 +206,18 @@ else
|
||||
if($0~/contains\(|find\(|grep|has_char\(|禁「|禁『|零「|零『/)next
|
||||
if(index(cmt,"去重闸")>0)next
|
||||
if(FILENAME~"enemy.tres"&&FNR==653)next
|
||||
if(FILENAME~"hero.tres"&&FNR==21)next
|
||||
if(FILENAME~"hero.tres"&&FNR==36)next
|
||||
if(FILENAME~"enemy.tres"&&FNR==751)next
|
||||
if(FILENAME~"enemy.tres"&&FNR==891)next
|
||||
if(FILENAME~"action_economy.tres"&&FNR==15)next
|
||||
if(FILENAME~"action_economy.tres"&&FNR==16)next
|
||||
if(FILENAME~"action_economy.tres"&&FNR==26)next
|
||||
if(FILENAME~"action_economy.tres"&&FNR==27)next
|
||||
if(FILENAME~"status_effects.tres"&&FNR==91)next
|
||||
if(FILENAME~"meta.tres"&&FNR==48)next
|
||||
if(FILENAME~"meta.tres"&&FNR==56)next
|
||||
if(FILENAME~"meta.tres"&&FNR==62)next
|
||||
print FILENAME":"FNR":"words[i]
|
||||
next
|
||||
}
|
||||
|
||||
@@ -99,11 +99,11 @@ func _api_mark_node(_node: Dictionary, _info: Dictionary, seq: Array) -> void:
|
||||
seq.append("node_entered")
|
||||
|
||||
|
||||
# ③ 冒险验收断言(映射 冒险结构.md 验收标准——表覆盖/满配通关/种子复现/大奖励/败北线/
|
||||
# ③ 冒险验收断言(映射 冒险结构.md 验收标准——表覆盖/满配冒险成功/种子复现/大奖励/败北线/
|
||||
# 重复拒绝/GP 跨关延续/加算区同账/恢复数学/放弃)
|
||||
|
||||
|
||||
# ③ 冒险验收断言(映射 冒险结构.md 验收标准——表覆盖/满配通关/种子复现/大奖励/败北线/
|
||||
# ③ 冒险验收断言(映射 冒险结构.md 验收标准——表覆盖/满配冒险成功/种子复现/大奖励/败北线/
|
||||
# 重复拒绝/GP 跨关延续/加算区同账/恢复数学/放弃)
|
||||
func _smoke_adventure() -> void:
|
||||
print("--- 冒险结构验收(三关序列 · 大奖励 · 摘要五件事 · 账本封闭)---")
|
||||
@@ -129,11 +129,11 @@ func _smoke_adventure() -> void:
|
||||
cov_miss += 1
|
||||
Kit.check("生成图每个战斗/Boss 节点经解析链命中敌行集与棋盘行(扫描 %d 节点,缺失 %d)〔fv24〕" % [cov_scan, cov_miss],
|
||||
cov_scan > 0 and cov_miss == 0)
|
||||
# ② 满配通关 + ④ 三层奖励兑付(三选一恰 2 次 · 回血封顶 · 钥石逐杀 · 候选零重复)
|
||||
# ② 满配冒险成功 + ④ 三层奖励兑付(三选一恰 2 次 · 回血封顶 · 钥石逐杀 · 候选零重复)
|
||||
var st: Dictionary = autoplay.run_adventure(_db, 42, 900, 400)
|
||||
var sum: Dictionary = st["summary"]
|
||||
Kit.check("满配通关(42 / atk+900 / hp+400——cleared=true;v2 怪阶梯后旧 200/100 档不再满配)", String(st["result"]) == "completed" and bool(sum.get("cleared", false)))
|
||||
Kit.check("通关几关 = 3(levels_cleared)", int(sum.get("levels_cleared", -1)) == 3)
|
||||
Kit.check("满配冒险成功(42 / atk+900 / hp+400——cleared=true;v2 怪阶梯后旧 200/100 档不再满配)", String(st["result"]) == "completed" and bool(sum.get("cleared", false)))
|
||||
Kit.check("完成几关 = 3(levels_cleared)", int(sum.get("levels_cleared", -1)) == 3)
|
||||
var ks_exp := 0
|
||||
for li in range(1, int(_db.level.rows.size()) + 1):
|
||||
ks_exp += int(_db.meta_boss_keystone_row(li).get("first_kill", 0))
|
||||
@@ -151,7 +151,7 @@ func _smoke_adventure() -> void:
|
||||
node_picks += 1 # 奖励节点三选一(每关奖励节点各 1 次)
|
||||
elif (ev as Dictionary).has("curio_resolved"):
|
||||
resolves.append((ev as Dictionary)["curio_resolved"])
|
||||
Kit.check("通关三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)", picks.size() == 2 and resolves.size() == 2 + node_picks)
|
||||
Kit.check("关卡完成三选一恰呈现 2 次、恰兑付 2 次(L1/L2 后——末关不发;resolves 含节点回执另计)", picks.size() == 2 and resolves.size() == 2 + node_picks)
|
||||
# 奖励节点在生成图里是可选路位(旧单链模板的「L1 必经 1 个」形状前提随模板消亡)——
|
||||
# 接线证明拆两半:图侧每关含 ≥1 奖励节点,行进侧种子扫描内确有经停(扫描零经停=三选一接线不可达)
|
||||
var reward_levels := 0
|
||||
@@ -242,7 +242,7 @@ func _smoke_adventure() -> void:
|
||||
if rk == "max_hp":
|
||||
hp_acc += int(r.get("value", max_flat))
|
||||
Kit.check("三选一候选 2–3 枚且零重复(令牌洗牌不放回——kind+tier 复合身份)", cand_ok)
|
||||
Kit.check("通关部分回血封顶(floor(观测上限×比例) 封顶——购血观测值精确断言,核代码增补)", heal_ok)
|
||||
Kit.check("关卡完成部分回血封顶(floor(观测上限×比例) 封顶——购血观测值精确断言,核代码增补)", heal_ok)
|
||||
Kit.check("钥石逐杀入账(首杀档位 + 累计逐位)", ks_gain_ok)
|
||||
Kit.check("点选恰兑付选中一件(值=令牌档位表值;未选项零生效)", resolve_ok)
|
||||
var seq: Array = st["encounter_max_hp_seq"]
|
||||
@@ -271,7 +271,7 @@ func _smoke_adventure() -> void:
|
||||
Kit.check("零配 L1 败北种子可达(seed=%s)" % str(lose.get("s", -1)), not lose.is_empty())
|
||||
if not lose.is_empty():
|
||||
var sl: Dictionary = lose["st"]["summary"]
|
||||
Kit.check("L1 败北摘要:未打穿 · 通关 0 关 · 钥石 0 · 止步第 1 层",
|
||||
Kit.check("L1 败北摘要:未打穿 · 完成 0 关 · 钥石 0 · 止步第 1 层",
|
||||
not bool(sl["cleared"]) and int(sl["levels_cleared"]) == 0 \
|
||||
and int(sl["keystone_total"]) == 0 and int(sl["stopped_at"]) == 1)
|
||||
var dl: Dictionary = sl.get("defeat", {})
|
||||
@@ -351,8 +351,8 @@ func _smoke_adventure() -> void:
|
||||
var acc_shop := int(a8.run_ledger.bonus.get("max_hp", -1)) # L1 三选一后 + 商店 3(首件随种子)
|
||||
Kit.check("统一冒险加算区含商店值(max_hp=%d ≥ 商店 3——L1 三选一件值另计)" % acc_shop, acc_shop >= 3)
|
||||
a8._on_level_finished({"result": "victory", "hp": int(r8.hero_hp), "node_index": r8.node_index,
|
||||
"node_id": r8.current_node_id(), "node_type": "shop"}) # 模拟 L2 通关(victory 载荷直调——非末关)
|
||||
Kit.check("L2 通关三选一停点在案(直调兑付前——零自动推进)", not a8._curio_pending.is_empty())
|
||||
"node_id": r8.current_node_id(), "node_type": "shop"}) # 模拟 L2 关卡完成(victory 载荷直调——非末关)
|
||||
Kit.check("L2 关卡完成三选一停点在案(直调兑付前——零自动推进)", not a8._curio_pending.is_empty())
|
||||
Kit.check("未兑付推进拒绝(选择停点零自动推进契约)", not a8.continue_to_next_level())
|
||||
var l2_cands: Array = a8._curio_pending["candidates"]
|
||||
var l2_pick: Dictionary = l2_cands[0]
|
||||
@@ -361,7 +361,7 @@ func _smoke_adventure() -> void:
|
||||
var expect_acc: int = acc_shop + (int(a8._curio_result.get("value", 0)) if picked2 == "max_hp" else 0)
|
||||
Kit.check("三选一属性件与商店购买同账累加(max_hp=%d = %d + 三选一%s)" % [int(a8.run_ledger.bonus.get("max_hp", -1)), acc_shop, picked2], int(a8.run_ledger.bonus.get("max_hp", -1)) == expect_acc)
|
||||
var max_eff8: int = a8.effective_max_hp() # 账本生效上限含调试浮窗 pct(a8 已 set 400)
|
||||
Kit.check("通关部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" % [int(a8.ledger["hero_hp"]), max_eff8], int(a8.ledger["hero_hp"]) <= max_eff8)
|
||||
Kit.check("关卡完成部分回血封顶(hp %d ≤ 生效上限 %d——血回满语义退役)" % [int(a8.ledger["hero_hp"]), max_eff8], int(a8.ledger["hero_hp"]) <= max_eff8)
|
||||
Kit.check("兑付后推进受理(下一关开始)", a8.continue_to_next_level())
|
||||
# ⑨ 恢复点数学(接口③封顶公式三读数 + 同式两落点一致)
|
||||
var a9 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(99))
|
||||
@@ -406,7 +406,7 @@ func _smoke_adventure() -> void:
|
||||
treasure_seen = true
|
||||
break
|
||||
Kit.check("池收缩——40 次重抽零 aux 候选(停点渠道 aux 卡退役,迭代 11 第①批 T7)", not treasure_seen)
|
||||
# 满血回 0 合法(通关回血封顶口径)
|
||||
# 满血回 0 合法(关卡完成回血封顶口径)
|
||||
var a12 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(12))
|
||||
a12.start_adventure(12)
|
||||
var cap := {"payload": {}}
|
||||
@@ -414,7 +414,7 @@ func _smoke_adventure() -> void:
|
||||
var max_full := int(_db.hero.max_hp) + int(a12._base_override.get("max_hp", 0))
|
||||
a12.ledger["hero_hp"] = max_full
|
||||
a12._on_level_finished({"result": "victory", "hp": max_full, "node_index": 1, "node_id": 1, "node_type": "shop"})
|
||||
Kit.check("满血通关回血 0 合法(封顶口径——heal_gained=0 非负溢出)", int(cap["payload"].get("heal_gained", -1)) == 0)
|
||||
Kit.check("满血关卡完成回血 0 合法(封顶口径——heal_gained=0 非负溢出)", int(cap["payload"].get("heal_gained", -1)) == 0)
|
||||
# ⑬ 奖励节点共享池(同库同池同摘牌;TP0:attack 令牌耗尽后节点域同样零出现)
|
||||
var a13 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(13))
|
||||
Kit.check("节点共享池场景:冒险启动(seed 13)", a13.start_adventure(13, {"treasure_unlocked": {"wide_scout_band": true}}))
|
||||
@@ -509,7 +509,7 @@ func _smoke_feats() -> void:
|
||||
if String((t as Dictionary).get("kind", "")) == "relic":
|
||||
deck_relic += 1
|
||||
Kit.check("专长带入池:5 行 relic 每件 1 张(四级 rarity 直映射)——牌库 102+5=107", deck_relic == 5 and a_r._curio_deck.size() == 107)
|
||||
# 渠道白名单断言(渠道轴定位:通关渠道只出专长/节点渠道零专长)
|
||||
# 渠道白名单断言(渠道轴定位:关卡完成渠道只出专长/节点渠道零专长)
|
||||
var feat_seen: Dictionary = {}
|
||||
var node_relic_seen := false
|
||||
for i in 60:
|
||||
@@ -519,7 +519,7 @@ func _smoke_feats() -> void:
|
||||
for t in a_r._draw_curio_candidates("reward_node"):
|
||||
if String((t as Dictionary).get("kind", "")) == "relic":
|
||||
node_relic_seen = true
|
||||
Kit.check("通关渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)", feat_seen.size() >= 2)
|
||||
Kit.check("关卡完成渠道只出专长(60 次重抽 ≥2 种不同 relic——身份键 feat_id 同档可多件)", feat_seen.size() >= 2)
|
||||
Kit.check("节点渠道零专长(60 次重抽零 relic——专长在非 Boss 渠道只以专长凭证形态出现)", not node_relic_seen)
|
||||
# T5 专长凭证行为断言(A5 拿到即开 / A6 保底计数——契约④四口径;独立 runner 防污染 a_r 牌库)
|
||||
var a_t = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(91))
|
||||
|
||||
@@ -96,7 +96,7 @@ func _init() -> void:
|
||||
adr_e._curio_taken["attack"] = 1 # 白盒预置计数(断点附随件往返的判据面——计数不可由加算区反推)
|
||||
adr_e._on_level_finished({"result": "victory", "hp": 30, "node_index": 1, "node_id": 1, "node_type": "shop"})
|
||||
var cands_e: Array = (adr_e._curio_pending.get("candidates", []) as Array).duplicate()
|
||||
Kit.check_quiet(not cands_e.is_empty(), "P5 非末关通关三选一停点在案(直调 victory——L1 非末关)")
|
||||
Kit.check_quiet(not cands_e.is_empty(), "P5 非末关的关卡完成三选一停点在案(直调 victory——L1 非末关)")
|
||||
var sm_f = SaveManagerScript.new(_bp_path)
|
||||
Kit.check_quiet(sm_f.load_from_disk() and sm_f.has_breakpoint(), "P5 选择停点断点在档(选择停点可中断契约)")
|
||||
var bp_f: Dictionary = sm_f.breakpoint_snapshot()
|
||||
|
||||
@@ -23,9 +23,9 @@ func _init() -> void:
|
||||
Kit.check_quiet(adr.curio_stop_pending() == false, "无停点=在途谓词 false")
|
||||
Kit.check_quiet(adr.refresh_curio() == false and adr.refresh_curio_paid() == false, "无停点:免费/付费刷新皆拒绝零副作用")
|
||||
|
||||
# ---- ② 免费位优先序 X→Y(白盒造通关停点——smoke_all ⑫ 同构)----
|
||||
# ---- ② 免费位优先序 X→Y(白盒造关卡完成停点——smoke_all ⑫ 同构)----
|
||||
adr._curio_pending = {"candidates": _cands(), "payload": {}}
|
||||
Kit.check_quiet(adr.curio_stop_pending() == true, "通关停点在途=谓词 true")
|
||||
Kit.check_quiet(adr.curio_stop_pending() == true, "关卡完成停点在途=谓词 true")
|
||||
adr._attune_refresh_x = 1
|
||||
adr._attune_refresh_y = 2
|
||||
Kit.check_quiet(adr.refresh_curio() and adr._attune_refresh_x == 0 and adr._attune_refresh_y == 2, "X 优先消耗(x1y2→x0y2)")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tests/unit/core/level_shop_economy_test.gd — 关卡图与 GP 商店单元测试宿主
|
||||
# 断言面 = 七套(批 B 断言迁移自 smoke_all.gd,G8 施工组):
|
||||
# 关卡节点图·生成图推进(满配通关/恢复点公式/败北线/回放确定性/属性覆盖恢复)
|
||||
# 关卡节点图·生成图推进(满配冒险成功/恢复点公式/败北线/回放确定性/属性覆盖恢复)
|
||||
# 分支节点图(图校验正反/L2·L3 行进与行集定点/混编运行面/真图类型可见性)
|
||||
# GP 冒险钱包·到账(掷定窗口/关级加值)
|
||||
# escrow 隔离(格面兑付入缓冲/破财直验/钱包钳制不透支)
|
||||
@@ -323,15 +323,15 @@ func _smoke_generated_coverage() -> void:
|
||||
# ---- 关卡节点图·生成图推进冒烟(图 = 该关参数档 × 种子)----
|
||||
|
||||
func _smoke_level() -> void:
|
||||
print("--- 关卡节点图·生成图推进(参数档×种子产图·满配通关/恢复公式/败北线/回放确定性)---")
|
||||
print("--- 关卡节点图·生成图推进(参数档×种子产图·满配冒险成功/恢复公式/败北线/回放确定性)---")
|
||||
var autoplay = load("res://sim/level_autoplay.gd")
|
||||
var hero_base := {"atk": int(_db.hero.atk), "hp": int(_db.hero.max_hp), "ac": int(_db.hero.armor_class)} # 配置基线快照——⑤ 回基线对比用(改表自动跟随,不再硬编码)
|
||||
var nodes1: Array = _level_active_nodes(1)
|
||||
var params1: Dictionary = _db.level_gen_params_row(1)
|
||||
Kit.check("L1 现行起关图取数非空(种子 %d × 参数档 → 节点 %d 颗)" % [ACTIVE_GRAPH_SEED, nodes1.size()], not nodes1.is_empty())
|
||||
# ① 满配通关路径(atk+200%/hp+100%——测试面板同源的属性覆盖)
|
||||
# ① 满配冒险成功路径(atk+200%/hp+100%——测试面板同源的属性覆盖)
|
||||
var win = autoplay.run_level(_db, ACTIVE_GRAPH_SEED, 1, 200, 100)
|
||||
Kit.check("关卡·满配(atk+200/hp+100)通关(实测:%s @节点 %d)" % [win.result, win.defeat_node_index], win.result == "victory")
|
||||
Kit.check("关卡·满配(atk+200/hp+100)完成(实测:%s @节点 %d)" % [win.result, win.defeat_node_index], win.result == "victory")
|
||||
# 行进面读数与图读数自洽:路径逐跳为图出边,路径上战斗/Boss 计数 = battles 读数
|
||||
var by_id1 := _nodes_by_id(nodes1)
|
||||
var path_battles := 0
|
||||
@@ -339,10 +339,10 @@ func _smoke_level() -> void:
|
||||
var vkind := String(by_id1.get(int(vid), {}).get("type", ""))
|
||||
if vkind == "battle" or vkind == "boss":
|
||||
path_battles += 1
|
||||
Kit.check("L1 通关路径逐跳为图出边(入口起步·末节点无出边——句柄轴行进自洽)", _path_in_graph(nodes1, win.visited_ids))
|
||||
Kit.check("L1 通关路径战斗/Boss 节点数 = 行进 battles 读数(路径 %d 节点·战斗 %d 颗·battles %d)" % [win.visited_ids.size(), path_battles, int(win.battles)],
|
||||
Kit.check("L1 关卡完成路径逐跳为图出边(入口起步·末节点无出边——句柄轴行进自洽)", _path_in_graph(nodes1, win.visited_ids))
|
||||
Kit.check("L1 关卡完成路径战斗/Boss 节点数 = 行进 battles 读数(路径 %d 节点·战斗 %d 颗·battles %d)" % [win.visited_ids.size(), path_battles, int(win.battles)],
|
||||
path_battles == int(win.battles) and path_battles >= 2)
|
||||
# 终点恒落最长路径步数:Boss 深度 = 参数档层高,且通关路径末节点即该终点
|
||||
# 终点恒落最长路径步数:Boss 深度 = 参数档层高,且关卡完成路径末节点即该终点
|
||||
var boss_depth1 := -1
|
||||
for n in nodes1:
|
||||
if String(n.get("type", "")) == "boss":
|
||||
@@ -350,7 +350,7 @@ func _smoke_level() -> void:
|
||||
var last_id := -1
|
||||
if win.visited_ids.size() > 0:
|
||||
last_id = int(win.visited_ids[win.visited_ids.size() - 1])
|
||||
Kit.check("L1 终点 Boss 深度 = 参数档层高 %d 且通关路径末节点落该深度(实测终点深度 %d·末句柄 %d)" % [int(params1.get("layers", -1)), boss_depth1, last_id],
|
||||
Kit.check("L1 终点 Boss 深度 = 参数档层高 %d 且关卡完成路径末节点落该深度(实测终点深度 %d·末句柄 %d)" % [int(params1.get("layers", -1)), boss_depth1, last_id],
|
||||
boss_depth1 == int(params1.get("layers", -1)) and int(by_id1.get(last_id, {}).get("depth", -1)) == boss_depth1)
|
||||
Kit.check("L1 终点恰一个(大岔口自 L2 起的图侧投影)", _terminal_ids(nodes1).size() == 1 and _ids_of_type(nodes1, "boss").size() == 1)
|
||||
Kit.check("L1 生成图含岔口(出度 ≥2 节点 %d 处)" % _fork_ids(nodes1).size(), _fork_ids(nodes1).size() >= 1)
|
||||
@@ -371,7 +371,7 @@ func _smoke_level() -> void:
|
||||
else:
|
||||
Kit.check("恢复点公式·缺口不足档:回复封顶到缺口 2(实测 %d,比例项 %d)" % [int(rest_narrow["healed"]), expect_pct],
|
||||
int(rest_narrow["healed"]) == mini(expect_pct, 2) and expect_pct > 2)
|
||||
# ③ 裸装败北路径(atk1/hp10——Roguelike 基线应难以通关)
|
||||
# ③ 裸装败北路径(atk1/hp10——Roguelike 基线应难以完成)
|
||||
var lose = autoplay.run_level(_db, ACTIVE_GRAPH_SEED, 1, -80, -80)
|
||||
Kit.check("关卡·裸装(atk≈1/hp≈10)败北路径可达(实测:%s)" % lose.result, lose.result == "defeat")
|
||||
Kit.check("败北节点记录在域内(节点 %d,图节点数 %d)" % [lose.defeat_node_index, nodes1.size()],
|
||||
@@ -488,7 +488,7 @@ func _smoke_level_graph() -> void:
|
||||
Kit.check("图校验拒绝:非 Boss 终点(终点恒为 Boss)〔v1〕", _has_err_category(LT.validate_graph(orphan), "structural"))
|
||||
# ④ L2 选路推进:min / max 双路径全通(确定性路径轴——同种子同图,方向轴只换候选取值)
|
||||
var win_min: Dictionary = autoplay.run_level(_db, ACTIVE_GRAPH_SEED, 2, 200, 100, false, null, "normal", "min")
|
||||
Kit.check("L2·满配 min 路径通关(实测:%s)" % win_min.result, win_min.result == "victory")
|
||||
Kit.check("L2·满配 min 路径完成(实测:%s)" % win_min.result, win_min.result == "victory")
|
||||
var n2_last := -1
|
||||
if win_min.visited_ids.size() > 0:
|
||||
n2_last = int(win_min.visited_ids[win_min.visited_ids.size() - 1])
|
||||
@@ -501,7 +501,7 @@ func _smoke_level_graph() -> void:
|
||||
_path_shop_count(nodes2, win_min.visited_ids) == 1)
|
||||
Kit.check("选路口发生 %d 次(每节点兑付后——零自动推进)" % win_min.choices, win_min.choices >= 3)
|
||||
var win_max: Dictionary = autoplay.run_level(_db, ACTIVE_GRAPH_SEED, 2, 200, 100, false, null, "normal", "max")
|
||||
Kit.check("L2·满配 max 路径(候选取最大句柄)通关(实测:%s·路径 %s)" % [win_max.result, str(win_max.visited_ids)], win_max.result == "victory")
|
||||
Kit.check("L2·满配 max 路径(候选取最大句柄)完成(实测:%s·路径 %s)" % [win_max.result, str(win_max.visited_ids)], win_max.result == "victory")
|
||||
var n2_last_max := -1
|
||||
if win_max.visited_ids.size() > 0:
|
||||
n2_last_max = int(win_max.visited_ids[win_max.visited_ids.size() - 1])
|
||||
@@ -973,7 +973,7 @@ func _smoke_shop_room() -> void:
|
||||
var shop_run := {}
|
||||
for s in range(42, 92):
|
||||
var r0 = load("res://core/level_runner.gd").new(_db, _fresh_rng(s))
|
||||
r0.set_hero_override(900, 400) # 行进段快速通关档(购买断言与百分比轴正交——加算区单独断言)
|
||||
r0.set_hero_override(900, 400) # 行进段快速完成档(购买断言与百分比轴正交——加算区单独断言)
|
||||
r0.start_level(2, s)
|
||||
var st := _battle_st("normal")
|
||||
var guard := 60
|
||||
|
||||
@@ -159,7 +159,7 @@ func _meta_settle_and_workshop() -> void:
|
||||
var row1: Dictionary = _db.meta_boss_keystone_row(1)
|
||||
var row2: Dictionary = _db.meta_boss_keystone_row(2)
|
||||
# ⑥ 逐杀掉落收束入账(掉落已在冒险内逐杀入账〔summary.keystone_total 含全部〕,
|
||||
# keystone_drops 明细随摘要归档;首杀标记仍由 apply 逐通关关次置位)
|
||||
# keystone_drops 明细随摘要归档;首杀标记仍由 apply 逐关卡完成关次置位)
|
||||
var d1: Dictionary = mp.settle_adventure({"result": "failed", "cleared": false, "levels_cleared": 1,
|
||||
"stopped_at": 2, "keystone_total": int(row1.get("first_kill", 0)),
|
||||
"keystone_drops": [{"level": 1, "first": true, "amount": int(row1.get("first_kill", 0))}],
|
||||
@@ -186,21 +186,21 @@ func _meta_settle_and_workshop() -> void:
|
||||
"master_seed": 13})
|
||||
Kit.check("重复杀第三轮恒定不递减(验收5——无衰减曲线)",
|
||||
not bool(d3["boss_drops"][0].get("first", true)) and int(d3["boss_drops"][0].get("amount", 0)) == int(row1.get("repeat_kill", -1)))
|
||||
# 混合归因:通关两关 → L1 重复 + L2 首杀(明细随摘要逐笔归档)
|
||||
# 混合归因:完成两关 → L1 重复 + L2 首杀(明细随摘要逐笔归档)
|
||||
var d4: Dictionary = mp.settle_adventure({"result": "failed", "cleared": false, "levels_cleared": 2,
|
||||
"stopped_at": 0, "keystone_total": int(row1.get("repeat_kill", 0)) + int(row2.get("first_kill", 0)),
|
||||
"keystone_drops": [{"level": 1, "first": false, "amount": int(row1.get("repeat_kill", 0))},
|
||||
{"level": 2, "first": true, "amount": int(row2.get("first_kill", 0))}],
|
||||
"master_seed": 14})
|
||||
Kit.check("两关通关逐关归因:L1 重复档 + L2 首杀档(first_kill 金额)",
|
||||
Kit.check("两关完成逐关归因:L1 重复档 + L2 首杀档(first_kill 金额)",
|
||||
not bool(d4["boss_drops"][0].get("first", true)) and bool(d4["boss_drops"][1].get("first", false))
|
||||
and int(d4["boss_drops"][1].get("amount", 0)) == int(row2.get("first_kill", -1)))
|
||||
# ⑦ 败北零通关摘要:余额只增不减(零入账)、clear_counts 不变
|
||||
# ⑦ 败北零关卡完成摘要:余额只增不减(零入账)、clear_counts 不变
|
||||
var bal_before: int = sv.keystone_balance()
|
||||
var cc1_before: int = sv.clear_count(1)
|
||||
var dz: Dictionary = mp.settle_adventure({"result": "failed", "cleared": false, "levels_cleared": 0,
|
||||
"stopped_at": 1, "keystone_total": 0, "master_seed": 15})
|
||||
Kit.check("败北零通关摘要:零入账零标记(钥石只增不减——验收1;败北局 clear_counts 不变)",
|
||||
Kit.check("败北零关卡完成摘要:零入账零标记(钥石只增不减——验收1;败北局 clear_counts 不变)",
|
||||
dz["boss_drops"].is_empty() and sv.keystone_balance() == bal_before and sv.clear_count(1) == cc1_before)
|
||||
# ⑧ 秘宝解锁服务(V3 钥石购买/V4 条件解锁;注入=直调解锁写口+直置存档生涯计数,
|
||||
# 不走正常资源积累流程——卡片 7.1 测试方式)
|
||||
@@ -217,10 +217,10 @@ func _meta_settle_and_workshop() -> void:
|
||||
and sv_u.keystone_balance() == 0)
|
||||
Kit.check("已解锁重复购买拒绝〔秘宝批 V3〕",
|
||||
not mp_u.buy_treasure_unlock("morning_order"))
|
||||
Kit.check("条件未达不解锁(远程镐=通关 1 次)〔秘宝批 V4〕",
|
||||
Kit.check("条件未达不解锁(远程镐=冒险成功 1 次)〔秘宝批 V4〕",
|
||||
mp_u.refresh_treasure_unlocks().is_empty() and not sv_u.item_unlocked("ranged_pick"))
|
||||
sv_u._data["meta"]["adventure_cleared"] = 1 # 直置存档生涯计数(调试注入口径)
|
||||
Kit.check("远程镐=adventure_cleared≥1 达标恰解锁一次+重复刷新幂等(C6:通关=打通整局冒险)〔秘宝批 V4〕",
|
||||
Kit.check("远程镐=adventure_cleared≥1 达标恰解锁一次+重复刷新幂等(C6:冒险成功=打通整局冒险)〔秘宝批 V4〕",
|
||||
(mp_u.refresh_treasure_unlocks() as Array).has("ranged_pick")
|
||||
and sv_u.item_unlocked("ranged_pick") and mp_u.refresh_treasure_unlocks().is_empty())
|
||||
sv_u._data["meta"]["boss_kill_count"] = 3
|
||||
@@ -230,7 +230,7 @@ func _meta_settle_and_workshop() -> void:
|
||||
Kit.check("天眼单条件不足不解锁(AND 语义——cleared 3 达 boss 4 未达)〔秘宝批 V4〕",
|
||||
not sv_u.item_unlocked("sky_eye"))
|
||||
sv_u._data["meta"]["boss_kill_count"] = 4
|
||||
Kit.check("天眼双条件 AND(通关≥3 且 Boss≥4)达标解锁〔秘宝批 V4〕",
|
||||
Kit.check("天眼双条件 AND(冒险成功≥3 且 Boss≥4)达标解锁〔秘宝批 V4〕",
|
||||
(mp_u.refresh_treasure_unlocks() as Array).has("sky_eye"))
|
||||
sv_u._data["meta"]["vision_used"] = 10
|
||||
Kit.check("信标网络=真视类工具累计使用≥10(行为计数新域——use_treasure 受理成功累计)〔秘宝批 V4〕",
|
||||
@@ -267,7 +267,7 @@ func _meta_settle_and_workshop() -> void:
|
||||
and int(mp_a.attune_camp_status()["refresh"]["charges"]) == 1)
|
||||
mp_a.settle_adventure({"result": "failed", "cleared": false, "levels_cleared": 1,
|
||||
"stopped_at": 2, "keystone_total": 0, "master_seed": 21})
|
||||
Kit.check("通关 1 关 → Slots 恒 0(旧「按击杀 Boss 次数自动涨」条件已删——槽位唯一来源=树点亮)且 Boss 击杀总数=1(计数域保留)〔天赋树批 V14〕",
|
||||
Kit.check("完成 1 关 → Slots 恒 0(旧「按击杀 Boss 次数自动涨」条件已删——槽位唯一来源=树点亮)且 Boss 击杀总数=1(计数域保留)〔天赋树批 V14〕",
|
||||
Attunement.slots(_db, sv_a) == 0 and sv_a.boss_kill_count() == 1)
|
||||
sv_a.set_tree_level("att_socket1", 1)
|
||||
Kit.check("树上点亮同调位 1 → Slots=1(槽位收编树节点——Q2 裁甲)",
|
||||
|
||||
@@ -27,7 +27,7 @@ func _test_no_arg_instance_has_no_activation() -> void:
|
||||
"sv2 无参实例无激活态(路径空/激活号 0/写盘目标空)〔卡① sv2〕")
|
||||
Kit.check_quiet(sm.keystone_balance() == 0 and not sm.has_breakpoint()
|
||||
and sm.adventure_total() == 0 and sm.adventure_cleared() == 0,
|
||||
"sv2 内存=默认合法空态(钥石/断点/总局数/通关数全零值——访问器 fail-safe)〔卡① sv2〕")
|
||||
"sv2 内存=默认合法空态(钥石/断点/总局数/冒险成功数全零值——访问器 fail-safe)〔卡① sv2〕")
|
||||
# attach_path 注入任意隔离路径=注入即激活(契约 7 单路径语义——隔离面行为零变化)
|
||||
sm.attach_path("user://smoke_boot_noact_iso.json")
|
||||
Kit.check_quiet(sm.has_write_target() and sm.keystone_balance() == 0,
|
||||
|
||||
@@ -78,11 +78,11 @@ func _smoke_save() -> void:
|
||||
w.unlock_item("probe_compass")
|
||||
w.apply_adventure_summary({"result": "failed", "cleared": false, "levels_cleared": 2,
|
||||
"stopped_at": 3, "keystone_total": 3, "master_seed": 99})
|
||||
Kit.check("apply 原语入账(钥石 7+3、通关次数 1/1/0)",
|
||||
Kit.check("apply 原语入账(钥石 7+3、关卡完成次数 1/1/0)",
|
||||
w.keystone_balance() == 10 and w.clear_count(1) == 1 and w.clear_count(2) == 1 and w.clear_count(3) == 0)
|
||||
Kit.check("首杀标记随通关关卡置位(1/2 有、3 无)",
|
||||
Kit.check("首杀标记随关卡完成置位(1/2 有、3 无)",
|
||||
w.boss_first_kill_marked(1) and w.boss_first_kill_marked(2) and not w.boss_first_kill_marked(3))
|
||||
Kit.check("生涯终身计数随 apply 自增恰一(中止局:总局数 1、通关局数 0——迭代 18 卡④)",
|
||||
Kit.check("生涯终身计数随 apply 自增恰一(中止局:总局数 1、冒险成功局数 0——迭代 18 卡④)",
|
||||
w.adventure_total() == 1 and w.adventure_cleared() == 0)
|
||||
Kit.check("写盘成功", w.write_to_disk())
|
||||
var r = sm_script.new(paths[0])
|
||||
@@ -95,7 +95,7 @@ func _smoke_save() -> void:
|
||||
and r.forge_unlocked_purchases() == ["spear"]
|
||||
and r.forge_weapon() == "spear" and r.forge_armor() == "ward")
|
||||
Kit.check("道具解锁往返相等", r.item_unlocked("probe_compass") and not r.item_unlocked("rope"))
|
||||
Kit.check("通关次数表往返相等", r.clear_count(1) == 1 and r.clear_count(2) == 1 and r.clear_count(3) == 0)
|
||||
Kit.check("关卡完成次数表往返相等", r.clear_count(1) == 1 and r.clear_count(2) == 1 and r.clear_count(3) == 0)
|
||||
Kit.check("首杀标记往返相等",
|
||||
r.boss_first_kill_marked(1) and r.boss_first_kill_marked(2) and not r.boss_first_kill_marked(3))
|
||||
Kit.check("生涯终身计数往返相等(1/0)", r.adventure_total() == 1 and r.adventure_cleared() == 0)
|
||||
@@ -265,11 +265,11 @@ func _smoke_save() -> void:
|
||||
f13b.load_from_disk()
|
||||
f13b.apply_adventure_summary({"result": "completed", "cleared": true, "levels_cleared": 3,
|
||||
"stopped_at": 0, "keystone_total": 0, "master_seed": 1})
|
||||
Kit.check("收束自增恰一·通关局(三关全通 cleared=true:总局数+1 且通关局数+1)",
|
||||
Kit.check("收束自增恰一·冒险成功局(三关全通 cleared=true:总局数+1 且冒险成功局数+1)",
|
||||
f13b.adventure_total() == 1 and f13b.adventure_cleared() == 1)
|
||||
f13b.apply_adventure_summary({"result": "abandoned", "cleared": false, "levels_cleared": 1,
|
||||
"stopped_at": 2, "keystone_total": 0, "master_seed": 2})
|
||||
Kit.check("收束自增恰一·中止局(放弃局:仅总局数+1,通关局数不动)",
|
||||
Kit.check("收束自增恰一·中止局(放弃局:仅总局数+1,冒险成功局数不动)",
|
||||
f13b.adventure_total() == 2 and f13b.adventure_cleared() == 1)
|
||||
|
||||
# ⑮ 旧档破坏性废弃(契约 4:换代前结构检测=树域废弃重置,禁迁移禁折算)〔V16〕
|
||||
@@ -321,11 +321,11 @@ func _smoke_save() -> void:
|
||||
var mp13 = load("res://core/meta_progression.gd").new(_db, f13b)
|
||||
mp13.settle_adventure({"result": "failed", "cleared": false, "levels_cleared": 2,
|
||||
"stopped_at": 3, "keystone_total": 0, "master_seed": 3})
|
||||
Kit.check("收束链自增·中止局(settle_adventure 真实挂点:总局数+1、通关局数不动)",
|
||||
Kit.check("收束链自增·中止局(settle_adventure 真实挂点:总局数+1、冒险成功局数不动)",
|
||||
f13b.adventure_total() == 3 and f13b.adventure_cleared() == 1)
|
||||
mp13.settle_adventure({"result": "completed", "cleared": true, "levels_cleared": 3,
|
||||
"stopped_at": 0, "keystone_total": 0, "master_seed": 4})
|
||||
Kit.check("收束链自增·通关局(三关全通:总局数+1 且通关局数+1——全链恰一)",
|
||||
Kit.check("收束链自增·冒险成功局(三关全通:总局数+1 且冒险成功局数+1——全链恰一)",
|
||||
f13b.adventure_total() == 4 and f13b.adventure_cleared() == 2)
|
||||
|
||||
# 段尾清理(测试文件绝不残留)
|
||||
|
||||
@@ -46,7 +46,7 @@ func _test_peek_three_slots_distinct_values() -> void:
|
||||
var ps: Dictionary = t.peek_slot_summary(i)
|
||||
Kit.check_quiet(bool(ps.get("readable", false)) and int(ps.get("keystone", -1)) == 200 * i
|
||||
and int(ps.get("cleared", -1)) == i and int(ps.get("total", -1)) == 5 * i,
|
||||
"sv7 窥视三项逐槽正确·槽 %d(钥石/通关/总局)〔卡① sv7〕" % i)
|
||||
"sv7 窥视三项逐槽正确·槽 %d(钥石/冒险成功/总局)〔卡① sv7〕" % i)
|
||||
Kit.check_quiet(t.active_slot() == 0 and not t.has_write_target() and t.keystone_balance() == 0,
|
||||
"sv7 窥视不激活不写内存(激活号 0/无写盘目标/内存余额零)〔卡① sv7〕")
|
||||
# 路径注入口同语义(契约 7 注入面)
|
||||
|
||||
@@ -93,7 +93,7 @@ func _smoke_adventure_table() -> void:
|
||||
if _db.adventure == null:
|
||||
return
|
||||
Kit.check("AdventureTable.validate() 零违例(全键域检+档位配额结构锚在案)", _db.adventure.validate().is_empty())
|
||||
Kit.check("clear_heal_pct ∈ [0,100](%d——通关部分回血比例)" % int(_db.adventure.clear_heal_pct),
|
||||
Kit.check("clear_heal_pct ∈ [0,100](%d——关卡完成部分回血比例)" % int(_db.adventure.clear_heal_pct),
|
||||
int(_db.adventure.clear_heal_pct) >= 0 and int(_db.adventure.clear_heal_pct) <= 100)
|
||||
for tier in _db.adventure.TIERS:
|
||||
Kit.check("档位配额结构锚(四级 70/25/7/0——迭代 31 卡② 净删两件投放):%s = %d 张" % [tier, int(_db.adventure.TIER_QUOTAS[tier])],
|
||||
@@ -464,7 +464,7 @@ func _smoke_nine_types_gp_stream() -> void:
|
||||
gp_pos = int(row.weight)
|
||||
if String(row.get("reward_id", "")) == "gp_lose":
|
||||
gp_neg = int(row.weight)
|
||||
Kit.check("奖励字典 18 行(九型×正负 18 格——aux 挖格投放四行已退役 2026-10-01 用户拍板甲:战斗/挖格不掉道具,秘宝唯一来路=三选一共享池〔节点/通关〕;清图槽独立批尾不计)",
|
||||
Kit.check("奖励字典 18 行(九型×正负 18 格——aux 挖格投放四行已退役 2026-10-01 用户拍板甲:战斗/挖格不掉道具,秘宝唯一来路=三选一共享池〔节点/关卡完成〕;清图槽独立批尾不计)",
|
||||
cell_rows == 18)
|
||||
# 危险格负向直伤当量(负向≈正向 2 倍定价——真人反馈「三雷 3/4 血无后果感」的投值口径)
|
||||
var mine_power := {"heal_backlash": 6, "pierce_snare": 8, "magic_recoil": 8} # 危险格负向直伤当量(抬档值;依据见 numfix 报告)
|
||||
|
||||
@@ -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")
|
||||
|
||||
+1656
-1653
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user