feat(flow): 停点前移批——LevelRunner 入口停点模式(opt-in defer+零RNG漂移)+关卡开局即出图选路+A 区进度条退役(转 C 区钱包行,底行 548)+GDD 四文件对齐三区+art-bible 连带+06 条目解决移除(用户拍板 2026-09-23,台账 142)
This commit is contained in:
1 parent
c22174274c
commit
8eddcdd5a7
19 files changed
+190
-208
No files matched your search
@@ -24,6 +24,12 @@
|
||||
# continue_to_next_level 开始下一关,两步间为呈现层大奖励屏(冒烟/自动路径立即确认)。
|
||||
# 结算受理只算一次(契约):state != "running" 的关卡结束宣布与放弃请求一律拒绝并留诊断,
|
||||
# 账本与摘要零变化。
|
||||
# 停点前移批(2026-09-23,用户拍板「关卡开始即出节点图选路、选节点才进战斗」):每关开始 =
|
||||
# 入口选路停点——_start_current_level 以 defer_entry 起关,level_started 之后才 present_entry_choice
|
||||
# (信号顺序契约:呈现层先重取 runner 实例,选路口才不会被空防御丢弃);pick 入口候选走同一
|
||||
# _enter_node(entry)、呈现零 RNG 消耗——与旧「start 直进首战」逐位等价,测量面零漂移。
|
||||
# 关卡切换(continue_to_next_level)后新 LevelRunner 实例重新走 defer 流程;abandon 在入口停点
|
||||
# 天然非战斗期(current_battle 为空)——受理语义与选路停点一致。
|
||||
# 纪律:ADR-0001 S6——连 runner 信号一律 _forward_* 方法 Callable(禁 lambda 捕获 self 成环);
|
||||
# Core 层禁 UI 引用;数值零字面量——level_clear_hp_bonus / keystone_per_level 只读 db.adventure,
|
||||
# 基础上限只读 db.hero。
|
||||
@@ -153,11 +159,15 @@ func _start_current_level() -> bool:
|
||||
_runner.level_finished.connect(_on_level_finished)
|
||||
_runner.set_hero_override(int(_override[0]), int(_override[1]))
|
||||
# 接口①开始关卡:生命经共享账本交接(账本当前值即开局生命——冒险开始置满 / 大奖励回满已写好);
|
||||
# _base_override 共享引用随载荷传入(永久强化 flat——LevelRunner.effective_* 叠加序第二项)
|
||||
# _base_override 共享引用随载荷传入(永久强化 flat——LevelRunner.effective_* 叠加序第二项)。
|
||||
# 停点前移批 2026-09-23:defer_entry=true 起每关(含首关)——先 emit level_started(呈现层在
|
||||
# 该回调重取 runner 实例),后 present_entry_choice()(入口候选呈现;顺序反了选路口会被呈现层
|
||||
# 的 runner 空防御静默丢弃)。呈现零 RNG 消耗、pick 走同一 _enter_node——测量面逐位零漂移。
|
||||
var ok: bool = _runner.start_level_external(level_index, ledger, bonus, purchased,
|
||||
Callable(self, "apply_rest"), _base_override)
|
||||
Callable(self, "apply_rest"), _base_override, true)
|
||||
if ok:
|
||||
level_started.emit(level_index, String(_runner.level_row.get("display_name", "")))
|
||||
_runner.present_entry_choice()
|
||||
return ok
|
||||
|
||||
|
||||
|
||||
@@ -158,8 +158,15 @@ func start_level(level_index: int, seed_val: int, base_override_in: Dictionary =
|
||||
# base_override = 永久强化 flat 共享引用(元进度实现批——缺省 {} 全零白板,与 B1 空参数路径逐位一致)。
|
||||
# 流程与 start_level 同款(_find_level_row → 状态初始化 → _graph_mode 探测 → _enter_node(entry)),
|
||||
# 差异仅在账本/加算区/已购档/恢复执行体的注入与零重置。
|
||||
# 停点前移批 2026-09-23:defer_entry 尾参(缺省 false = 既有调用方零变更)——true 时不自动
|
||||
# _enter_node(entry),改置选路态(awaiting_choice + 单候选 = 入口节点),由 AdventureRunner 在
|
||||
# level_started 之后调 present_entry_choice() 呈现、玩家 pick_next_node 显式进首战。拆两步的
|
||||
# 信号顺序契约:呈现层须先经 level_started 重取 runner 实例,choices_presented 才不会被
|
||||
# _on_choices_presented 的 runner 空防御静默丢弃。入口呈现零 RNG 消耗、pick 走同一个
|
||||
# _enter_node(entry)——与旧自动进入逐位等价(测量面零漂移红线)。
|
||||
func start_level_external(level_index: int, ledger: Dictionary, bonus_ref: Dictionary,
|
||||
purchased_ref: Dictionary, rest_handler: Callable, base_override_ref: Dictionary = {}) -> bool:
|
||||
purchased_ref: Dictionary, rest_handler: Callable, base_override_ref: Dictionary = {},
|
||||
defer_entry: bool = false) -> bool:
|
||||
var row := _find_level_row(level_index)
|
||||
if row.is_empty():
|
||||
push_error("[关卡节点地图] level_index %d 在 LevelTable 无行" % level_index)
|
||||
@@ -187,9 +194,23 @@ func start_level_external(level_index: int, ledger: Dictionary, bonus_ref: Dicti
|
||||
push_error("[关卡节点地图] 入口节点缺失(无入边唯一节点——图合法性校验应已拦截)")
|
||||
_running = false
|
||||
return false
|
||||
if defer_entry:
|
||||
_awaiting_choice = true # 入口停点(node_index 保持 -1——进节点前无 current_node)
|
||||
_candidates = [entry]
|
||||
return true
|
||||
return _enter_node(entry)
|
||||
|
||||
|
||||
# 入口停点呈现(停点前移批 2026-09-23——defer_entry=true 的第二步):发出入口候选供呈现层弹
|
||||
# 选路层;非停点态(缺省自动进入路径)拒绝。零 RNG 消耗——呈现与行进取号解耦。
|
||||
func present_entry_choice() -> bool:
|
||||
if not _running or not _awaiting_choice or _candidates.is_empty() or node_index != -1:
|
||||
push_error("[关卡节点地图] present_entry_choice 拒绝——非入口停点态(defer_entry 未开或已 pick)")
|
||||
return false
|
||||
choices_presented.emit(_candidates)
|
||||
return true
|
||||
|
||||
|
||||
func _find_level_row(level_index: int) -> Dictionary:
|
||||
for row in db.level.rows:
|
||||
if int(row.level_index) == level_index:
|
||||
|
||||
@@ -48,17 +48,17 @@ var route_overlay: RouteOverlay # 节点图选路覆盖层(分支节点图
|
||||
# 调试模式编排(单场/连续两层——状态机与战斗生命周期归 debug_session.gd,本类只接线与出 UI)
|
||||
var _debug = null
|
||||
|
||||
# 视图(views/)——battle.md 四区(E 区道具面板随可选类删除下线)
|
||||
var strip_view: LevelStripView # A 区
|
||||
# 视图(views/)——battle.md 三区(A 区关卡进度条顶栏随 停点前移批 2026-09-23 退役——节点链进度
|
||||
# 由关卡画面本体选路覆盖层承载;E 区道具面板随可选类删除下线)
|
||||
var stage: BattleStageView # B 区(含 log_view 明细带)
|
||||
var log_view: BattleLogView
|
||||
var info_panel: HeroInfoPanelView # C 区
|
||||
var info_panel: HeroInfoPanelView # C 区(含钱包行——原 A 区顶栏 GP/钥石余额常驻位)
|
||||
var board_zone: BoardZoneView # D 区(含 ap_bar/board_view/toast/fx)
|
||||
var board_view: BoardView # 探针兼容别名(= board_zone.board_view)
|
||||
var ap_bar: ApBarView # 探针兼容别名(= board_zone.ap_bar)
|
||||
var settlement_fx: SettlementFxView # 结算演出覆盖层(跨区弹道/光带——A–D 之上、模态之下)
|
||||
var gp_label: Label # A 区顶栏 GP 余额常驻位(GP+商店房实施批——文案装配归 HudPresenter)
|
||||
var keystone_label: Label # A 区顶栏钥石余额常驻位(元进度实现批——文案装配归 HudPresenter)
|
||||
var gp_label: Label # GP 余额常驻位(C 区钱包行——文案装配归 HudPresenter)
|
||||
var keystone_label: Label # 钥石余额常驻位(C 区钱包行——文案装配归 HudPresenter)
|
||||
# 模态 overlay(z 序 = 登记序;pattern 6 层级纪律)
|
||||
var legend_overlay: LegendOverlay
|
||||
var review_overlay: SettlementReviewOverlay
|
||||
@@ -102,7 +102,6 @@ func _build_ui() -> void:
|
||||
var root := VBoxContainer.new()
|
||||
add_child(root)
|
||||
var battle_ui: Dictionary = UiAssembler.build_battle_ui(root, db)
|
||||
strip_view = battle_ui.strip_view
|
||||
gp_label = battle_ui.gp_label
|
||||
keystone_label = battle_ui.keystone_label
|
||||
stage = battle_ui.stage
|
||||
@@ -248,10 +247,10 @@ func _start_adventure() -> void:
|
||||
return
|
||||
|
||||
|
||||
# 每关开始(冒险模式):重取 runner 实例 + 图条刷新 + 层名行(L1 与单关模式同序——层名行随首战横幅后到达)
|
||||
# 每关开始(冒险模式):重取 runner 实例 + 层名行(L1 与单关模式同序——层名行随首战横幅后到达;
|
||||
# 停点前移批 2026-09-23:本回调返回后才有 choices_presented 到达——入口选路层随即弹出)
|
||||
func _on_adventure_level_started(level_index: int, display_name: String) -> void:
|
||||
runner = adventure.runner()
|
||||
strip_view.refresh(runner)
|
||||
log_view.append_bb(Narrator.level_open_line(display_name, int(adventure.master_seed),
|
||||
int(db.hero.atk), int(db.hero.max_hp),
|
||||
tune_panel.atk_pct() > 0 or tune_panel.hp_pct() > 0))
|
||||
@@ -291,7 +290,6 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void:
|
||||
shop_overlay.open_shop(runner.shop_products(), int(runner.gp))
|
||||
log_view.append_bb(Narrator.shop_line(int(runner.gp)))
|
||||
logger.log_node(node, info, runner.node_index)
|
||||
strip_view.refresh(runner)
|
||||
_refresh_presentations()
|
||||
|
||||
|
||||
@@ -329,7 +327,6 @@ func _on_adventure_finished(summary: Dictionary) -> void:
|
||||
logger.log_level_end(summary) # 试玩时间线:冒险终局(摘要随载荷)
|
||||
if runner != null:
|
||||
runner.restore_hero_base()
|
||||
strip_view.refresh(runner)
|
||||
_open_modal(_end_overlay)
|
||||
_refresh_presentations()
|
||||
|
||||
@@ -355,7 +352,6 @@ func _on_battle_end_continue() -> void:
|
||||
adventure = null
|
||||
_adventure_mode = false
|
||||
tune_panel.reset_sliders()
|
||||
strip_view.refresh(null)
|
||||
_start_overlay.visible = true
|
||||
_refresh_presentations()
|
||||
|
||||
|
||||
@@ -7,39 +7,22 @@
|
||||
extends RefCounted
|
||||
|
||||
|
||||
# 战斗屏四区(A 进度条 / B 战斗画面 / C·D 两列——E 区道具面板已随可选类删除下线,2026-09-15 格面资源批)
|
||||
# 战斗屏三区(B 战斗画面 / C·D 两列——A 区关卡进度条顶栏随 停点前移批 2026-09-23 退役:节点链
|
||||
# 进度由关卡画面本体(选路覆盖层整图)承载,28px 纵向空间拨给 C/D 底行 520→548;E 区道具面板
|
||||
# 已随可选类删除下线,2026-09-15 格面资源批)。
|
||||
# + 结算演出覆盖层(A–D 之上、模态之下——跨区弹道/光带载体,结算演出链批 2026-09-20)。
|
||||
# 返回句柄字典:{strip_view, gp_label, stage, log_view, info_panel, board_zone, board_view, ap_bar, settlement_fx}
|
||||
# 返回句柄字典:{gp_label, keystone_label, stage, log_view, info_panel, board_zone, board_view, ap_bar, settlement_fx}
|
||||
static func build_battle_ui(root: Control, db) -> Dictionary:
|
||||
# set_anchors_and_offsets_preset(非 set_anchors_preset):后者会把控件重置回最小内容尺寸
|
||||
# (实测 927×613——缩顶、棋盘 45px 格 ×10 行超出 D 区 333px 高被裁,2026-09-13 用户报告回归)
|
||||
root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
root.add_theme_constant_override("separation", 0)
|
||||
|
||||
# A 区:关卡进度条(全宽 × 28px 顶栏)+ GP 余额常驻位(GP+商店房实施批 2026-09-21——
|
||||
# 顶栏邻接位,显示名「金币」;文案装配归 HudPresenter,此处只建节点)
|
||||
var strip_panel := PanelContainer.new()
|
||||
strip_panel.custom_minimum_size = Vector2(0, 28)
|
||||
var sb := UiKit.panel_flat(UiKit.BG_ROOT, UiKit.BG_ROOT, 0, 0)
|
||||
strip_panel.add_theme_stylebox_override("panel", sb)
|
||||
root.add_child(strip_panel)
|
||||
var strip_row := HBoxContainer.new()
|
||||
strip_panel.add_child(strip_row)
|
||||
var strip_view := LevelStripView.new()
|
||||
strip_view.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
strip_row.add_child(strip_view)
|
||||
var gp_label := UiKit.label("金币 0", 15, true)
|
||||
gp_label.add_theme_color_override("font_color", UiKit.GOLD_DEEP)
|
||||
strip_row.add_child(gp_label)
|
||||
var keystone_label := UiKit.label("钥石 0", 15, true) # 元进度实现批——A 区顶栏钥石余额常驻(文案装配归 HudPresenter,此处只建节点)
|
||||
keystone_label.add_theme_color_override("font_color", UiKit.GOLD)
|
||||
strip_row.add_child(keystone_label)
|
||||
|
||||
# B 区:战斗画面(全宽 × 252px——常驻双舞台,布局决策 D1)
|
||||
var stage := BattleStageView.new(db)
|
||||
root.add_child(stage)
|
||||
|
||||
# C/D 两列(520px)——E 区库存面板删除后 D 区横向加宽
|
||||
# C/D 两列(548px——停点前移批 2026-09-23:原 A 区 28px 顶栏纵向空间并入)
|
||||
var bottom := HBoxContainer.new()
|
||||
bottom.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
bottom.add_theme_constant_override("separation", 4)
|
||||
@@ -56,10 +39,11 @@ static func build_battle_ui(root: Control, db) -> Dictionary:
|
||||
var settlement_fx := SettlementFxView.new()
|
||||
root.add_child(settlement_fx)
|
||||
|
||||
# GP/钥石余额常驻位(GP+商店房实施批 2026-09-21 原居 A 区顶栏——顶栏退役后迁 C 区底行,
|
||||
# 句柄经 info_panel 视图成员透出;文案装配归 HudPresenter,此处只透传引用)
|
||||
return {
|
||||
"strip_view": strip_view,
|
||||
"gp_label": gp_label,
|
||||
"keystone_label": keystone_label,
|
||||
"gp_label": info_panel.gp_label,
|
||||
"keystone_label": info_panel.keystone_label,
|
||||
"stage": stage,
|
||||
"log_view": stage.log_view,
|
||||
"info_panel": info_panel,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 实现 GDD:design/ux/battle.md(D 区扫雷图 ~760×520 容器——顶部 AP 条 44px〔左:点阵+停手;
|
||||
# 实现 GDD:design/ux/battle.md(D 区扫雷图 ~760×548 容器〔底行 520→548——停点前移批 2026-09-23,
|
||||
# 原 A 区顶栏空间并入〕——顶部 AP 条 44px〔左:点阵+停手;
|
||||
# 右:图例+弃图刷新〕、棋盘主体居中、toast 槽上缘内侧、演出层)。
|
||||
# Date: 2026-09-12
|
||||
# 高内聚边界:容器组合 + 信号转发——动作意图全部信号外发到组合根,零核心写调用。
|
||||
|
||||
+18
-1
@@ -1,5 +1,6 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 实现 GDD:design/ux/battle.md(C 区角色信息 240×520——HP/攻击(+修正)/RAP 直角细线数据框
|
||||
# 实现 GDD:design/ux/battle.md(C 区角色信息 240×548〔底行 520→548——停点前移批 2026-09-23,
|
||||
# 原 A 区顶栏空间并入〕——HP/攻击(+修正)/RAP 直角细线数据框
|
||||
# 〔修正来源角标〕、状态效果槽 ×4、小人立绘占位;RAP 清零可见反馈〔行动经济 义务〕;
|
||||
# 状态槽空态 = 槽隐藏上移补位〔EC12〕)。
|
||||
# Date: 2026-09-12
|
||||
@@ -20,6 +21,8 @@ var _rap_label: Label
|
||||
var _rap_feedback: Label
|
||||
var _status_feedback: Label
|
||||
var slot_row: StatusSlotRow
|
||||
var gp_label: Label # 冒险钱包余额常驻位(停点前移批 2026-09-23——原 A 区顶栏迁入 C 区底行)
|
||||
var keystone_label: Label # 钥石余额常驻位(同上——文案装配归 HudPresenter)
|
||||
var _last_rap := 0 # 上次投影值(清零反馈判据——禁解析自身 label 文本当状态)
|
||||
|
||||
|
||||
@@ -76,6 +79,20 @@ func _init() -> void:
|
||||
portrait_glyph.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
portrait.add_child(portrait_glyph)
|
||||
|
||||
# 钱包行(停点前移批 2026-09-23:A 区 28px 顶栏退役——GP/钥石余额常驻位迁本面板底行,
|
||||
# 建节点归视图、文案装配归 HudPresenter,句柄经 ui_assembler 透传组合根)
|
||||
var wallet_row := HBoxContainer.new()
|
||||
wallet_row.add_theme_constant_override("separation", 10)
|
||||
col.add_child(wallet_row)
|
||||
gp_label = UiKit.label("金币 0", 15, true)
|
||||
gp_label.add_theme_color_override("font_color", UiKit.GOLD_DEEP)
|
||||
wallet_row.add_child(gp_label)
|
||||
keystone_label = UiKit.label("钥石 0", 15, true)
|
||||
keystone_label.add_theme_color_override("font_color", UiKit.GOLD)
|
||||
keystone_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
keystone_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
wallet_row.add_child(keystone_label)
|
||||
|
||||
|
||||
func set_hero(hp: int, max_hp: int) -> void:
|
||||
_hp_bar.max_value = maxi(1, max_hp)
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 实现 GDD:design/ux/battle.md(A 区关卡进度条——全宽 × 28px 顶栏,当前节点高亮、已过节点暗金)。
|
||||
# Date: 2026-09-12(battle.md 战斗屏改造批——对齐 28px 顶栏与高亮样式;基础 = 2026-09-11 关卡节点地图-lite 呈现)
|
||||
# 2026-09-17 分支节点图批:图形态行集渲染 = 行进序列(已到暗金 → 当前金 → 可选下一跳亮边候选);
|
||||
# 单链形态(第一关零迁移)保持全链横条渲染。
|
||||
# 高内聚边界:只读 runner 的节点链/进度/候选做渲染,无任何写面。
|
||||
class_name LevelStripView
|
||||
extends HBoxContainer
|
||||
|
||||
const COL_DONE := UiKit.GOLD_DIM # 已过——暗金
|
||||
const COL_CURRENT := UiKit.GOLD # 当前——奖励金高亮
|
||||
const COL_UPCOMING := UiKit.DARK_UPCOMING
|
||||
const COL_CANDIDATE := UiKit.GOLD_LIT # 候选下一跳——亮金边框
|
||||
const COL_BOSS_BORDER := UiKit.FLAG_RED # Boss 边框——旗朱
|
||||
const NODE_LABELS := {"battle": "战", "reward": "赏", "rest": "疗", "boss": "王", "shop": "商"} # 商 = GP+商店房实施批 2026-09-21
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
add_theme_constant_override("separation", 6)
|
||||
custom_minimum_size = Vector2(0, 28) # A 区 28px 顶栏(battle.md §Layout)
|
||||
|
||||
|
||||
func refresh(runner) -> void:
|
||||
for child in get_children():
|
||||
child.queue_free()
|
||||
if runner == null:
|
||||
return
|
||||
var nodes: Array = runner.nodes()
|
||||
for node in nodes:
|
||||
if node.has("next"):
|
||||
refresh_graph(runner)
|
||||
return
|
||||
_refresh_chain(runner, nodes)
|
||||
|
||||
|
||||
# 单链形态(退化观感 = 全链横条——第一关现状渲染)
|
||||
func _refresh_chain(runner, nodes: Array) -> void:
|
||||
for i in nodes.size():
|
||||
var node: Dictionary = nodes[i]
|
||||
var type := String(node.get("type", ""))
|
||||
add_child(_node_chip(type, _node_color(i, runner), false))
|
||||
if i < nodes.size() - 1:
|
||||
add_child(_arrow())
|
||||
|
||||
|
||||
# 图形态:行进序列 → 当前 → 可选下一跳(编号显示经 runner 编号空间读面)
|
||||
func refresh_graph(runner) -> void:
|
||||
var visited: Array = runner.visited
|
||||
var cands: Array = runner.next_candidates()
|
||||
for i in visited.size():
|
||||
var node: Dictionary = visited[i]
|
||||
var is_current: bool = i == visited.size() - 1 and String(runner.level_result) == ""
|
||||
add_child(_node_chip(String(node.get("type", "")), COL_CURRENT if is_current else COL_DONE, false,
|
||||
runner.node_id_of(node)))
|
||||
add_child(_arrow())
|
||||
for j in cands.size():
|
||||
var cand: Dictionary = cands[j]
|
||||
add_child(_node_chip(String(cand.get("type", "")), COL_UPCOMING, true, runner.node_id_of(cand)))
|
||||
if j < cands.size() - 1:
|
||||
add_child(_arrow())
|
||||
|
||||
|
||||
func _arrow() -> Label:
|
||||
var arrow := UiKit.label("→", 13, false)
|
||||
arrow.add_theme_color_override("font_color", UiKit.STONE)
|
||||
return arrow
|
||||
|
||||
|
||||
func _node_color(i: int, runner) -> Color:
|
||||
var finished: bool = i < int(runner.node_index) or String(runner.level_result) != ""
|
||||
var is_current: bool = i == int(runner.node_index) and String(runner.level_result) == ""
|
||||
if is_current:
|
||||
return COL_CURRENT
|
||||
if finished:
|
||||
return COL_DONE
|
||||
return COL_UPCOMING
|
||||
|
||||
|
||||
func _node_chip(type: String, col: Color, candidate_border: bool, node_id: int = -1) -> PanelContainer:
|
||||
var p := PanelContainer.new()
|
||||
var border := col
|
||||
var border_w := 0
|
||||
if type == "boss":
|
||||
border = COL_BOSS_BORDER
|
||||
border_w = 2
|
||||
elif candidate_border:
|
||||
border = COL_CANDIDATE
|
||||
border_w = 2
|
||||
var sb := UiKit.panel_flat(col, border, 4, border_w, Vector4(9, 2, 9, 2))
|
||||
p.add_theme_stylebox_override("panel", sb)
|
||||
var text := String(NODE_LABELS.get(type, "?"))
|
||||
if node_id >= 0:
|
||||
text += str(node_id)
|
||||
var l := UiKit.label(text, 15, true)
|
||||
l.add_theme_color_override("font_color", UiKit.INK_TITLE if col != COL_UPCOMING else UiKit.DARK_TEXT_FAINT)
|
||||
p.add_child(l)
|
||||
return p
|
||||
@@ -1 +0,0 @@
|
||||
uid://mv5cxybk4mf0
|
||||
@@ -67,6 +67,9 @@ static func run_level(db, seed_val: int, level_index: int, atk_pct: int, hp_pct:
|
||||
|
||||
|
||||
# 单关驱动段(run_level 抽取——零自动推进停点 + 战斗自动玩家;policy 经 st 入 _autoplay_battle)
|
||||
# 停点前移批 2026-09-23:入口停点(node_index 尚 -1 = 进节点前无 current_node)也经本分支驱动——
|
||||
# 读 next_candidate_ids 候选(入口单候选)→ pick_next_node,同一 _enter_node 与旧自动进入逐位
|
||||
# 等价(零 RNG 漂移);入口 pick 不计 st.choices(单候选强制选路,计数口径与旧测量面逐位同构)。
|
||||
static func _drive_level(runner, st: Dictionary, logger, policy: String, route: String) -> void:
|
||||
st["policy"] = policy
|
||||
var guard_battles := 20
|
||||
@@ -74,8 +77,9 @@ static func _drive_level(runner, st: Dictionary, logger, policy: String, route:
|
||||
while st.result == "" and guard_battles > 0 and guard_actions > 0:
|
||||
guard_actions -= 1
|
||||
if runner.awaiting_choice():
|
||||
# 选路态(兑付确认/战斗胜利后的零自动推进停点)——route 策略显式选下一跳
|
||||
st.choices = int(st.choices) + 1
|
||||
# 选路态(入口停点 / 兑付确认 / 战斗胜利后的零自动推进停点)——route 策略显式选下一跳
|
||||
if runner.node_index >= 0:
|
||||
st.choices = int(st.choices) + 1
|
||||
runner.pick_next_node(_route_pick(runner, route))
|
||||
continue
|
||||
guard_battles -= 1
|
||||
|
||||
@@ -120,14 +120,28 @@ func _smoke_adventure_table() -> void:
|
||||
check(ks >= 0 and ks <= 999, "keystone_per_level ∈ [0,999](%d)" % ks)
|
||||
|
||||
|
||||
# ② 冒险结构 API 闸(状态机受理面——重复开始拒绝 / 放弃两态)
|
||||
# ② 冒险结构 API 闸(状态机受理面——重复开始拒绝 / 入口停点 / 放弃两态)
|
||||
func _smoke_adventure_api() -> void:
|
||||
print("--- 冒险结构 API 闸(重复开始拒绝 · 放弃两态受理)---")
|
||||
print("--- 冒险结构 API 闸(重复开始拒绝 · 入口停点 · 放弃两态受理)---")
|
||||
var adr = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(42))
|
||||
adr.set_hero_override(900, 400) # 首战速胜档(放弃的非战斗受理要走到选路态)
|
||||
# 停点前移批 2026-09-23:入口停点观测(信号序 + 入口候选快照——容器写回规避 lambda 按值捕获)
|
||||
var sig_seq: Array = []
|
||||
var obs := {"entry_cands": []}
|
||||
adr.level_started.connect(_api_mark_started.bind(sig_seq))
|
||||
adr.choices_presented.connect(_api_mark_choices.bind(sig_seq, obs))
|
||||
adr.node_entered.connect(_api_mark_node.bind(sig_seq))
|
||||
check(adr.start_adventure(42), "冒险启动受理(主种子注入恰一次——state=running)")
|
||||
check(not adr.start_adventure(43), "进行中重复 start_adventure 拒绝(重开须待结算完毕)")
|
||||
check(adr.runner().current_battle() != null, "首战在飞(接口①开始关卡直达战斗节点)")
|
||||
check(sig_seq.size() == 2 and String(sig_seq[0]) == "level_started" and String(sig_seq[1]) == "choices_presented",
|
||||
"停点前移次序契约:level_started 先于入口候选呈现(反序选路口会被呈现层 runner 空防御丢弃)")
|
||||
check(adr.runner().awaiting_choice() and (obs["entry_cands"] as Array).size() == 1 \
|
||||
and adr.runner().current_battle() == null,
|
||||
"入口停点:选路态 + 入口单候选 + 首战未开(呈现零 RNG 消耗——pick 前零战斗域)")
|
||||
check(adr.runner().pick_next_node(int(adr.runner().next_candidate_ids()[0])),
|
||||
"pick 入口候选受理(同一 _enter_node——与旧自动进入逐位等价)")
|
||||
check(sig_seq.size() == 3 and String(sig_seq[2]) == "node_entered" and adr.runner().current_battle() != null,
|
||||
"pick 后 node_entered 到达 + 首战在飞(接口①开始关卡——停点闭合直达战斗节点)")
|
||||
check(not adr.abandon(), "战斗中 abandon 拒绝(战斗域单事务在飞)")
|
||||
# 驱动首战到胜利 → 选路态(非战斗期)→ abandon 受理
|
||||
var autoplay = load("res://smoke/level_autoplay.gd")
|
||||
@@ -146,6 +160,20 @@ func _smoke_adventure_api() -> void:
|
||||
check(not adr.abandon(), "结算后重复 abandon 拒绝(结算受理只算一次——state=idle)")
|
||||
|
||||
|
||||
# 停点前移批 2026-09-23 观测桩(信号序采集——bind 容器写回,签名按各信号实参定制)
|
||||
func _api_mark_started(_li: int, _dn: String, seq: Array) -> void:
|
||||
seq.append("level_started")
|
||||
|
||||
|
||||
func _api_mark_choices(cands: Array, seq: Array, obs: Dictionary) -> void:
|
||||
seq.append("choices_presented")
|
||||
obs["entry_cands"] = cands.duplicate()
|
||||
|
||||
|
||||
func _api_mark_node(_node: Dictionary, _info: Dictionary, seq: Array) -> void:
|
||||
seq.append("node_entered")
|
||||
|
||||
|
||||
# ③ 冒险验收断言(映射 冒险结构.md 验收标准——表覆盖/满配通关/种子复现/大奖励/败北线/
|
||||
# 重复拒绝/GP 跨关延续/加算区同账/恢复数学/放弃)
|
||||
func _smoke_adventure() -> void:
|
||||
@@ -244,6 +272,7 @@ func _smoke_adventure() -> void:
|
||||
var adr6 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(7))
|
||||
adr6.set_hero_override(900, 400)
|
||||
check(adr6.start_adventure(7), "重复拒绝场景:冒险启动(seed 7)")
|
||||
adr6.runner().pick_next_node(int(adr6.runner().next_candidate_ids()[0])) # 停点前移批 2026-09-23:入口停点 pick 进首战
|
||||
var b6 = adr6.runner().current_battle()
|
||||
var st6 := _adv_st()
|
||||
var g6 := 30
|
||||
@@ -314,7 +343,9 @@ func _smoke_adventure() -> void:
|
||||
# ⑩ 放弃(战斗中拒绝 · 非战斗期受理同失败型结算)
|
||||
var a10 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(15))
|
||||
a10.set_hero_override(900, 400)
|
||||
check(a10.start_adventure(15), "放弃场景:冒险启动(seed 15——首战在飞)")
|
||||
check(a10.start_adventure(15), "放弃场景:冒险启动(seed 15)")
|
||||
check(a10.runner().pick_next_node(int(a10.runner().next_candidate_ids()[0])),
|
||||
"放弃场景:入口停点 pick 进首战(停点前移批 2026-09-23 驱动口径)")
|
||||
check(not a10.abandon(), "战斗中 abandon 拒绝(战斗域单事务在飞)")
|
||||
var b10 = a10.runner().current_battle()
|
||||
var st10 := _adv_st()
|
||||
|
||||
@@ -87,6 +87,13 @@ func _ready() -> void:
|
||||
main.tune_panel.set_sliders(400, 200) # 高加成保 L1 可通关——L2 岔路选路段只能经冒险推进到达(单关直入口退役)
|
||||
var t1 := Time.get_ticks_msec()
|
||||
main._start_adventure_from_menu()
|
||||
await get_tree().process_frame
|
||||
# 停点前移批 2026-09-23:关卡开始 = 入口选路停点——真实按钮路径选入口候选进首战
|
||||
#(首格可点 AC 口径随之含入口选路一步:菜单点击 → 停点呈现 → pick → 棋盘安装)
|
||||
if main.route_overlay.visible:
|
||||
main.route_overlay.press_candidate(0)
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
var t_entry := Time.get_ticks_msec() - t1
|
||||
# 性能 AC:进入战斗屏到首格可点 ≤500ms(棋盘 install 完成口径);
|
||||
# 场景实例化冷启动总时长(含全部脚本首载编译——引擎启动域,非 AC 口径)另列观测
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# 实现 GDD:design/ux/battle.md(场景级 headless 探针——四区布局实例化断言;entry 17②③)。
|
||||
# Date: 2026-09-12(战斗屏改造批扩展;基础 = 2026-09-11 首战棋盘探针——既有断言全保留)
|
||||
# 项目运行模式(autoload 在位)实例化 main.tscn,直接驱动 _start_adventure_from_menu()(完整冒险——唯一试玩入口),
|
||||
# 断言四区真实构建(棋盘/进度条/主角站桩/敌方站桩/AP 点阵/明细带/数据框;E 区道具面板已删除;
|
||||
# 2026-09-20 全站桩拍板——英雄卡/敌人卡断言随站桩改造批换装)。
|
||||
# 断言真实构建(棋盘/主角站桩/敌方站桩/AP 点阵/明细带/数据框;A 区进度条断言随 停点前移批 2026-09-23 退役;
|
||||
# E 区道具面板已删除;2026-09-20 全站桩拍板——英雄卡/敌人卡断言随站桩改造批换装)。
|
||||
# 运行:Godot_console.exe --headless --path . res://smoke/ui_probe.tscn
|
||||
extends Node
|
||||
|
||||
@@ -61,6 +61,17 @@ func _ready() -> void:
|
||||
main._start_adventure_from_menu()
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
# ---- 入口选路停点(停点前移批 2026-09-23:关卡开始即出节点图——level_started 后、首战前弹选路层;
|
||||
# 真实按钮路径 pick 入口单候选进首战)----
|
||||
if not main.route_overlay.visible:
|
||||
problems.append("关卡开始未弹入口选路停点(route_overlay 应可见)")
|
||||
elif main.runner == null or not main.runner.awaiting_choice() \
|
||||
or main.runner.next_candidate_ids().size() != 1:
|
||||
problems.append("入口停点状态失守(选路态 + 入口单候选)")
|
||||
else:
|
||||
main.route_overlay.press_candidate(0)
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
# ---- 放弃入口·冒险模式可见 + 战斗中按下被拒(真实按钮路径——入口挂选路停点,战斗中拒绝为防御分支)----
|
||||
if main.route_overlay.abandon_btn == null or not main.route_overlay.abandon_btn.visible:
|
||||
problems.append("冒险模式放弃按钮未开放(选路停点入口在案)")
|
||||
@@ -69,13 +80,11 @@ func _ready() -> void:
|
||||
await get_tree().process_frame
|
||||
if main.adventure == null or String(main.adventure.state) != "running":
|
||||
problems.append("首战中放弃未被拒(战斗域单事务在飞——state 应保持 running)")
|
||||
# ---- 既有断言(回归基线)----
|
||||
# ---- 既有断言(回归基线;A 区进度条断言随 停点前移批 2026-09-23 退役——节点链进度由关卡画面选路覆盖层承载)----
|
||||
if main.board_view._cells.is_empty():
|
||||
problems.append("棋盘格未生成(cells=0)")
|
||||
if main.board_view.board() == null:
|
||||
problems.append("board 未装载")
|
||||
if main.strip_view.get_child_count() < 2 * main.runner.nodes().size() - 1:
|
||||
problems.append("节点进度条未构建(%d 子节点)" % main.strip_view.get_child_count())
|
||||
if main.info_panel.hp_text().begins_with("HP —") or main.info_panel.hp_text().contains("—"):
|
||||
problems.append("C 区血量未显示(%s)" % main.info_panel.hp_text())
|
||||
if main.settlement == null or main.settlement.battle_result != "":
|
||||
@@ -183,9 +192,9 @@ func _ready() -> void:
|
||||
main.board_view.set_desaturated(true)
|
||||
main.board_view.set_desaturated(false)
|
||||
if problems.is_empty():
|
||||
print("[ui_probe] PASS — cells=%d board=%dx%d strip=%d hp='%s' enemies=%d ap_dots=%d" % [
|
||||
print("[ui_probe] PASS — cells=%d board=%dx%d hp='%s' enemies=%d ap_dots=%d" % [
|
||||
main.board_view._cells.size(), main.board_view.board().width, main.board_view.board().height,
|
||||
main.strip_view.get_child_count(), main.info_panel.hp_text(),
|
||||
main.info_panel.hp_text(),
|
||||
main.stage.enemy_box.get_child_count(), main.ap_bar._dots.get_child_count()])
|
||||
get_tree().quit(0)
|
||||
else:
|
||||
|
||||
@@ -64,6 +64,11 @@ func _ready() -> void:
|
||||
main._start_adventure_from_menu() # 完整冒险路径(唯一试玩入口;_start_single 按语义清零滑条 = 恒基线,不承载加成断言)
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
# 停点前移批 2026-09-23:关卡开始 = 入口选路停点——真实按钮路径选入口候选进首战再读呈现快照
|
||||
if main.route_overlay.visible:
|
||||
main.route_overlay.press_candidate(0)
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
var hero: Dictionary = main.settlement.presentation_snapshot().hero
|
||||
if int(hero.get("atk", -1)) != boosted_atk or int(hero.get("max_hp", -1)) != boosted_hp:
|
||||
problems.append("战斗加成单次生效违例(atk=%s max_hp=%s,期望 %d/%d)" % [
|
||||
|
||||
Reference in new issue
Block a user