From 8eddcdd5a72d699f2d2e5bfeda28c13baf027e7d Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Thu, 24 Sep 2026 00:59:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(flow):=20=E5=81=9C=E7=82=B9=E5=89=8D?= =?UTF-8?q?=E7=A7=BB=E6=89=B9=E2=80=94=E2=80=94LevelRunner=20=E5=85=A5?= =?UTF-8?q?=E5=8F=A3=E5=81=9C=E7=82=B9=E6=A8=A1=E5=BC=8F(opt-in=20defer+?= =?UTF-8?q?=E9=9B=B6RNG=E6=BC=82=E7=A7=BB)+=E5=85=B3=E5=8D=A1=E5=BC=80?= =?UTF-8?q?=E5=B1=80=E5=8D=B3=E5=87=BA=E5=9B=BE=E9=80=89=E8=B7=AF+A=20?= =?UTF-8?q?=E5=8C=BA=E8=BF=9B=E5=BA=A6=E6=9D=A1=E9=80=80=E5=BD=B9(?= =?UTF-8?q?=E8=BD=AC=20C=20=E5=8C=BA=E9=92=B1=E5=8C=85=E8=A1=8C,=E5=BA=95?= =?UTF-8?q?=E8=A1=8C=20548)+GDD=20=E5=9B=9B=E6=96=87=E4=BB=B6=E5=AF=B9?= =?UTF-8?q?=E9=BD=90=E4=B8=89=E5=8C=BA+art-bible=20=E8=BF=9E=E5=B8=A6+06?= =?UTF-8?q?=20=E6=9D=A1=E7=9B=AE=E8=A7=A3=E5=86=B3=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=EF=BC=88=E7=94=A8=E6=88=B7=E6=8B=8D=E6=9D=BF=202026-09-23?= =?UTF-8?q?=EF=BC=8C=E5=8F=B0=E8=B4=A6=20142=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/art/art-bible.md | 7 +- design/gdd/06-待办清单.md | 8 -- design/gdd/systems/冒险循环/关卡节点地图.md | 26 ++--- design/gdd/systems/冒险循环/冒险结构.md | 3 +- design/gdd/systems/表现与支撑/HUD框架.md | 28 +++--- design/ux/battle.md | 34 +++---- .../core/adventure_runner.gd | 14 ++- .../core/level_runner.gd | 23 ++++- .../presentation/main.gd | 18 ++-- .../presentation/ui_assembler.gd | 34 ++----- .../presentation/views/board_zone_view.gd | 3 +- .../views/hero_info_panel_view.gd | 19 +++- .../presentation/views/level_strip_view.gd | 98 ------------------- .../views/level_strip_view.gd.uid | 1 - .../smoke/level_autoplay.gd | 8 +- .../smoke/smoke_all.gd | 39 +++++++- .../smoke/ui_click_probe.gd | 7 ++ .../smoke/ui_probe.gd | 23 +++-- .../smoke/ui_tune_probe.gd | 5 + 19 files changed, 190 insertions(+), 208 deletions(-) delete mode 100644 prototypes/dungeon-sweeper-vertical-slice/presentation/views/level_strip_view.gd delete mode 100644 prototypes/dungeon-sweeper-vertical-slice/presentation/views/level_strip_view.gd.uid diff --git a/design/art/art-bible.md b/design/art/art-bible.md index 194e9cb8..5c7d1634 100644 --- a/design/art/art-bible.md +++ b/design/art/art-bible.md @@ -1,6 +1,6 @@ # 美术圣经(Art Bible)— Dungeon Sweeper -> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**(§6 环境/§7 UI/§8 资产规范/§9 参考与反参考已于 2026-09-23 v1.8 按 D1/D2/D4/D5 框架成文;D3=§5 角色已于 v1.6 落地、v1.7 抽离独立文档;框架 2026-09-22 用户拍板,原登记于中间产物修订指导,经裁决不入库,明细归台账 125);历次修订明细见文末版本历史(现行为 v2.0) +> 项目:Dungeon Sweeper(扫雷 × 自动战斗 闯关 Roguelike)· 状态:**第 1–9 节定稿**(§6 环境/§7 UI/§8 资产规范/§9 参考与反参考已于 2026-09-23 v1.8 按 D1/D2/D4/D5 框架成文;D3=§5 角色已于 v1.6 落地、v1.7 抽离独立文档;框架 2026-09-22 用户拍板,原登记于中间产物修订指导,经裁决不入库,明细归台账 125);历次修订明细见文末版本历史(现行为 v2.1) > > **Art Director Sign-Off (AD-ART-BIBLE)**:历次批均 SKIPPED(v1.8 §6–9 补全亦 SKIPPED)— Lean/Solo mode(非 PHASE-GATE;§6–9 已成文,full 门禁签核时机待用户表态) > 风格基调(2026-09-10 用户定稿):地牢题材但**非暗黑风**——轻快明亮的幻想冒险风,全年龄;像素风;角色 2~3 头身 @@ -332,7 +332,7 @@ ## 6. 环境与场景(Environment & Scenes) -> 本节定格面之外的场景容器怎么画:战斗舞台背景、营地场景、三关图类型色板方向、关卡间下探过渡。格内画法归 §3.2,情绪与能量归 §2,换色模板归 §4.3;本节不新增信息通道、不承载玩法语义。屏级分区布局(四区)的裁决权归 battle.md,本节只定环境资产的视觉规格。 +> 本节定格面之外的场景容器怎么画:战斗舞台背景、营地场景、三关图类型色板方向、关卡间下探过渡。格内画法归 §3.2,情绪与能量归 §2,换色模板归 §4.3;本节不新增信息通道、不承载玩法语义。屏级分区布局(三区 B/C/D)的裁决权归 battle.md,本节只定环境资产的视觉规格。 ### 6.1 战斗舞台背景(B 区环境) @@ -381,7 +381,7 @@ ### 7.3 布局栅格 -- **结构**:双舞台四区(A 顶栏进度/B 战斗画面/C 数据框/D 棋盘)为结构冻结项,分区尺寸与像素级裁决归 battle.md(HUD框架承载 1280×800 基座锚点值,如扫雷图区 1036×476、结算明细带展开 84px)。 +- **结构**:双舞台三区(B 战斗画面/C 数据框/D 棋盘;字母保持不重编)为结构冻结项,分区尺寸与像素级裁决归 battle.md(HUD框架承载 1280×800 基座锚点值,如扫雷图区 1036×504、结算明细带展开 84px)。 - **栅格口径(候选)**:面板间距/内边距按 4px 步进档(8/12/16/24 候选),全 UI 恒定;数据区直角细线不占圆角预算。终值归样张批。 ### 7.4 字体与字号 @@ -452,6 +452,7 @@ ## 版本历史 +- **v2.1(2026-09-23)停点前移批连带同步(批内代拍板②引用,worktree stoppoint-shift-20260923,台账 142)**:§6 开篇与 §7.2 结构行「双舞台四区」改「双舞台三区(B/C/D,字母保持不重编)」、扫雷图区锚点 1036×476→1036×504——战斗屏 A 区进度条随停点前移批退役(战后返回关卡画面,节点链进度由关卡画面选路覆盖层节点态承载),28px 纵向空间归 C/D 底行;裁决权与事实源归属 battle.md/HUD框架 不变,历次版本历史行不动。 - **v2.0(2026-09-23)UI 章迁入批 + 画布定档 + 色板方向裁定(用户对 v1.8 代拍板四项逐条表态+五缺口给口径;worktree art-ch69-amend-20260923,台账 138)**:①**§3.3/§4.4 物理迁入 §7**(用户拍板:UI 具体选择须在 UI 章定义,三/四章只留简单定义与方向指导——v1.8 代拍板①「按引用整合」据此推翻):§3.3/§4.4 降为方向指导节(各留一段+指路),细则全文迁 **§7.1 形状语法细则/§7.2 UI 色板细则**;§7 重排号 7.1–7.5(原 7.4 组件库语义并入 7.1,零内容损失);§6.2/§7.3/§7.5 与清单引用随改。②**§8.2 画布基准暂定值**(用户缺口一裁定):主角人设原图=48×72、主角战斗状态图(侧向)=48×72〔v1.9 待决注记「主角立绘档空缺」收口〕、棋盘小人=32×32 二头身独立派生资产(允许更小;36px 板占比摩擦沿「独立小源档」路线样张批定终值)、敌种立绘三档 48/64/96=普通怪(小)/精英(中)/Boss(大)对应关系经确认保留候选;旧「像素基准 32px」通用候选退役;1920 档缩放策略与帧数规格维持待裁决,登记 06-待办清单(表现与支撑/HUD框架节新条目),样张批时提醒。③**§6.3 三关色板方向**(用户裁定):underdark 幽暗地域=**冷调幽暗**(灰白蓝主调+少量紫黑点缀,紫系取紫罗兰域 ≈256° 候选、禁入带 300°–345° 不变)、deep_underdark 灰矮人矿城=**灰黑主调+矿脉金属色点缀**(金属质感族冷银/铜灰候选,奖励金亮金禁上格底 §4.2 不变)——替换 v1.8 苔绿/冷灰岩方向草案。④**§9.1 扩池候选否决**(Forager/杀戮尖塔不入池且撤登记——用户裁定美术参考意义不足;代拍板④收口)。⑤命名规范草案获确认(代拍板②收口)。清单 §0 像素行与风格宪法表指向随批。 - **v1.9(2026-09-23)§8.2 待决注记批**(侧会话用户指令「你在这里直接把这件事给做了吧」;worktree art-s82-note-20260923,台账 137):§8.2 增两条待决注记——①**36px 板 × 32px 源摩擦**:32px 源在 45px 格 1× 占 ≈71% 合规、在 36px 格 1× 占 ≈89% 超「≤0.75 格边」红线且禁非整数缩放回退,出路二选一(棋盘小人独立更小源档如 24px 级 vs 两种板各出一版),样张批随格边长终值一并裁决,裁决前棋盘小人源图不排产;②**主角立绘档空缺**:主角 B 区站桩立绘未单独立档(现行 48/64/96px 阶梯按敌种 archetype 写),随战斗一屏样张锁「角色剪影方向/战斗空间」时一并定。头注「现行为」随批 v1.9。 - **v1.8(2026-09-23)§6–9 四章补全批(D 框架 D1/D2/D4/D5 落地,圣经全章成文;worktree art-ch6-9-20260923,台账 136)**:**§6 环境与场景**——6.1 战斗舞台背景(明亮地窟/低对比自然后退/明度恒定适用/`wall_ambient` 联动/紫赤仅 Boss 战)、6.2 营地场景(烛光黄×木色/三柜台一字排开/亮起走饱和不动明度/系统语义归元进度 GDD)、6.3 三关图类型色板候选(dungeon 暖石棕基准档/underdark 苔绿偏灰/deep_underdark 冷灰岩,方向候选归样张批重跑 §1.4 冻结,增「与五组微光互辨」抽测)、6.4 下探过渡(渐变取下一关槽值,只动色相饱和);**§7 UI 设计**——7.1 视觉基准(§3.3/§4.4 上位法**按引用整合**,D5 框架「迁入」不物理迁移——保持形状/色彩两章完整性,物理迁移待用户另有裁决)、7.2 布局栅格(四区结构事实源归 battle.md+4px 步进档候选)、7.3 字体字号(Kenney CC0/Fusion Pixel OFL 候选+整数倍缩放+下限 18/14/13 事实源 HUD框架+禁字重区分)、7.4 组件库三类容器语义+极性冗余、7.5 营地界面;**§8 资产与技术规范**——8.1 两档定案+格边长口径(锚点 45/36px 事实源 battle.md,扩板与 1920 档换算归表现批)、8.2 像素基准 32px 候选(active.md 58 条挂账清偿入章)+整数倍缩放纪律+1920 档适配策略候选+画布档位候选、8.3 导出 PNG/SpriteFrames+命名规范候选草案(对象键=数据表主键,图标键契约的文件侧延伸)、8.4 帧数规格候选档;**§9 参考与反参考**——9.1 正面三款取/弃表+扩池候选(Forager/杀戮尖塔)仅登记不入池、9.2 反面四条绑条款(Darkest Dungeon 压暗/Gungeon 粒子色相爆炸/Hades 特效密度/传统扫雷八色),反面图证待素材生成。**同步面**——头注状态行/Sign-Off 注记/参考池行、art-assets-checklist §0 像素尺寸行与参考图行及 §6 依赖链、04-美术音频方向.md 参考指引随批。**代拍板四项待追认**——①D5「迁入」按引用整合不物理迁移;②命名规范候选草案;③栅格/画布/帧数候选值系起草非裁决;④扩池候选两款不入池仅登记。 diff --git a/design/gdd/06-待办清单.md b/design/gdd/06-待办清单.md index 4c758c01..31243ade 100644 --- a/design/gdd/06-待办清单.md +++ b/design/gdd/06-待办清单.md @@ -221,14 +221,6 @@ **状态**:方向观察待立项(2026-09-20 登记防散佚;立项前不展开设计) -#### Alpha 第一批:冒险开局停点前移——关卡画面常驻枢纽(节点图先于首战+进度条退场落地) - -**现状**:用户 2026-09-23 反馈冒险入口流程倒置(进冒险直进战斗、战后才弹节点图)并拍目标形态——先见关卡画面选节点、再进战斗/商店/营地节点屏,战后返回关卡画面;同批两裁定——**战斗屏进度条退场**(节点链进度只留关卡画面,战斗屏目标形态 B/C/D 三区)、**A 区 LevelStripView 退役不迁移**(关卡画面不重新实现,由 RouteOverlay 升级常驻页面承接:节点态语义同族 UiKit token、同一 runner 只读数据出口,零独有资产流失)。方案要素已落籍 `docs/architecture/adr-0006-board-node2d-rendering.md` 与 `board-node2d-migration-plan.md` §1。执行面:①流程停点前移(LevelRunner 加停点模式——关卡开始不自动 `_enter_node`,改出图选路;RouteOverlay 开合时机改常驻:进关卡即开、战后返回即开)②LevelStripView 删除面(类本体、ui_assembler 组装行 :28、main.gd `strip_view` 引用 :52 等、ui_probe :77-78 构建检查与 :188 报告行两断言、battle.md A 区条款)③GDD 修订面三项(HUD框架 四区结构表、关卡节点地图 节点链常显条款、battle.md 像素布局表)④冒烟面(新停点行为断言+探针同步)。**随批三待裁决**:B/C/D 是否重新编字母、进度条腾出的 28px 纵向空间归属、关卡画面上节点链进度的具体呈现形态。 - -**归属**:Alpha 开工第一批;建议先于棋盘 Node2D Phase A 实施批开工(两者零硬依赖——先正流程后迁棋盘为最省路径;Phase A 方案已定稿随时可开工) - -**状态**:已立项登记(2026-09-23 用户拍板),待点名开工 - ### 难度曲线与Boss #### 调参批数值点评登记(八型批随批登记) diff --git a/design/gdd/systems/冒险循环/关卡节点地图.md b/design/gdd/systems/冒险循环/关卡节点地图.md index efa1084e..277e67bc 100644 --- a/design/gdd/systems/冒险循环/关卡节点地图.md +++ b/design/gdd/systems/冒险循环/关卡节点地图.md @@ -64,28 +64,29 @@ 每个节点在关卡内领一个**节点编号**(本文含义:关卡内不重复的序号,用来当战斗的定位地址;数据键沿用切片字段 `node_depth`)。Boss 的编号恒为全图最大——"最深处的房间是关底"这条直感由此保留。单链特例:一张每步只有一条连线的图就是一条链,此时节点编号等于它前面走过的战斗场数。第一关行集就是单链特例,切片代码零改动运行;分支图行集随第二关内容批落地(见 P1)。 -呈现侧口径(2026-09-17 用户拍板):节点图的画面表达走**方块地牢观感**——节点画成地牢房间的方块、连线画成通道——但逻辑拓扑就是本文的节点图,不产生任何额外选路或探索语义;视觉规格归 [HUD 框架](../表现与支撑/HUD框架.md)。 +呈现侧口径(2026-09-17 用户拍板):节点图的画面表达走**方块地牢观感**——节点画成地牢房间的方块、连线画成通道——但逻辑拓扑就是本文的节点图,不产生任何额外选路或探索语义。关卡画面本体即节点链进度的呈现载体(选路覆盖层的常驻形态,节点态直接读图:已过暗金 / 当前金 / 候选下一跳通道亮金 / Boss 旗朱描边),不另设进度条组件;视觉规格归 [HUD 框架](../表现与支撑/HUD框架.md)。 ### 行进与选路:兑付后确认,选路就是行进 -**结论**:关卡里没有一次自动前进——每一步都是玩家亲手走出来的。 +**结论**:关卡里没有一次自动前进——每一步都是玩家亲手走出来的。关卡画面(节点图,选路覆盖层的常驻形态)是单关内的常驻枢纽:关卡开始即呈现,此后每次兑付结束都回到它再选路。 行进的完整循环如下: ``` -到达节点 → 节点兑付(开战 / 发奖 / 回血)→ 兑付反馈呈现 → 玩家确认 - → 看当前节点的连线指向的下一批节点(岔路选其一;只有一条路就是"继续") - → 到达下一节点……直到 Boss 分出胜负 +关卡开始 → 呈现节点图选路停点(关卡画面;入口不自动进入,第一次选路就是"出发") + → 玩家选一条连线 → 到达所选节点 → 节点兑付(开战 / 发奖 / 回血) + → 兑付反馈呈现 → 玩家确认 → 返回关卡画面(战后回图) + → 再选路……直到 Boss 分出胜负 ``` 两条纪律: | 纪律 | 内容 | |---|---| -| 兑付后确认 | 到达节点立即兑付,不设"进入前确认"步;兑付反馈呈现完,玩家确认才继续。战斗节点的确认挂在战斗结算画面上,奖励/恢复节点挂在兑付反馈覆盖层上,商店节点挂在商店覆盖层的离开动作上——复用既有呈现位,零新增交互范式 | -| 零自动推进 | 确认之后是选路口:岔路口玩家选一条连线,单链路口确认"继续"。选了才走;不选就停在原地 | +| 兑付后确认 | 到达节点立即兑付,不设"进入前确认"步;兑付反馈呈现完,玩家确认才回图。战斗节点的确认挂在战斗结算画面上,奖励/恢复节点挂在兑付反馈覆盖层上,商店节点挂在商店覆盖层的离开动作上——复用既有呈现位,零新增交互范式 | +| 零自动推进 | 确认之后回到关卡画面的选路口:岔路口玩家选一条连线,单链路口确认"继续"。选了才走;不选就停在图上 | -开始关卡后的第一次行进确认就是"出发"。Boss 兑付(胜利或英雄死亡)触发关卡收束点,此后不再有选路——关卡已定局,后续流程归冒险结构。 +Boss 兑付(胜利或英雄死亡)触发关卡收束点,此后不再有选路——关卡已定局,后续流程归冒险结构。 ### 节点兑付:把控制流移交给小循环 @@ -132,12 +133,12 @@ | 上游 | [难度曲线与Boss](难度曲线与Boss.md) | 敌我数值曲线与遭遇投放节奏的调参所有权;本文零数值 | | 上游 | [数据配置](../表现与支撑/数据配置.md) | 商店表(商品行 / GP 掉落参数 / 价格参数)的承载与统一读取;商店覆盖层的商品与定价数据源 | | 下游 | [战斗结算](../战斗循环/战斗结算.md) | 两个调用入口的发起方:战斗发起组装(定位信息)、关卡收束(清库) | -| 下游 | [HUD 框架](../表现与支撑/HUD框架.md) | 节点图呈现(方块地牢观感)、当前位置与选路口、兑付反馈覆盖层的数据源 | +| 下游 | [HUD 框架](../表现与支撑/HUD框架.md) | 节点图呈现(方块地牢观感;关卡画面节点态即节点链进度呈现载体)、当前位置与选路口、兑付反馈覆盖层的数据源 | ## P0 最小闭环 -1. 从冒险结构接收开始关卡(关卡序号、当前生命、生效上限),加载本关节点图,玩家确认出发,行进自入口节点开始。 -2. 每节点兑付完成后玩家选路前进;岔路选一条、单链点继续;零自动推进。 +1. 从冒险结构接收开始关卡(关卡序号、当前生命、生效上限),加载本关节点图并呈现选路停点(关卡画面),玩家在图上选出第一跳,行进自所选节点开始(入口节点不自动进入)。 +2. 每节点兑付完成并确认后返回关卡画面,玩家再选路前进;岔路选一条、单链点继续;零自动推进。 3. 战斗 / Boss 节点:组装关卡序号 + 节点编号,移交战斗结算开战;战斗结束向冒险结构上报战斗结果。 4. 奖励节点:触发节点奖励兑付(切片期 = 直通合法空态);恢复节点:向冒险结构发治疗封顶回报;商店节点:打开商店覆盖层,购买即时生效,离开后兑付确认。 5. Boss 战胜利或任意战斗倒下 = 关卡收束点:先调战斗结算的关卡收束清库,再宣布关卡结束。 @@ -160,7 +161,7 @@ ## 验收标准 - 图合法:行集通过加载校验——节点编号关卡内唯一、入口唯一、Boss 为唯一终点、连线无环、所有节点沿连线可达 Boss;违规配置加载期致命。 -- 行进自主:从入口到 Boss 的每一次前进都由玩家选路发起;不存在任何自动推进路径;倒下时行进终止。 +- 行进自主:从入口到 Boss 的每一次前进都由玩家选路发起(含关卡开始后的第一跳——关卡开始停在图上,入口节点不自动进入);不存在任何自动推进路径;倒下时行进终止。 - 兑付移交:每场开战调用携带(关卡序号 + 节点编号),与节点图上该节点位置一致;每场战斗结束恰一次战斗结果上报。 - 兑付后确认:战斗节点的继续确认挂战斗结算画面,奖励/恢复挂兑付反馈覆盖层;到达与兑付之间无确认步。 - 恢复兑付:治疗封顶回报载荷 = 该关恢复比例(读关卡配置行);满血回复 0 是合法状态(执行语义归冒险结构)。 @@ -184,3 +185,4 @@ | v1.0 | 2026-09-17 | **定稿**(逐问题拍板,全用户裁决):① 形态重开——用户拍板重开[05-不做清单](../../05-不做清单.md)「路径分支地图后置」裁定,定稿为**分支节点图**(分层节点图,杀戮尖塔 / 哥布林弹珠同构;方块地牢美术观感、逻辑为节点图),P0 图定稿 + 单链特例先行;② 节点类型定稿四类封闭枚举不扩类;③ 行进交互定稿全节点确认 + 兑付后确认、零自动推进;④ 遭遇构成收窄为编排面三条(战斗内部初始化全归战斗小循环),寻址定稿节点编号升级(`node_depth` 语义升级为关卡内唯一节点编号,表键与字段名零改动,单链零漂移);⑤ 节点奖励拍板为功能道具的正式渠道(内容归魔法道具内容重定批,切片期直通为合法空态);⑥ 「精英与 Boss 编码位」(同层多遭遇由节点编号消解,不设编码位)与「多敌数量的消费面」(关卡层零消费)两悬案收口,同场多敌种面拆归战斗侧;⑦ 切片行进代码对照的接口漂移清单登记 06 | | v1.0.1 | 2026-09-17 | **P1 分支节点图内容与实现批落地**(用户拍板任务批——切片代码 / 难度曲线与Boss v0.7 / registry / 06 待办同批):首张分支图 = 切片关卡 2「第二层 · 幽暗地域」行集落地(9 节点网状图:入口 0 → 中间三层 2/3/2 → Boss 8,真实岔路 4 处,奖励点 + 恢复点各 1,战斗节点 6 + Boss);行进器图版推进(`choices_presented` 选路态 + `pick_next_node`——零自动推进,战斗胜利不再自动推进、`continue_after_node` 泛化为兑付确认,06 接口漂移清单 ④⑤⑧ 清偿);图合法性校验落地 `LevelTable.validate`(单链与图同一套算法,L1 零迁移过校验,违规加载致命);节点图选路 UI(选路覆盖层整图自绘——房间方块 + 通道连线 + 当前/候选高亮,占位图形质量;顶栏图形态渲染行进序列 + 候选);首页"关卡 2 试玩"直入口(测试模式,≠ 两关序列生效)。本版本号只记实现批状态,图规则口径零改动 | | v1.1 | 2026-09-20 | **商店房扩类(GP+商店房批,用户拍板——与冒险结构 / 战斗结算 / 地图战斗资源 / 难度曲线与Boss / registry / 切片同批)**:① 节点封闭枚举四类→五类(+商店房);② 编排规则定稿——每关恰 1、关底 Boss 前一位必经位(哈迪斯式"决战前最后一个补给点"定位);必经位由冒烟断言承载、图合法性六检零新增;③ 兑付定稿——打开商店覆盖层、购买即时生效写统一冒险加算区(与关卡大奖励同账,归冒险结构)、离开后兑付确认;商店房不携带战斗行;④ 节点兑付表 / 纪律表 / 依赖表 / P0 闭环 / 验收标准 / P2「事件类节点候选」注记同步 | +| v1.2 | 2026-09-23 | **停点前移批(冒险入口流程倒置修复,用户 2026-09-23 拍板;上位裁决 `docs/architecture/adr-0006-board-node2d-rendering.md` 与 `docs/architecture/board-node2d-migration-plan.md` §1,代码实装另批并行)**:① 行进循环改停点前移口径——关卡开始即呈现节点图选路停点(关卡画面 = 选路覆盖层常驻化,入口节点不自动进入,第一次选路就是"出发"),选路确认进节点,兑付确认后返回关卡画面再选路(战后回图);兑付后确认、零自动推进两条纪律语义不变,只改承载位;② 呈现侧口径补进度承载条款——关卡画面本体即节点链进度呈现载体(节点态直接读图:已过暗金 / 当前金 / 候选下一跳通道亮金 / Boss 旗朱描边),不另设进度条组件(战斗屏 A 区进度条随批退役);③ P0 闭环两条、依赖表 HUD 框架行、验收标准「行进自主」条同步。**批内代拍板三项待追认**:① B/C/D 字母保持不重编;② 28px 纵向空间划归 C/D 底行;③ 关卡画面进度呈现由选路覆盖层节点态承载(登记面见 HUD框架 v0.8) | diff --git a/design/gdd/systems/冒险循环/冒险结构.md b/design/gdd/systems/冒险循环/冒险结构.md index 0da4a2df..fac57c46 100644 --- a/design/gdd/systems/冒险循环/冒险结构.md +++ b/design/gdd/systems/冒险循环/冒险结构.md @@ -219,7 +219,7 @@ GP(显示名「金币」)是冒险内的一次性属性提升货币:战斗 ## P0 最小闭环 1. 待机中开始新冒险:注入新主种子(恰一次)、生命账本置满、序列位置 = 第 1 关。 -2. 开始关卡:把关卡序号、当前生命、生效上限下放关卡节点地图,后者开始节点链行进。 +2. 开始关卡:把关卡序号、当前生命、生效上限下放关卡节点地图,后者加载节点图并停在入口选路停点(关卡画面),行进自玩家首次选路开始。 3. 每场战斗结束:战斗结果上报到达,账本回写生命终值;上报为 0(倒下)直接走失败结算。 4. 恢复点节点:治疗封顶回报到达,按比例回血并封顶。 5. 关底 Boss 被击杀 = 收束点:关卡节点地图先调战斗结算清库,再向本文宣布关卡结束(通关)。 @@ -285,3 +285,4 @@ GP(显示名「金币」)是冒险内的一次性属性提升货币:战斗 | v0.6 | 2026-09-21 | **冒险结构实现批落地登记(切片 AdventureRunner + LevelRunner 账本外置 + AdventureTable 建表——与切片代码 / registry / 03 / 06 / roadmap 同批)**:本批只记实现批状态,正文规则口径零改动。落地面 = 三关正式序列生效(首页「开始冒险」)、接口契约四件套正式侧承载(开始关卡 start_level_external / 战斗结果上报共享账本直写 / 治疗封顶回报 apply_rest / 关卡结束 level_finished)、生命账本与 GP 冒险账本上交冒险结构层、大奖励非末关恰兑付一次(末关钥石照记)、冒险摘要五件事产出、主种子恰一次注入(registry rng_service new_adventure 调用权同批登记)、结算受理只算一次;06 待办「两关序列未生效」「切片行进代码与定稿的接口漂移」「冒险表未建表」三条随批解决移除。**规划书冲突按代码为准登记两处**:①大奖励 `_pending_next_level` 置位先于 emit(信号同步派发——处理器内立即确认需标记已就位);②放弃受理补 state=running 守卫(契约 7 结算受理只算一次的 abandoned 侧对齐) | | v0.7 | 2026-09-21 | **放弃入口界面呈现批(用户拍板「放弃接口也要有」——台账 119,推翻本文原「MVP 无界面入口、呈现后置」条款)**:① 放弃入口挂[关卡节点地图](关卡节点地图.md)选路覆盖层(选路停点 = 天然非战斗期;战斗中拒绝转为防御兜底分支——入口在战斗期不可达);② 入口只在冒险模式开放,单关测试直入口没有可放弃的冒险对象;③ 正文改现行口径(放弃节 / 验收标准放弃条),P1 后置表「放弃入口的界面呈现」随落地移除(同表「两关序列生效」系 v0.6 已落地漏收口,顺手同口径移除);④ 验收闸 = ui_probe 三断言(默认隐藏 / 冒险模式可见 / 首战中按下被拒 state 保持 running)+ 冒烟既有放弃两态 API 断言 | | v0.8 | 2026-09-23 | **单关测试直入口退役(用户拍板——唯一试玩入口 = 完整三关冒险)**:正文改现行口径两处(放弃节「按钮只在冒险模式开放」的理由从「单关直入口无冒险对象」改为「选路覆盖层只在冒险模式出现」;验收标准放弃条同步去单关直入口从句)——放弃语义对象(整局冒险)与拒绝分支零改动。版本历史行(v0.7)按 ADR 纪律不回改 | +| v0.9 | 2026-09-23 | **停点前移批流程口径对齐(用户 2026-09-23 拍板冒险入口流程倒置修复——与 HUD框架 / 关卡节点地图 / battle.md / `docs/architecture/adr-0006-board-node2d-rendering.md` 同批)**:P0 闭环「开始关卡」条改新流程口径——关卡节点地图加载节点图后停在入口选路停点(关卡画面,选路覆盖层常驻形态),行进自玩家首次选路开始;通读复核其余章节——「通关自动进下一关」属关卡序列层语义(进入下一关后仍出图选路,零矛盾)、接口契约四件套、收束点先清库后宣布、放弃入口(选路停点 = 天然非战斗期)等既有裁决零改动。**批内代拍板三项待追认**(字母不重编 / 28px 归 C/D 底行 / 进度呈现由选路覆盖层节点态承载)不落本文修订面,登记见 HUD框架 v0.8 | diff --git a/design/gdd/systems/表现与支撑/HUD框架.md b/design/gdd/systems/表现与支撑/HUD框架.md index d6a99dc3..396a0d18 100644 --- a/design/gdd/systems/表现与支撑/HUD框架.md +++ b/design/gdd/systems/表现与支撑/HUD框架.md @@ -18,7 +18,7 @@ HUD框架把各玩法系统产生的事实变成玩家看得见、点得动、 | 负责 | 说明 | |---|---| -| 战斗屏四区布局 | 四区系统级契约与常驻组件呈现形态;棋盘格尺寸定值档与装板校验 | +| 战斗屏三区布局 | 三区系统级契约与常驻组件呈现形态;棋盘格尺寸定值档与装板校验 | | 输入翻译 | 鼠标操作翻译成意图批次、一帧内整批提交;悬停预承诺与可挖域指示 | | 提示体系 | toast 轻提示与一次性提示;拒绝反馈的呈现映射 | | 模态覆盖层家族 | 图例面板、明细回看、胜负终局共用的层级纪律 | @@ -50,20 +50,19 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻 本章六节:前三节定画面的骨架与进出规则(屏、弹层、刷新),后三节定数据与资产怎么进来(载荷、占位、底线)。 -### 战斗屏四区双舞台:挖掘与结算同屏不切场景 +### 战斗屏三区双舞台:挖掘与结算同屏不切场景 战斗屏是 MVP 唯一的玩法屏,采用"双舞台"布局——战斗画面与扫雷图两块区域同屏常驻、互不顶替:挖掘期敌人在上方待机可见,轮末结算在原地逐行展开,全程不切换场景。"扫雷的因"与"战斗的果"因此永远摆在一起,因果不需要玩家凭记忆拼接。 -四区各管一块: +三区各管一块: | 区 | 位置与尺寸 | 管什么 | |---|---|---| -| A 关卡进度条 | 全宽 28px 顶栏 | 节点链步进与当前位置 | | B 战斗画面 | 全宽 252px | 主角站桩立绘、敌人站桩立绘 ×1–3(**全站桩形态,2026-09-20 用户拍板:无框立绘+底座、状态小图标挂血条下,英雄卡/敌人卡退役**)、轮次阶段指示、结算明细带 | -| C 角色信息 | 左下 240×520 | 生命 / 攻击 / 储备数据框、状态效果槽、小人立绘 | -| D 扫雷图 | 中下约 1036×520(原右下面板删除后横向加宽) | 行动点条与按钮组、棋盘本体、toast 槽 | +| C 角色信息 | 左下 240×548 | 生命 / 攻击 / 储备数据框、状态效果槽、小人立绘 | +| D 扫雷图 | 中下约 1036×548(原右下面板删除后横向加宽) | 行动点条与按钮组、棋盘本体、toast 槽 | -分区尺寸与组件落位的像素级裁决归 [battle.md](../../../ux/battle.md);四区结构与双舞台常驻是结构冻结项,修订走文档修订而非调参。 +分区尺寸与组件落位的像素级裁决归 [battle.md](../../../ux/battle.md);三区结构与双舞台常驻是结构冻结项,修订走文档修订而非调参。节点链进度不在战斗屏呈现——它由关卡画面(选路覆盖层常驻形态)的节点态承载,见[关卡节点地图](../冒险循环/关卡节点地图.md)。 **结论**:棋盘格边长取两档定值——12×12 的板用 36px、10×10 的板用 45px([battle.md](../../../ux/battle.md) 拍板值);下式不推导这两档,只在装板时校验"定值塞得进扫雷图区",违例立即报错中止。 @@ -78,10 +77,10 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻 |---|---|---| | N | 棋盘边长格数 | 10 或 12([棋盘生成](../排雷循环/棋盘生成.md)的两档尺寸) | | 定值档 | 12×12 → 36px;10×10 → 45px | 结构冻结 | -| 棋盘区宽 / 高 | 扫雷图区基准尺寸 | 1280×800 下 1036 / 476(520 减行动点条 44) | +| 棋盘区宽 / 高 | 扫雷图区基准尺寸 | 1280×800 下 1036 / 504(548 减行动点条 44) | | 边距 | 水平 / 垂直 | 各 16px | -1280×800 下实算:可用高度 460px,12×12 得 38 ≥ 36、10×10 得 46 ≥ 45,两档都通过。宽度向同理余量充裕(1036−16 远大于 12 格定值档所需)。棋盘数字直接显示排雷玩法给的真值——呈现层禁止自己算数字,这是"数据层从不说谎"在呈现侧的镜像。 +1280×800 下实算:可用高度 488px,12×12 得 40 ≥ 36、10×10 得 48 ≥ 45,两档都通过。宽度向同理余量充裕(1036−16 远大于 12 格定值档所需)。棋盘数字直接显示排雷玩法给的真值——呈现层禁止自己算数字,这是"数据层从不说谎"在呈现侧的镜像。 ### 提示与弹层:轻提示随起随消、重弹层排队进出 @@ -158,7 +157,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻 | 上游 | [增益状态](../战斗循环/增益状态.md) | 状态投影经战斗结算呈现面取得,零直读 | | 上游 | [地图战斗资源](../战斗循环/地图战斗资源.md) | 已开格奖励身份投影(`query_identity` 只读回调,组合根注入)零直读 | | 上游 | [关卡节点地图](../冒险循环/关卡节点地图.md) | 节点链、当前节点、跨战斗生命携带的读数;胜利后推进 | -| 下游 | [战斗表现](../战斗循环/战斗表现.md) | 交付四区布局、明细带呈现位、敌人呈现语义(站桩形态);它只叠加动画密度与演出 | +| 下游 | [战斗表现](../战斗循环/战斗表现.md) | 交付三区布局、明细带呈现位、敌人呈现语义(站桩形态);它只叠加动画密度与演出 | | 下游 | [音频](音频.md) | 挂点事件源(明细行类型流、toast 触发);MVP 无音频 | | 下游 | [教学引导](教学引导.md) | toast 与图例面板两个承载位——教学内容装进这两个容器 | | 下游 | [设置与本地化](设置与本地化.md) | 中文字体随包、按钮宽度、术语表等契约的消费方 | @@ -170,7 +169,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻 | [排雷玩法](../排雷循环/排雷玩法.md) | 已开 / 未开格态一眼可辨;悬停预承诺与可挖域常驻指示;移动路径预览即合法性指示;每次拒绝可感知且棋盘面细因可区分;停手被改判可感知 | | [行动经济](../排雷循环/行动经济.md) | 行动点与储备常显;储备清零反馈;棋盘面与资源面拒绝可区分;刷新入口与代价可读 | | [战斗结算](../战斗循环/战斗结算.md) | 未生效类明细行(奖励未生效)的呈现;高光行强调 | -| [关卡节点地图](../冒险循环/关卡节点地图.md) | 节点链进度常显(A 区顶栏:当前节点奖励金高亮、已过暗金、Boss 旗朱描边、候选下一跳亮金);选路覆盖层只读呈现(零自动推进停点语义) | +| [关卡节点地图](../冒险循环/关卡节点地图.md) | 节点链进度呈现(关卡画面选路覆盖层节点态承载:暗金已过、金当前、候选下一跳通道亮金、Boss 旗朱描边);选路覆盖层只读呈现(零自动推进停点语义) | | [敌人系统](../战斗循环/敌人系统.md) | 意图线索映射成文且全局恒定;单位顺位与血条可读;敌方共享层数各卡同值 | | [增益状态](../战斗循环/增益状态.md) | 状态栏常显;无状态时整排槽隐藏;有利 / 有害极性呈现不看数值符号 | | [地图战斗资源](../战斗循环/地图战斗资源.md) | 已开格的奖励身份常读 | @@ -179,7 +178,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻 ## P0 最小闭环 -1. 进入战斗屏:四区同屏就绪,半秒内首格可点。 +1. 进入战斗屏:三区同屏就绪,半秒内首格可点。 2. 挖掘期鼠标操作经单意图帧整批提交(唯一写路径,经组合根)。 3. 回执链:组合根重查只读面,界面字段级就地刷新。 4. 轮末进入结算:棋盘区锁输入,明细带展开逐行兑现(战斗结算流水驱动)。 @@ -213,13 +212,13 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻 | reduce_motion_scale | 每段动画 | 0.5 或跳终态 | 减动效开关系数 | | detail_band_height | 结算明细带 | 24 / 84 px | 摘要 / 实时流两态高度 | -结构冻结(非调参项,改动走文档修订):四区布局与分区尺寸、双舞台常驻、单意图帧纪律、弹层层级纪律、图标键映射契约、程序化占位策略、订阅纪律。性能预算为引用值:同屏 draw calls < 200、进入战斗屏 500ms 内可交互。 +结构冻结(非调参项,改动走文档修订):三区布局与分区尺寸、双舞台常驻、单意图帧纪律、弹层层级纪律、图标键映射契约、程序化占位策略、订阅纪律。性能预算为引用值:同屏 draw calls < 200、进入战斗屏 500ms 内可交互。 ## 验收标准 以下判据逐条可测,全部通过才算闭环达标。 -- 四区无遮挡、无重叠、无内容裁切(棋盘全部格子与数字完整可见),分区尺寸与 battle.md 布局表一致;同屏 draw calls < 200;进入战斗屏 500ms 内首格可点。 +- 三区无遮挡、无重叠、无内容裁切(棋盘全部格子与数字完整可见),分区尺寸与 battle.md 布局表一致;同屏 draw calls < 200;进入战斗屏 500ms 内首格可点。 - 两档板装板后格边长取定值档(36 / 45px)且可行性校验通过;数字字号不低于下限、整数倍缩放无模糊。 - 已开 / 未开格态在灰度截图下可逐格分类;已引爆雷格"可通行可读"(已开底加巢穴纹、可作移动目标)。 - 悬停光标三语义(移动 / 挖 / 旗)随目标切换并高亮目标格;悬停已开格画出合法路径(不可达不显示);可挖域常驻呼吸微光。 @@ -251,3 +250,4 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻 | v0.5 | 2026-09-17 | 数值纪律收口批:调参表标注文档自持口径(无表承载常量的唯一事实源声明) | | v0.6 | 2026-09-20 | **敌人卡形态裁决收口批(与 battle.md / 战斗表现 / 06 清单同批,台账 99)**:全站桩形态拍板——B 区主角与敌人均为无框立绘+底座、状态小图标挂血条正下方,英雄卡/敌人卡退役;B 区行、敌人呈现义务行(载体由卡改立绘,意图占位/共享层数各立绘同值/死亡灰化退场等义务面平移)、下游战斗表现交付行、就地更新示例措辞四处同步;攻击值等规划数据仍归 C 区数据框(C 区不动)。呈现快照数据面零改动(消费字段不变,只换呈现载体) | | v0.7 | 2026-09-20 | **表现层三批落地 + 义务清单补行(代码同日三批:样式收敛 / 站桩改造 / 结算演出链)**:呈现义务来源清单补「关卡节点地图」行(节点链进度常显 + 选路覆盖层只读呈现——此前漏登记);新增结算演出覆盖层(跨区弹道/光带载体,兑现演出因果可视);减动效承诺落地(UiKit.motion 单点包装全部呈现层 tween 时长,开启时长减半、可挖域呼吸停格;切片期入口 = TunePanel 开关,正式设置层归设置批)。代码侧站桩载体(StandeeView)与卡片退役随 v0.6 裁决执行 | +| v0.8 | 2026-09-23 | **停点前移批(冒险入口流程倒置修复,用户 2026-09-23 拍板;上位裁决 `docs/architecture/adr-0006-board-node2d-rendering.md` 与 `docs/architecture/board-node2d-migration-plan.md` §1,代码实装另批并行)**:① 战斗屏布局四区 → 三区——删 A 区关卡进度条行(LevelStripView 退役删除,节点链进度呈现移出战斗屏、只留关卡画面);C/D 区纵向 520 → 548;装板校验变量与实算同步(棋盘区高 504、可用高度 488);② 呈现义务来源清单「关卡节点地图」行改承载口径——节点链进度由关卡画面选路覆盖层节点态承载(暗金已过 / 金当前 / 候选下一跳通道亮金 / Boss 旗朱描边),不另设进度条组件;③ 「四区」措辞全线改「三区」(负责表、节题、结构冻结、验收、下游战斗表现交付行),双舞台常驻语义不动。**批内代拍板三项待追认**:① B/C/D 字母保持不重编(字母是跨文档/代码注释的承载标识);② A 区 28px 纵向空间划归 C/D 底行(520 → 548;1920 档换算仍归表现层升级批);③ 关卡画面进度呈现由选路覆盖层节点态直接承载、不另设进度条组件 | diff --git a/design/ux/battle.md b/design/ux/battle.md index 7bba5c18..142e1f27 100644 --- a/design/ux/battle.md +++ b/design/ux/battle.md @@ -2,7 +2,7 @@ > **Status**: Complete — /ux-review 修订落稿(2026-09-12,blocking 7 项已修,待复检) > **Author**: user + ux-designer -> **Last Updated**: 2026-09-20(敌人卡形态裁决收口:全站桩形态拍板——主角与敌人均无框立绘+底座、状态小图标挂血条下、卡片式退役;B 区行/组件表/线框图/OQ4 同步改写——见文末修订记录) +> **Last Updated**: 2026-09-23(停点前移批:A 区关卡进度条退役、布局四区→三区、C/D 底行 548、关卡画面为选路常驻枢纽——见文末修订记录) > **Platform Target**: PC (Steam)——Keyboard/Mouse 主(全部操作纯鼠标可完成)、Gamepad Partial 非 MVP、Touch None(同 technical-preferences) > **上游修订依赖**: 弃图刷新挖掘期受理已随布局决策 4 落稿上游(2026-09-12:行动经济 Active 受理 + 每轮上限 2 次、排雷玩法 Active·仅刷新路径装板、战斗结算 `refresh_board` 编排 + 恒 same_board、registry `rap_rules` 同步——见 OQ11) > **Template**: UX Spec @@ -28,7 +28,7 @@ ## Player Context on Arrival - **首次到达**:从关卡首节点(战×2 的第一战)进入。MVP 无引导流,chord 肌肉记忆提示等非模态提示承担首次教学(见 §States) -- **到达前刚在做**:关卡节点图上点击战斗节点(LevelRunner 推进);或上一场战斗胜利后点下一节点 +- **到达前刚在做**:关卡画面(选路覆盖层常驻形态)上点击战斗节点(LevelRunner 推进);或上一场战斗胜利后返回关卡画面再选下一节点 - **情绪状态**:棋手式清明专注(art-bible §2.1,能量 2/10)——挖掘期画面必须无杂音;结算期转为清算爽感(7/10) - **到达方式**:游戏推进送入(非自愿导航),进入即处于挖掘阶段(棋盘已 install、初始 3×3 已展开、小人出生) @@ -39,13 +39,13 @@ ## Navigation Position ``` -存档 → 冒险 → 关卡(节点图,关卡节点地图 lite) → 【战斗屏】 - ├─ 胜利 → 节点奖励/恢复覆盖层 → 下一节点(下一场战斗屏) +存档 → 冒险 → 关卡(关卡画面 = 节点图选路常驻枢纽) → 【战斗屏】 + ├─ 胜利 → 节点奖励/恢复覆盖层 → 返回关卡画面选下一节点(→ 下一场战斗屏) ├─ 胜利(末节点 Boss)→ 关卡结算(level_clear,清库反馈义务载体 = 魔法道具背包,随魔法道具 P0 落地)→ 下关 └─ 失败 → 冒险终结 → 返回存档/营地 ``` -战斗屏是**上下文相关屏**(仅由战斗节点进入),非顶层常驻。关卡内节点进度常驻屏顶(A 区),玩家始终知道自己身处图中第几环。 +战斗屏是**上下文相关屏**(仅由战斗节点进入),非顶层常驻。关卡内节点进度常驻关卡画面(选路覆盖层的常驻形态,节点态即进度呈现:已过暗金、当前金、候选通道亮金、Boss 旗朱描边),战斗屏零进度承载——战斗结束返回关卡画面,玩家始终知道自己身处图中第几环。 - 上游屏:关卡节点图(分支节点图形态,切片第一关为单链特例) - 下游屏:节点奖励/恢复覆盖层(NodeOverlay,已有 demo 组件——建议后续单独出 spec `design/ux/node-reward.md`) @@ -60,7 +60,7 @@ | Entry Source | Trigger | Player carries this context | |---|---|---| | 关卡战斗节点 | 点击节点(LevelRunner 推进 encounter) | 携带 HP(跨战斗延续)、RAP=0(战斗域新开) | -| 上一场战斗胜利 → 下一节点 | 同上 | 同上 | +| 上一场战斗胜利 → 返回关卡画面 → 选下一节点 | 同上 | 同上 | **离开**: @@ -83,7 +83,7 @@ 2. **本轮行动资源**:AP 余量(决定还能挖几格)、停手/弃图刷新按钮——紧贴棋盘上缘(动作发生地) 3. **战果兑现面**(结算期为第一注视点):敌人血条变化、逐行结算明细、高光(完美排雷/标中旗) 4. **规划面**(边缘注视,常读):HP、攻击、RAP 池、状态效果(己方/敌方)、敌人意图徽标与顺位 -5. **进度语境**:轮次/阶段指示、关卡节点进度、图例入口 +5. **进度语境**:轮次/阶段指示、图例入口(关卡节点进度常驻关卡画面,战斗屏不承载——见 Navigation Position) 依据:挖掘期玩家视线锚定棋盘(art-bible §2.1"视线始终被数字牵引");结算期视线转向战斗画面区(§2.2"兑现光带逐行横扫")。规划信息必须常读但不得抢焦——这正是用户布局图三分下栏的合理性:**动作区居中,规划区两翼**。 @@ -93,10 +93,11 @@ | 区 | 位置与尺寸 | 内容 | |---|---|---| -| **A 关卡进度条** | 全宽 × 28px 顶栏 | 节点图步进(战 → 战 → 赏 → 战 → 战 → 疗 → 王——文字签槽,对应 battle/reward/rest/boss 四型节点),当前节点奖励金高亮、已过暗金、Boss 旗朱描边 | | **B 战斗画面** | 全宽 × 252px | 主角站桩立绘(左)+ 兑现演出区(中)+ 敌人站桩立绘 ×1–3(右)——**全站桩形态(2026-09-20 用户拍板,敌人卡形态待办裁决收口):主角与敌人均为无框立绘+底座,零卡片**;底部结算明细带(24px 摘要 / 结算期 84px 实时流);顶部中央轮次·阶段指示 | -| **C 角色信息** | 左下 240px × 520px | HP 条、攻击值(含修正显示)、RAP 池、状态效果槽(固定 4 槽)、小人立绘占位 | -| **D 扫雷图** | 中下 ~1036px × 520px(原右下面板删除后横向加宽,吸收其宽度) | 顶部 AP 条(44px:左——AP 点阵 + 停手按钮;右——图例按钮 + 弃图刷新按钮〔雷图右上角,2026-09-12 拍板〕);棋盘主体(居中,格 36–45px 见方);toast 槽(上缘内侧) | +| **C 角色信息** | 左下 240px × 548px | HP 条、攻击值(含修正显示)、RAP 池、状态效果槽(固定 4 槽)、小人立绘占位 | +| **D 扫雷图** | 中下 ~1036px × 548px(原右下面板删除后横向加宽,吸收其宽度) | 顶部 AP 条(44px:左——AP 点阵 + 停手按钮;右——图例按钮 + 弃图刷新按钮〔雷图右上角,2026-09-12 拍板〕);棋盘主体(居中,格 36–45px 见方);toast 槽(上缘内侧) | + +> 布局为 B/C/D 三区——关卡进度条不在战斗屏(节点链进度常驻关卡画面,见 Navigation Position;字母不重编沿用 B/C/D)。 **布局决策 1(本 spec 最大决策,用户拍板)**:**常驻双舞台分屏**——战斗画面与扫雷图同屏常驻,挖掘期敌人待机可见,结算期不切场景。 - 备选(已否决):Peglin 式"结算时才切入独立战斗场景"——切换打断推理心流,且资产管线成本翻倍 @@ -109,7 +110,6 @@ | 区 | 组件 | 类型 | 内容 | 交互 | 现有 demo 组件 | |---|---|---|---|---|---| -| A | LevelStripView | 步进条 | 节点图 + 当前位置 | 只读(点击节点不可跳) | ✅ level_strip_view.gd 迁移 | | B | 主角站桩立绘 | 站桩(无框立绘+底座) | HP 条悬浮头顶、状态效果小图标一排挂血条正下方(形状+色相双通道、层数角标同 C 区状态槽键);攻击值等规划数据仍归 C 区数据框(2026-09-20 全站桩拍板,英雄卡退役) | 只读 | HUDView 拆分改造 | | B | 敌人站桩立绘 ×1–3 | 站桩(无框立绘+底座) | 血条+顺位徽标悬浮头顶、状态小图标挂血条正下方(敌方 side-wide 增益各立绘同值呈现——蓄力/硬化/削弱/暴走/警觉;伏击层数通道随 2026-09-20 八型批退役——数据源 = 战斗结算流水经增益状态投影,敌人系统×4④;2026-09-16 敌人技能池批改型:行为环点拆除〔proposal §2.4〕,意图占位 = 本拍摇定结果的动画占位——普通攻击零表示 / 特殊技类型级提示,敌方增益〔蓄力/硬化〕入状态图标行);死亡灰化加碎裂退场直接演在立绘上(2026-09-20 全站桩拍板,敌人卡退役) | 只读 | 新建 | | B | 轮次·阶段指示 | 标签 | "第 N 轮 · 挖掘中/结算中"(终局定格「战斗结束」;Boss 变体附 Boss 名) | 只读 | 新建 | @@ -149,13 +149,12 @@ ### ASCII Wireframe +> **2026-09-23 停点前移批**:A 区关卡进度条退役,下方线框图为 B/C/D 三区(28px 纵向空间划归 C/D 底行 520 → 548;关卡内节点进度常驻关卡画面,战斗屏零进度承载);旧四区线框留档于 git 历史。 > **2026-09-20 全站桩形态改版**:敌人卡形态裁决收口(卡片式退役、主角与敌人均站桩——无框立绘+底座、状态小图标挂血条下),下方线框图 B 区已随裁决重绘;旧卡片式线框留档于 git 历史。 ``` 1280 × 800 ──────────────────────────────────────────────────────────────────── -┌─[A] 关卡进度条 28px ─────────────────────────────────────────────────────────┐ -│ 战→战→赏→战→战→疗→王 (当前节点奖励金高亮,已过节点暗金) │ -├─[B] 战斗画面 252px ──────────────────────────────────────────────────────────┤ +┌─[B] 战斗画面 252px ──────────────────────────────────────────────────────────┐ │ HP▓▓▓▓░ ①HP▓▓▓░ ②HP▓▓░░ │ │ ⬆2 🛡1 [⚔特殊技] (普攻·零表示) │ │ /小人\ 第 3 轮 · 挖掘中 伏×2 伏×2 │ @@ -165,7 +164,7 @@ │ │ ▸上轮: 主角攻击3→蛆虫-3 | 攻击+50%(1结算) | 警觉+1×2 … [展开明细▾] │ │ # 2026-09-17 数值压缩批:UI 示意文本同步(atk 7→3〔地精格数÷3 + L1 锚行;UX 示意按当前地精行值读〕、伏击 +3 → +1/层,幅度参考 data/tables/) │ └────────────────────────────────────────────────────────────────────────┘ │ ├─[C] 角色信息 240px ───┬─[D] 扫雷图 ~1036px ──────────────────────────────────┤ -│ 520px │ 520px │ +│ 548px │ 548px │ │ ┌──────────────────┐ │ ┌─────────────────────────────────────────────────┐ │ │ │ HP 42/50 ▓▓▓░ │ │ │ AP ●●●●●○○○ 5/8 │ │ │ │ 攻击 6 (+50%) │ │ │ [⏹停手] [?图例] [↻刷新 8|剩2] │ │ @@ -281,7 +280,7 @@ | 敌单位 hp/顺位/摇定动作(pending_skill/type/name)/敌方增益/enemy_id/存活 | 敌人系统 只读面(经战斗结算呈现快照) | Read | 意图线索映射全局恒定(敌人系统×4②,技能池批三档口径);**despawn 前捕获**(旧版台账 entry 17③——缺陷②教训) | | 状态效果投影(含空态/极性/溢出) | 增益状态 投影口径 | Read | 极性映射数据源 = 战斗结算 SettlementTable 效果语义,非 magnitude 符号(增益状态×③) | | 已开格奖励身份(奖励徽章) | 地图战斗资源 `query_identity`(经 board_view 身份解析只读回调,组合根注入) | Read | 已开格身份常读——挖掘期 Active 相位内有效;reward_id 全局恒定、禁止重掷;徽章绘制优先级见 §Component Inventory BoardView 行 | -| 节点图/当前节点/HP 携带 | 关卡节点地图 lite · LevelRunner | Read | A 区进度条 | +| 节点图/当前节点/HP 携带 | 关卡节点地图 lite · LevelRunner | Read | 关卡画面选路覆盖层节点态呈现(战斗屏零进度承载) | | 意图提交 | 排雷玩法 | **Write** | `settle_batch`(stop 亦经此——不直调行动经济) | | 弃图刷新 | 行动经济 | **Write** | `request_refresh`(挖掘期 Active 当场受理——布局决策 4 2026-09-12 上游已落稿;战斗结算 `refresh_board` 编排承载) | | 节点推进 | LevelRunner | **Write** | 胜利 overlay「下一节点」 | @@ -321,7 +320,7 @@ **布局与可读** -- [ ] 1280×800 与 16:9 全屏(canvas_items/expand)下四区无遮挡、无重叠、无内容裁切(棋盘 12×12 全部格子 + 数字完整可见) +- [ ] 1280×800 与 16:9 全屏(canvas_items/expand)下三区(B/C/D)无遮挡、无重叠、无内容裁切(棋盘 12×12 全部格子 + 数字完整可见) - [ ] 进入战斗屏 ≤500ms 内可交互(棋盘 install 淡入完成后首格可点);同屏 draw calls < 200(technical-preferences 预算) - [ ] 两种格态(已开格 / 未开格)灰度截图下一眼可辨(排雷玩法×6②) - [ ] 悬停光标区分移动/挖/旗三语义;可挖域(8 向相邻)常驻指示;非相邻格悬停显禁止反馈(排雷玩法×6③) @@ -404,3 +403,4 @@ | 2026-09-15 | 雾格残留清理:格态收窄为已开 / 未开两态(删除雾图变体状态行、四格态 AC 与已开雾格 tint / 雾区全开信号两条 AC);图例面板与 toast 槽去掉雾条目与雾首遇提示;BoardView 纹带、绘制优先级与色弱冗余改按通用变异纹带表述;`ux_hint_shown` 的 hint_id 枚举去掉 fog_first;输入义务与 §13 义务对照表去掉 地形变异×3 一项;目标感知示例与提示文案 OQ 措辞同步。本 spec 不装载被移除机制的呈现义务 | | 2026-09-16 | 格面资源批 UI 欠账收口(随 2026-09-15 可选类道具删除的代码收口同步文档):① 布局四区化——删 E 区道具面板行、布局决策 2(道具窗口形态=右栏原位激活)整段删除(编号留空防交叉引用失锚)、ASCII 线框图重绘为 A/B/C/D 四区(D 区横向加宽 ~1036px)、A 区节点链示例改文字签槽实际渲染(战/赏/疗/王);② States 删道具窗口态与零库存空态(「道包空空」);③ 结算口径五段化(授予→增益/减益→战斗→衰减→判定)与明细行 10 类封闭协议(删 15 类行、四类行旧口径;「奖励未生效」行保留);④ 交互/动效/事件表删道具窗口行、跳过按钮、`ux_window_choice`、`submit_window_selection`;⑤ 数据源登记删 `query_inventory` 与窗口载荷,奖励身份按现行 `query_identity` 只读回调口径校正;⑥ 验收清单删窗口断言、五区改四区、未生效类行改现行两型(溢出未生效/奖励未生效);⑦ 关卡末清库反馈义务改载魔法道具背包(随魔法道具 P0 落地,本批不设计具体呈现);⑧ OQ7(道具窗口原位激活)删除、OQ 重编号,「囤刀待机」等旧可选类措辞清理。本 spec 不装载被删除机制的呈现义务 | | 2026-09-20 | **敌人卡形态裁决收口(2026-09-20 用户看对照样张拍板,06 待办「敌人卡形态」解决移除——台账 99)**:全站桩形态定稿——① B 区主角与敌人均为无框立绘+底座(站桩式),**英雄卡/敌人卡组件整体退役,卡片式不再作为候选**;主角血条悬浮头顶、状态效果小图标一排挂血条正下方,敌人血条+顺位徽标悬浮头顶、状态小图标同款挂血条下;攻击值等规划数据仍归 C 区数据框(C 区不动);② 布局表 B 区行、Component Inventory 英雄卡/敌人卡两行随裁决改写(敌方 side 池共享层数各立绘同值、意图动画占位等既有语义平移到站桩载体,不随形态改变);③ ASCII 线框图 B 区重绘(旧"意图:爪击/环点"示意早已随行为环退役,本次一并清掉);④ OQ4 改写为裁决已收口注记,对照样张存档 `production/mockups/`;呈现义务细节重述(受击/退场演出挂点等)随表现层升级批执行 | +| 2026-09-23 | **停点前移批(冒险入口流程倒置修复,用户 2026-09-23 拍板;上位裁决 `docs/architecture/adr-0006-board-node2d-rendering.md` 与 `docs/architecture/board-node2d-migration-plan.md` §1,代码实装另批并行)**:① 布局四区 → 三区——删布局表 A 区关卡进度条行与 Component Inventory LevelStripView 行(LevelStripView 退役删除,节点链进度只留关卡画面,战斗屏零进度承载);C/D 底行 520 → 548;ASCII 线框图重绘为 B/C/D 三区;② 新流程口径落文——关卡画面(选路覆盖层常驻形态)为关卡内常驻枢纽:Navigation Position 图、到达语境、Entry 表、信息层级第 5 层、Data Requirements 节点图行改「关卡开始即出图选路、战后返回关卡画面再选路」;③ AC 四区措辞改三区。**批内代拍板三项待追认**:① B/C/D 字母保持不重编;② 28px 纵向空间划归 C/D 底行(520 → 548);③ 关卡画面进度呈现由选路覆盖层节点态承载(暗金已过 / 金当前 / 亮金候选通道 / 旗朱 Boss)、不另设进度条组件 | diff --git a/prototypes/dungeon-sweeper-vertical-slice/core/adventure_runner.gd b/prototypes/dungeon-sweeper-vertical-slice/core/adventure_runner.gd index 49d629b4..0a82bd82 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/core/adventure_runner.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/core/adventure_runner.gd @@ -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 diff --git a/prototypes/dungeon-sweeper-vertical-slice/core/level_runner.gd b/prototypes/dungeon-sweeper-vertical-slice/core/level_runner.gd index 97f2eb7f..2dd5b120 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/core/level_runner.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/core/level_runner.gd @@ -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: diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/main.gd b/prototypes/dungeon-sweeper-vertical-slice/presentation/main.gd index 57c5f248..b8bc0204 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/main.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/presentation/main.gd @@ -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() diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/ui_assembler.gd b/prototypes/dungeon-sweeper-vertical-slice/presentation/ui_assembler.gd index 366253a3..38a65783 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/ui_assembler.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/presentation/ui_assembler.gd @@ -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, diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/board_zone_view.gd b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/board_zone_view.gd index 38c24f99..5dd8a3f8 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/board_zone_view.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/board_zone_view.gd @@ -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 # 高内聚边界:容器组合 + 信号转发——动作意图全部信号外发到组合根,零核心写调用。 diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/hero_info_panel_view.gd b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/hero_info_panel_view.gd index aa6379cb..0b023817 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/hero_info_panel_view.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/hero_info_panel_view.gd @@ -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) diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/level_strip_view.gd b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/level_strip_view.gd deleted file mode 100644 index 3cc518b5..00000000 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/level_strip_view.gd +++ /dev/null @@ -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 diff --git a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/level_strip_view.gd.uid b/prototypes/dungeon-sweeper-vertical-slice/presentation/views/level_strip_view.gd.uid deleted file mode 100644 index 626c051a..00000000 --- a/prototypes/dungeon-sweeper-vertical-slice/presentation/views/level_strip_view.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://mv5cxybk4mf0 diff --git a/prototypes/dungeon-sweeper-vertical-slice/smoke/level_autoplay.gd b/prototypes/dungeon-sweeper-vertical-slice/smoke/level_autoplay.gd index 6bd14add..509cd9c3 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/smoke/level_autoplay.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/smoke/level_autoplay.gd @@ -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 diff --git a/prototypes/dungeon-sweeper-vertical-slice/smoke/smoke_all.gd b/prototypes/dungeon-sweeper-vertical-slice/smoke/smoke_all.gd index 36322a49..57a1e4f6 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/smoke/smoke_all.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/smoke/smoke_all.gd @@ -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() diff --git a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_click_probe.gd b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_click_probe.gd index c4f5c487..4994f8d3 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_click_probe.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_click_probe.gd @@ -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 口径)另列观测 diff --git a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_probe.gd b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_probe.gd index 603a8655..c1e76fb0 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_probe.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_probe.gd @@ -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: diff --git a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_tune_probe.gd b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_tune_probe.gd index 17465696..c392b676 100644 --- a/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_tune_probe.gd +++ b/prototypes/dungeon-sweeper-vertical-slice/smoke/ui_tune_probe.gd @@ -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)" % [