merge: M-2 迭代 3「独立屏架构」loop——关卡画面升格独立屏(LevelMapScreen)+全链流程+独立性断言

四批次 30002f8/ae2ca4f/a055722/3dffbde:①T1 level-map 形态升格 spec 先行(模态覆盖层措辞全量退役=独立屏独占呈现零透出+屏底+淡入+独立性断言 AC;interaction-patterns pattern 6 退出注记+battle.md 三处随改)②T2 RouteOverlay→LevelMapScreen 改名+不透明屏底(BG_ROOT+石纹底噪 load_tex 守卫)+进入淡入 200ms+组合根 _battle_root/_level_screen_active 屏级互斥单一出口③T3 兑付覆盖层本屏域内弹出(_submit_choice 节点类型分支:奖励/恢复/商店本屏保持激活=模态栈遮挡恢复零改动复用;战斗/Boss 出屏;open_choices 幂等刷新)④T4 探针独立性断言(屏激活期宿主隐藏/战斗节点提交后恢复)。
验证:worktree+主区双跑冒烟 557/0+五探针 PASS+doc 三闸对基线零新增(lint 8=8/term 215=215/死链 0)+熵增闸 PASS。M-2 迭代 4 下一站=关卡画面样张+节点图标接线+视觉终稿。
This commit is contained in:
DKChan committed 2026-09-28 06:45:42 +08:00
commit 74c8ebe430
10 files changed
+114 -56

No files matched your search

+3 -3
View File
@@ -28,7 +28,7 @@
## Player Context on Arrival ## Player Context on Arrival
- **首次到达**:从关卡首节点(战×2 的第一战)进入。MVP 无引导流,chord 肌肉记忆提示等非模态提示承担首次教学(见 §States) - **首次到达**:从关卡首节点(战×2 的第一战)进入。MVP 无引导流,chord 肌肉记忆提示等非模态提示承担首次教学(见 §States)
- **到达前刚在做**:关卡画面(选路覆盖层常驻形态)上点击战斗节点(LevelRunner 推进);或上一场战斗胜利后返回关卡画面再选下一节点 - **到达前刚在做**:关卡画面(独立屏)上点击战斗节点(LevelRunner 推进);或上一场战斗胜利后返回关卡画面再选下一节点
- **情绪状态**:棋手式清明专注(art-bible §2.1,能量 2/10)——挖掘期画面必须无杂音;结算期转为清算爽感(7/10) - **情绪状态**:棋手式清明专注(art-bible §2.1,能量 2/10)——挖掘期画面必须无杂音;结算期转为清算爽感(7/10)
- **到达方式**:游戏推进送入(非自愿导航),进入即处于挖掘阶段(棋盘已 install、初始 3×3 已展开、小人出生) - **到达方式**:游戏推进送入(非自愿导航),进入即处于挖掘阶段(棋盘已 install、初始 3×3 已展开、小人出生)
@@ -45,7 +45,7 @@
└─ 失败 → 冒险终结 → 返回存档/营地 └─ 失败 → 冒险终结 → 返回存档/营地
``` ```
战斗屏是**上下文相关屏**(仅由战斗节点进入),非顶层常驻。关卡内节点进度常驻关卡画面(选路覆盖层的常驻形态,节点态即进度呈现:已过暗金、当前金、候选通道亮金、Boss 紫赤描边),战斗屏零进度承载——战斗结束返回关卡画面,玩家始终知道自己身处图中第几环。 战斗屏是**上下文相关屏**(仅由战斗节点进入),非顶层常驻。关卡内节点进度常驻关卡画面(独立屏的常驻枢纽形态,节点态即进度呈现:已过暗金、当前金、候选通道亮金、Boss 紫赤描边),战斗屏零进度承载——战斗结束返回关卡画面,玩家始终知道自己身处图中第几环。
- 上游屏:关卡画面(选路枢纽,屏级 spec:[level-map.md](level-map.md)——2026-09-27 建档;分支节点图形态,切片第一关为单链特例) - 上游屏:关卡画面(选路枢纽,屏级 spec:[level-map.md](level-map.md)——2026-09-27 建档;分支节点图形态,切片第一关为单链特例)
- 下游屏:关卡画面(选路枢纽,spec:[level-map.md](level-map.md))——战斗胜利后返回选路;落地奖励/恢复节点时由关卡画面域开节点覆盖层(NodeOverlay,恢复点全量服务、奖励点占位待内容定稿)确认兑付——节点奖励屏不立独立 spec,呈现义务随魔法道具内容重定批交付(2026-09-27 欠账处置拍板,06 待办挂该批名下) - 下游屏:关卡画面(选路枢纽,spec:[level-map.md](level-map.md))——战斗胜利后返回选路;落地奖励/恢复节点时由关卡画面域开节点覆盖层(NodeOverlay,恢复点全量服务、奖励点占位待内容定稿)确认兑付——节点奖励屏不立独立 spec,呈现义务随魔法道具内容重定批交付(2026-09-27 欠账处置拍板,06 待办挂该批名下)
@@ -311,7 +311,7 @@
| 敌单位 hp/顺位/摇定动作(pending_skill/type/name)/敌方增益/enemy_id/存活 | 敌人系统 只读面(经战斗结算呈现快照) | Read | 意图线索映射全局恒定(敌人系统×4②,技能池批三档口径);**despawn 前捕获**(旧版台账 entry 17③——缺陷②教训) | | 敌单位 hp/顺位/摇定动作(pending_skill/type/name)/敌方增益/enemy_id/存活 | 敌人系统 只读面(经战斗结算呈现快照) | Read | 意图线索映射全局恒定(敌人系统×4②,技能池批三档口径);**despawn 前捕获**(旧版台账 entry 17③——缺陷②教训) |
| 状态效果投影(含空态/极性/溢出) | 增益状态 投影口径 | Read | 极性映射数据源 = 战斗结算 SettlementTable 效果语义,非 magnitude 符号(增益状态×③) | | 状态效果投影(含空态/极性/溢出) | 增益状态 投影口径 | Read | 极性映射数据源 = 战斗结算 SettlementTable 效果语义,非 magnitude 符号(增益状态×③) |
| 已开格奖励身份(奖励徽章) | 地图战斗资源 `query_identity`(经 board_view 身份解析只读回调,组合根注入) | Read | 已开格身份常读——挖掘期 Active 相位内有效;reward_id 全局恒定、禁止重掷;徽章绘制优先级见 §Component Inventory BoardView 行 | | 已开格奖励身份(奖励徽章) | 地图战斗资源 `query_identity`(经 board_view 身份解析只读回调,组合根注入) | Read | 已开格身份常读——挖掘期 Active 相位内有效;reward_id 全局恒定、禁止重掷;徽章绘制优先级见 §Component Inventory BoardView 行 |
| 节点图/当前节点/HP 携带 | 关卡节点地图 lite · LevelRunner | Read | 关卡画面选路覆盖层节点态呈现(战斗屏零进度承载) | | 节点图/当前节点/HP 携带 | 关卡节点地图 lite · LevelRunner | Read | 关卡画面(独立屏)节点态呈现(战斗屏零进度承载) |
| 意图提交 | 排雷玩法 | **Write** | `settle_batch`(stop 亦经此——不直调行动经济) | | 意图提交 | 排雷玩法 | **Write** | `settle_batch`(stop 亦经此——不直调行动经济) |
| 弃图刷新 | 行动经济 | **Write** | `request_refresh`(挖掘期 Active 当场受理——布局决策 4 2026-09-12 上游已落稿;战斗结算 `refresh_board` 编排承载) | | 弃图刷新 | 行动经济 | **Write** | `request_refresh`(挖掘期 Active 当场受理——布局决策 4 2026-09-12 上游已落稿;战斗结算 `refresh_board` 编排承载) |
| 节点推进 | LevelRunner | **Write** | 胜利 overlay「下一节点」 | | 节点推进 | LevelRunner | **Write** | 胜利 overlay「下一节点」 |
+1 -1
View File
@@ -154,7 +154,7 @@ MVP 未使用标准控件(不入库):toggle / dropdown / input field / tab
- 验收:**场景级断言「打开时遮挡物隐藏/输入锁定」为每个模态 overlay 的强制 AC**(battle.md 场景级测试节) - 验收:**场景级断言「打开时遮挡物隐藏/输入锁定」为每个模态 overlay 的强制 AC**(battle.md 场景级测试节)
**When to Use**: 全屏打断级信息、只读回看、仪式性终局 **When to Use**: 全屏打断级信息、只读回看、仪式性终局
**When NOT to Use**: 与常读面板同域的窗口态(原 pattern 2 原位激活已随道具窗口退役——此类场景当前无消费面) **When NOT to Use**: 与常读面板同域的窗口态(原 pattern 2 原位激活已随道具窗口退役——此类场景当前无消费面);**独立屏**(2026-09-28 起:关卡画面形态升格为独立屏退出本 pattern 族——其屏级语义见 [level-map.md](level-map.md);节点/商店覆盖层在关卡画面激活域内弹出时仍属本 pattern,遮挡/恢复语义复用单一模态栈)
**Reference**: battle.md §AC 场景级测试;缺陷①⑤(production/session-state entry 17) **Reference**: battle.md §AC 场景级测试;缺陷①⑤(production/session-state entry 17)
+13 -12
View File
@@ -1,11 +1,11 @@
# UX Spec: 关卡画面(选路枢纽,Level Map) # UX Spec: 关卡画面(选路枢纽,Level Map)
> **Status**: 占位升格版——三项设计提案已拍板收口(2026-09-27:底栏状态展示 / 图上点击选路·候选按钮退役 / 放弃二次确认);视觉终稿以表现层样张为前置 > **Status**: 占位升格版——三项设计提案已拍板收口(2026-09-27:底栏状态展示 / 图上点击选路·候选按钮退役 / 放弃二次确认);**形态已升格独立屏(2026-09-28 用户裁决:独立存在为最终形态,覆盖层实现定性=测试阶段形态)**;视觉终稿以关卡画面样张为前置(M-2 迭代 4)
> **Author**: user + ZCode(关卡画面 UX 批) > **Author**: user + ZCode(关卡画面 UX 批)
> **Last Updated**: 2026-09-27 > **Last Updated**: 2026-09-28
> **Platform Target**: PC (Steam)——Keyboard/Mouse 主,全部操作纯鼠标可完成(同 [battle.md](battle.md)) > **Platform Target**: PC (Steam)——Keyboard/Mouse 主,全部操作纯鼠标可完成(同 [battle.md](battle.md))
> **Template**: UX Spec > **Template**: UX Spec
> **覆盖范围**: 选路覆盖层常驻形态(关卡停点全景)· 入口停点 · 岔路停点 · 单链停点 · 放弃冒险入口 > **覆盖范围**: 独立屏形态(关卡停点全景)· 入口停点 · 岔路停点 · 单链停点 · 放弃冒险入口
> **不覆盖**: 节点覆盖层(NodeOverlay——奖励/恢复兑付反馈,呈现义务随魔法道具内容重定批交付)、商店覆盖层、关卡结算屏([level-clear.md](level-clear.md))、战斗屏([battle.md](battle.md)) > **不覆盖**: 节点覆盖层(NodeOverlay——奖励/恢复兑付反馈,呈现义务随魔法道具内容重定批交付)、商店覆盖层、关卡结算屏([level-clear.md](level-clear.md))、战斗屏([battle.md](battle.md))
> **机制权威**: [关卡节点地图](../systems/冒险循环/关卡节点地图.md)(选路、兑付、停点语义;节点图逐局随机的来源另见[关卡节点图生成](../systems/冒险循环/关卡节点图生成.md)——只换图的来源,不换本屏的呈现语义);**系统契约**: [HUD框架](../systems/表现与支撑/HUD框架.md)(订阅纪律、模态层级、可访问性基线) > **机制权威**: [关卡节点地图](../systems/冒险循环/关卡节点地图.md)(选路、兑付、停点语义;节点图逐局随机的来源另见[关卡节点图生成](../systems/冒险循环/关卡节点图生成.md)——只换图的来源,不换本屏的呈现语义);**系统契约**: [HUD框架](../systems/表现与支撑/HUD框架.md)(订阅纪律、模态层级、可访问性基线)
@@ -28,7 +28,7 @@
- **到达时机**:关卡开始即出图(停点前移——入口不自动进入,第一次选路就是「出发」);或每次兑付确认后返回(战后回图、奖励/恢复反馈确认后回图、商店覆盖层离开后回图) - **到达时机**:关卡开始即出图(停点前移——入口不自动进入,第一次选路就是「出发」);或每次兑付确认后返回(战后回图、奖励/恢复反馈确认后回图、商店覆盖层离开后回图)
- **到达前刚在做**:上一场战斗的结算画面,或奖励/恢复/商店覆盖层的确认动作 - **到达前刚在做**:上一场战斗的结算画面,或奖励/恢复/商店覆盖层的确认动作
- **情绪状态**:谋定后动——[art-bible](../../art/art-bible.md) §2.7 下探过渡的「纵深的期待」(能量 4/10);画面应安静可读,不给选路决策添噪 - **情绪状态**:谋定后动——[art-bible](../../art/art-bible.md) §2.7 下探过渡的「纵深的期待」(能量 4/10);画面应安静可读,不给选路决策添噪
- **到达方式**:游戏推进送入(模态覆盖层打开,底层内容锁定) - **到达方式**:游戏推进送入(屏级切换——独立屏激活期战斗屏整体隐藏)
## Navigation Position ## Navigation Position
@@ -43,7 +43,7 @@
- 上游屏:关卡结算确认(下一关开始)或冒险开始 - 上游屏:关卡结算确认(下一关开始)或冒险开始
- 下游屏:战斗屏 / 节点覆盖层 / 商店覆盖层(三者兑付确认后**一律回本屏**再选路——战后回图) - 下游屏:战斗屏 / 节点覆盖层 / 商店覆盖层(三者兑付确认后**一律回本屏**再选路——战后回图)
- 本屏是模态覆盖层形态:打开时底层内容锁定(pattern 6),关闭即进入所选节点的兑付流程 - **本屏为独立屏**:激活期**独占呈现**——战斗屏宿主整体隐藏(非暗幕半透、零透出);选路提交即离开本屏进入所选节点的兑付流程(战斗/Boss 节点切回战斗屏;奖励/恢复/商店节点本屏保持激活,兑付覆盖层在本屏域内叠加弹出,确认后回本屏)
## Layout Specification ## Layout Specification
@@ -60,7 +60,7 @@
### Layout Zones ### Layout Zones
按 1280×800 基座(ADR-0002)。本屏为模态全屏接管:暗幕(现行 0.6 黑)铺满,内容居中三段。 按 1280×800 基座(ADR-0002)。本屏为独立屏独占呈现:屏底(深底 `BG_ROOT` + 石纹底噪〔M-1 框材组冻结件,缺位纯色回退〕)铺满,内容居中三段。
| 区 | 位置与尺寸 | 内容 | | 区 | 位置与尺寸 | 内容 |
|---|---|---| |---|---|---|
@@ -74,10 +74,10 @@
|---|---|---|---|---| |---|---|---|---|---|
| 关卡名标签 | 文本 | `level_row.display_name`(关卡表行读数) | 只读 | 新建(现行标题位升级) | | 关卡名标签 | 文本 | `level_row.display_name`(关卡表行读数) | 只读 | 新建(现行标题位升级) |
| GP 常驻徽章 | 直角细线数据框 | 金币图标 + 数字(`runner.gp`,冒险钱包读数;刷新经 `wallet_changed` 信号投影〔gain/buy/clear 三事件〕或写路径回执后重查) | 只读 | 新建(GP 去向拍板落地面) | | GP 常驻徽章 | 直角细线数据框 | 金币图标 + 数字(`runner.gp`,冒险钱包读数;刷新经 `wallet_changed` 信号投影〔gain/buy/clear 三事件〕或写路径回执后重查) | 只读 | 新建(GP 去向拍板落地面) |
| 节点图画布 | 自绘 Control | 节点方块(五类型字面 + 编号)、通道折线、节点态着色、Boss 紫赤描边(BOSS_PURPLE——2026-09-26 封印紫批语义:紫赤本色) | 悬停预承诺 / 点击提交选路(唯一通道) | ✅ route_overlay.gd `_draw_graph`(程序化占位) | | 节点图画布 | 自绘 Control | 节点方块(五类型字面 + 编号)、通道折线、节点态着色、Boss 紫赤描边(BOSS_PURPLE——2026-09-26 封印紫批语义:紫赤本色) | 悬停预承诺 / 点击提交选路(唯一通道) | ✅ level_map_screen.gd `_draw_graph`(程序化占位;2026-09-28 形态升格随屏改名) |
| 状态数据框组 | 直角细线数据框 ×3 | 生命(当前/上限 + 迷你血条)、属性(攻击 / 护甲 AC 有效值 + 加成角标)、本关加成(加算区增量列表,零加成隐藏) | 只读 | 新建(2026-09-27 拍板:底栏状态展示) | | 状态数据框组 | 直角细线数据框 ×3 | 生命(当前/上限 + 迷你血条)、属性(攻击 / 护甲 AC 有效值 + 加成角标)、本关加成(加算区增量列表,零加成隐藏) | 只读 | 新建(2026-09-27 拍板:底栏状态展示) |
| 放弃冒险按钮 | 按钮(危险红族) | 「放弃冒险(同失败结算——已获大奖励保留)」;二次确认态已拍板(见布局决策 3) | 点击(首击转确认态、二击提交) | ✅ route_overlay.gd(可见性归组合根,仅冒险模式开) | | 放弃冒险按钮 | 按钮(危险红族) | 「放弃冒险(同失败结算——已获大奖励保留)」;二次确认态已拍板(见布局决策 3) | 点击(首击转确认态、二击提交) | ✅ level_map_screen.gd(可见性归组合根,仅冒险模式开) |
| 标题行 | 文本 | 停点语境提示(见布局决策 1 三停点标题) | 只读 | ✅ route_overlay.gd `_title`(措辞待改) | | 标题行 | 文本 | 停点语境提示(见布局决策 1 三停点标题) | 只读 | ✅ level_map_screen.gd `_title`(措辞已按布局决策 1 改稿) |
**布局决策 1 —— 停点标题措辞修正(清偿台账 143 遗留)**:现行入口停点标题沿用「继续前进(战斗房)」,与「尚未走出第一步」的语境矛盾。三停点标题定稿: **布局决策 1 —— 停点标题措辞修正(清偿台账 143 遗留)**:现行入口停点标题沿用「继续前进(战斗房)」,与「尚未走出第一步」的语境矛盾。三停点标题定稿:
@@ -97,7 +97,7 @@
``` ```
1280 × 800 ──────────────────────────────────────────────────────────────────── 1280 × 800 ────────────────────────────────────────────────────────────────────
│ (暗幕 0.6——底层战斗屏锁定透出) │ │ (独立屏底——战斗屏整体隐藏,零透出) │
│ 第二层 · 幽暗地域 [◈ 128](GP 常驻徽章) │ │ 第二层 · 幽暗地域 [◈ 128](GP 常驻徽章) │
│ │ │ │
│ [战0]──┐ │ │ [战0]──┐ │
@@ -161,7 +161,7 @@
| 时刻 | 过渡 | 依据 | | 时刻 | 过渡 | 依据 |
|---|---|---| |---|---|---|
| 打开(进图 / 兑付确认后回图) | 暗幕 + 内容淡入(约 200ms,同装板淡入档) | pattern 6 模态纪律;底层锁定 | | 打开(进图 / 兑付确认后回图) | 独立屏淡入(约 200ms,同装板淡入档;alpha 淡入非明度变化——§2) | 屏级切换;战斗屏整体隐藏 |
| 选路提交 | 本屏关闭,直达所选节点兑付流程 | 到达即兑付(关卡节点地图) | | 选路提交 | 本屏关闭,直达所选节点兑付流程 | 到达即兑付(关卡节点地图) |
| 放弃确认态(OQ3 提案) | 危险红边框脉冲约 3 秒(单段呼吸,非频闪) | pattern 7;闪烁基线(≤3 次/秒)内 | | 放弃确认态(OQ3 提案) | 危险红边框脉冲约 3 秒(单段呼吸,非频闪) | pattern 7;闪烁基线(≤3 次/秒)内 |
| 减动效 | 上述全部时长减半或跳终态 | HUD框架 可访问性基线 | | 减动效 | 上述全部时长减半或跳终态 | HUD框架 可访问性基线 |
@@ -209,7 +209,7 @@
**模态与测试** **模态与测试**
- [ ] 模态纪律(pattern 6):打开时底层输入锁定;关闭逐层恢复——场景级断言 - [ ] **独立性断言**:本屏激活期战斗屏宿主整体隐藏(`visible=false` 级互斥,零透出);选路提交至战斗节点后战斗屏恢复;兑付覆盖层(节点/商店)关闭后本屏恢复——场景级断言
- [ ] 场景级 headless 测试:真实实例化本屏 + 驱动选路全程 + 断言零脚本错误(ui_probe 家族扩展) - [ ] 场景级 headless 测试:真实实例化本屏 + 驱动选路全程 + 断言零脚本错误(ui_probe 家族扩展)
- [ ] 中文渲染无豆腐块;像素字体整数倍缩放;全部操作纯鼠标可完成 - [ ] 中文渲染无豆腐块;像素字体整数倍缩放;全部操作纯鼠标可完成
@@ -241,5 +241,6 @@
| 日期 | 改动 | | 日期 | 改动 |
|---|---| |---|---|
| 2026-09-28 | **形态升格独立屏批(M-2 迭代 3 T1,用户裁决:独立存在为最终形态,覆盖层实现定性=测试阶段形态)**:Status/覆盖范围/到达方式/Navigation Position——「模态覆盖层形态(暗幕 0.6 底层透出)」全量改写为「独立屏独占呈现(战斗屏宿主整体隐藏,零透出)」;Layout 屏底=深底+石纹底噪(M-1 框材件);Transitions=独立屏淡入 200ms(alpha 淡入非明度变化——art-bible §2);AC「模态纪律」条改「独立性断言」(激活期战斗屏隐藏/选路提交战斗节点后恢复/兑付覆盖层关闭后本屏恢复);节点/商店覆盖层「本屏域内弹出」语义随形态明确(本屏保持激活,模态栈遮挡恢复语义复用);实现锚改名 level_map_screen.gd(RouteOverlay 退役);battle.md 三处选路覆盖层措辞随改;视觉终稿前置改指 M-2 迭代 4 关卡画面样张 |
| 2026-09-27 | 初稿(关卡画面 UX 批,用户拍板立项):占位升格版——信息层级五层/三分区布局/组件清单/三停点状态/交互与数据锚/AC 成文;停点标题措辞修正(清偿台账 143 遗留);GP 常驻徽章落地面(2026-09-26 二轮拍板去向,经 GP 归属拍板对齐批维持);三项提案挂 OQ(HP 常驻读数/图上直接点击选路/放弃二次确认)登记 06 待拍板;视觉终稿以表现层样张为前置。实现锚 = route_overlay.gd 程序化占位现行 | | 2026-09-27 | 初稿(关卡画面 UX 批,用户拍板立项):占位升格版——信息层级五层/三分区布局/组件清单/三停点状态/交互与数据锚/AC 成文;停点标题措辞修正(清偿台账 143 遗留);GP 常驻徽章落地面(2026-09-26 二轮拍板去向,经 GP 归属拍板对齐批维持);三项提案挂 OQ(HP 常驻读数/图上直接点击选路/放弃二次确认)登记 06 待拍板;视觉终稿以表现层样张为前置。实现锚 = route_overlay.gd 程序化占位现行 |
| 2026-09-27 | **三拍板收口批(用户逐项裁决)**:① 底栏 = 状态展示——生命/属性(攻击·AC 有效值)/本关已选加成三组常读(原提案顶栏 HP 扩为底栏状态栏,布局决策 4);② 图上直接点击选路定稿——候选按钮整列退役、图上点击为唯一提交通道(布局决策 2;press_candidate 探针面随实装批改造注记);③ 放弃冒险二次确认定稿(布局决策 3)。信息层级六层重排/组件清单/States/交互表/Data Requirements(属性与加成读数入口)/AC/线框图随改;06 拍板点条目解决移除(去向 = 本批台账 204) | | 2026-09-27 | **三拍板收口批(用户逐项裁决)**:① 底栏 = 状态展示——生命/属性(攻击·AC 有效值)/本关已选加成三组常读(原提案顶栏 HP 扩为底栏状态栏,布局决策 4);② 图上直接点击选路定稿——候选按钮整列退役、图上点击为唯一提交通道(布局决策 2;press_candidate 探针面随实装批改造注记);③ 放弃冒险二次确认定稿(布局决策 3)。信息层级六层重排/组件清单/States/交互表/Data Requirements(属性与加成读数入口)/AC/线框图随改;06 拍板点条目解决移除(去向 = 本批台账 204) |
+40 -14
View File
@@ -43,7 +43,9 @@ var _start_overlay: Control
var _end_overlay: BattleEndOverlay var _end_overlay: BattleEndOverlay
var _inter_overlay: Control var _inter_overlay: Control
var _inter_label: Label var _inter_label: Label
var route_overlay: RouteOverlay # 节点图选路覆盖层(分支节点图批——零自动推进停点) var level_screen: LevelMapScreen # 关卡画面(独立屏——2026-09-28 形态升格批:激活期战斗屏宿主整体隐藏;零自动推进停点语义不变)
var _battle_root: Control # 战斗屏宿主(B/C/D 三区容器——独立屏激活期 visible=false 级互斥)
var _level_screen_active := false # 独立屏激活状态(覆盖兑付覆盖层在屏域内弹出期间——战斗屏保持隐藏)
# 调试模式编排(单场/连续两层——状态机与战斗生命周期归 debug_session.gd,本类只接线与出 UI) # 调试模式编排(单场/连续两层——状态机与战斗生命周期归 debug_session.gd,本类只接线与出 UI)
var _debug = null var _debug = null
@@ -99,6 +101,7 @@ func _ready() -> void:
func _build_ui() -> void: func _build_ui() -> void:
var root := VBoxContainer.new() var root := VBoxContainer.new()
add_child(root) add_child(root)
_battle_root = root # 独立屏互斥面(2026-09-28 形态升格批)
var battle_ui: Dictionary = UiAssembler.build_battle_ui(root, db) var battle_ui: Dictionary = UiAssembler.build_battle_ui(root, db)
stage = battle_ui.stage stage = battle_ui.stage
log_view = battle_ui.log_view log_view = battle_ui.log_view
@@ -132,15 +135,16 @@ func _build_ui() -> void:
shop_overlay = overlays.shop_overlay shop_overlay = overlays.shop_overlay
camp_overlay = overlays.camp_overlay camp_overlay = overlays.camp_overlay
tune_panel = overlays.tune_panel tune_panel = overlays.tune_panel
route_overlay = RouteOverlay.new() level_screen = LevelMapScreen.new()
add_child(route_overlay) add_child(level_screen)
# 树序 = 模态登记序 = z 遮挡序(缺陷⑤纪律)——选路层须夹在节点层与商店层之间 # 树序 = 模态登记序 = z 遮挡序(缺陷⑤纪律)——关卡画面须夹在节点层与商店层之间
move_child(route_overlay, node_overlay.get_index() + 1) #(兑付覆盖层在本屏域内弹出:模态栈打开低层时自动隐藏高层本屏、关闭自动恢复——零机制改动复用)
move_child(level_screen, node_overlay.get_index() + 1)
legend_overlay.closed.connect(_on_modal_closed.bind(legend_overlay)) legend_overlay.closed.connect(_on_modal_closed.bind(legend_overlay))
review_overlay.closed.connect(_on_modal_closed.bind(review_overlay)) review_overlay.closed.connect(_on_modal_closed.bind(review_overlay))
node_overlay.continue_pressed.connect(_on_node_continue) node_overlay.continue_pressed.connect(_on_node_continue)
route_overlay.route_chosen.connect(_on_route_chosen) level_screen.route_chosen.connect(_on_route_chosen)
route_overlay.abandon_requested.connect(_on_abandon_requested) # 放弃入口(2026-09-21 用户拍板——台账 119) level_screen.abandon_requested.connect(_on_abandon_requested) # 放弃入口(2026-09-21 用户拍板——台账 119)
shop_overlay.buy_requested.connect(_on_shop_buy) shop_overlay.buy_requested.connect(_on_shop_buy)
shop_overlay.leave_pressed.connect(_on_shop_leave) shop_overlay.leave_pressed.connect(_on_shop_leave)
camp_overlay.buy_requested.connect(_on_camp_buy) camp_overlay.buy_requested.connect(_on_camp_buy)
@@ -151,7 +155,7 @@ func _build_ui() -> void:
tune_panel.apply_requested.connect(_on_tune_apply) tune_panel.apply_requested.connect(_on_tune_apply)
tune_panel.simulate_requested.connect(_on_tune_simulate) tune_panel.simulate_requested.connect(_on_tune_simulate)
_modals = ModalStack.new() _modals = ModalStack.new()
for m: Control in [legend_overlay, review_overlay, node_overlay, route_overlay, _end_overlay, shop_overlay, camp_overlay, tune_panel]: for m: Control in [legend_overlay, review_overlay, node_overlay, level_screen, _end_overlay, shop_overlay, camp_overlay, tune_panel]:
_register_modal(m) _register_modal(m)
_tune_sim = TuneSimController.new(db, tune_panel, int(db.hero.max_hp)) _tune_sim = TuneSimController.new(db, tune_panel, int(db.hero.max_hp))
@@ -205,7 +209,7 @@ func _on_modal_closed(m: Control) -> void:
func _input_locked() -> bool: func _input_locked() -> bool:
# 模态/非战斗期棋盘输入锁(各 overlay 自身可见即锁定) # 模态/非战斗期棋盘输入锁(各 overlay 自身可见即锁定)
return legend_overlay.is_open() or review_overlay.is_open() or _end_overlay.is_open() \ return legend_overlay.is_open() or review_overlay.is_open() or _end_overlay.is_open() \
or node_overlay.visible or route_overlay.visible or shop_overlay.visible or tune_panel.visible \ or node_overlay.visible or level_screen.visible or shop_overlay.visible or tune_panel.visible \
or _start_overlay.visible or _inter_overlay.visible or settlement == null or _start_overlay.visible or _inter_overlay.visible or settlement == null
@@ -231,7 +235,7 @@ func _start_adventure() -> void:
adventure.adventure_finished.connect(_on_adventure_finished) adventure.adventure_finished.connect(_on_adventure_finished)
adventure.level_started.connect(_on_adventure_level_started) adventure.level_started.connect(_on_adventure_level_started)
_level_active = true _level_active = true
route_overlay.set_abandon_available(true) # 放弃入口挂选路停点(天然非战斗期——2026-09-21 用户拍板) level_screen.set_abandon_available(true) # 放弃入口挂选路停点(天然非战斗期——2026-09-21 用户拍板)
_reset_run_scope() _reset_run_scope()
log_view.clear() log_view.clear()
var seed_val := _seed_value() var seed_val := _seed_value()
@@ -304,7 +308,7 @@ func _on_adventure_finished(summary: Dictionary) -> void:
_level_active = false _level_active = false
var result: String = String(summary.get("result", "")) var result: String = String(summary.get("result", ""))
settlement = null settlement = null
_close_modal(route_overlay) # 终局兜底(放弃入口新场景:选路层在开态——走模态栈恢复其遮挡面;未开过时 close 容错) _exit_level_screen() # 终局兜底:独立屏在开态(放弃入口)也先回战斗屏呈现摘要——屏级互斥单一出口
_sync_settlement_visuals() # → 全关 _sync_settlement_visuals() # → 全关
stage.hero_standee.set_effects_cleared_feedback() # 状态全清可见反馈(增益状态×4④——载体 2026-09-26 迁 B 区站桩) stage.hero_standee.set_effects_cleared_feedback() # 状态全清可见反馈(增益状态×4④——载体 2026-09-26 迁 B 区站桩)
stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面) stage.gray_all_enemies() # 死亡兜底:站桩灰化残位(禁瞬消语义的战斗收尾面)
@@ -572,17 +576,39 @@ func _on_choices_presented(candidates: Array) -> void:
if runner == null: if runner == null:
return return
log_view.append_bb(Narrator.route_line(candidates, runner)) log_view.append_bb(Narrator.route_line(candidates, runner))
_open_modal(route_overlay) _enter_level_screen()
route_overlay.open_choices(candidates, runner) level_screen.open_choices(candidates, runner)
_refresh_presentations() _refresh_presentations()
func _on_route_chosen(node_id: int) -> void: func _on_route_chosen(node_id: int) -> void:
_close_modal(route_overlay) # 形态升格分支(2026-09-28):视图已按节点类型定去留——战斗/Boss 出屏(visible=false,此处收栈
# 记录一致性+回战斗屏);奖励/恢复/商店本屏保持激活(兑付覆盖层由节点事件域内弹出,模态栈
# 打开低层时自动隐藏本屏、关闭自动恢复)
if not level_screen.visible:
_close_modal(level_screen)
_exit_level_screen()
if runner != null and not runner.pick_next_node(node_id): if runner != null and not runner.pick_next_node(node_id):
push_error("[组合根] 选路被拒(编号 %d 不在候选)" % node_id) push_error("[组合根] 选路被拒(编号 %d 不在候选)" % node_id)
# ---- 独立屏屏级互斥(形态升格批 2026-09-28——单一出口:进屏隐战斗屏 / 出屏显战斗屏)----
# 兑付覆盖层(节点/商店)在本屏激活域内弹出期间保持隐藏态不变——模态栈打开低层时隐藏本屏、
# 关闭自动恢复本屏(遮挡语义复用),战斗屏不在此期间回显。
func _enter_level_screen() -> void:
_level_screen_active = true
_battle_root.visible = false
_open_modal(level_screen)
func _exit_level_screen() -> void:
_level_screen_active = false
_battle_root.visible = true
if level_screen.visible:
_close_modal(level_screen)
func _on_abandon_requested() -> void: func _on_abandon_requested() -> void:
# 放弃入口(2026-09-21 用户拍板「放弃接口也要有」——推翻 冒险结构.md「MVP 无界面入口」后置条款; # 放弃入口(2026-09-21 用户拍板「放弃接口也要有」——推翻 冒险结构.md「MVP 无界面入口」后置条款;
# 入口挂选路停点 = 天然非战斗期,拒绝分支仅防御在案):受理 → 冒险放弃结算(同失败型, # 入口挂选路停点 = 天然非战斗期,拒绝分支仅防御在案):受理 → 冒险放弃结算(同失败型,
@@ -7,14 +7,16 @@
# ③ 放弃冒险二次确认(布局决策 3)——首击转确认态(文案+危险红脉冲约 3 秒超时回落),二击提交 # ③ 放弃冒险二次确认(布局决策 3)——首击转确认态(文案+危险红脉冲约 3 秒超时回落),二击提交
# ④ 顶栏语境:关卡名(level_row.display_name)+ GP 常驻徽章(2026-09-26 二轮拍板去向落地面) # ④ 顶栏语境:关卡名(level_row.display_name)+ GP 常驻徽章(2026-09-26 二轮拍板去向落地面)
# ⑤ 底部状态栏(布局决策 4):生命/属性/本关加成三组常读(零加成隐藏),放弃按钮右端异位 # ⑤ 底部状态栏(布局决策 4):生命/属性/本关加成三组常读(零加成隐藏),放弃按钮右端异位
# 节点图选路覆盖层(模态):兑付确认后由组合根打开。零自动推进的玩家停点语义:唯一前进通道 = # 关卡画面(独立屏,Level Map Screen——2026-09-28 形态升格批 M-2 迭代 3 T2:原 RouteOverlay 模态
# 覆盖层升格独立屏,spec level-map.md 形态章节同批改写):全屏不透明独占呈现——激活期战斗屏宿主
# 由组合根整体隐藏(非暗幕半透、零透出)。零自动推进的玩家停点语义不变:唯一前进通道 =
# 图上点候选节点 → route_chosen(node_id) → 组合根转 runner.pick_next_node。 # 图上点候选节点 → route_chosen(node_id) → 组合根转 runner.pick_next_node。
# 高内聚边界:只读 runner 成文出口(nodes()/visited_ids()/node_index/next_candidate_ids()/level_row/ # 高内聚边界:只读 runner 成文出口(nodes()/visited_ids()/node_index/next_candidate_ids()/level_row/
# hero_hp/gp/effective_*/bonus)做渲染,无任何写面;图形为程序化占位质量(占位纪律)。 # hero_hp/gp/effective_*/bonus)做渲染,无任何写面;图形为程序化占位质量(占位纪律)。
# 放弃入口(2026-09-21 用户拍板「放弃接口也要有」——台账 119):选路停点 = 天然非战斗期(接口契约: # 放弃入口(2026-09-21 用户拍板「放弃接口也要有」——台账 119):选路停点 = 天然非战斗期(接口契约:
# 战斗中放弃一律拒绝),按钮常驻底栏右端;可见性归组合根(只在冒险模式开——放弃的语义对象是 # 战斗中放弃一律拒绝),按钮常驻底栏右端;可见性归组合根(只在冒险模式开——放弃的语义对象是
# "整局冒险";调试两层无节点图天然不可达)。 # "整局冒险";调试两层无节点图天然不可达)。
class_name RouteOverlay class_name LevelMapScreen
extends Control extends Control
signal route_chosen(node_id: int) signal route_chosen(node_id: int)
@@ -54,13 +56,23 @@ var _confirm_left := 0.0 # 确认态剩余秒数(_process 倒计
func _init() -> void: func _init() -> void:
var dim := ColorRect.new() # 独立屏底(形态升格 2026-09-28——spec Layout:深底+石纹底噪铺满;底纹缺位回退纯色——load_tex 守卫)
dim.color = Color(0, 0, 0, 0.6) var base := ColorRect.new()
dim.set_anchors_preset(Control.PRESET_FULL_RECT) base.color = UiKit.BG_ROOT
add_child(dim) base.set_anchors_preset(Control.PRESET_FULL_RECT)
add_child(base)
var noise := UiKit.load_tex(UiKit.TEX_BACKDROP_STONE)
if noise != null:
var tex := TextureRect.new()
tex.texture = noise
tex.stretch_mode = TextureRect.STRETCH_TILE
tex.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
tex.set_anchors_preset(Control.PRESET_FULL_RECT)
tex.mouse_filter = Control.MOUSE_FILTER_IGNORE
base.add_child(tex)
var center := CenterContainer.new() var center := CenterContainer.new()
center.set_anchors_preset(Control.PRESET_FULL_RECT) center.set_anchors_preset(Control.PRESET_FULL_RECT)
dim.add_child(center) base.add_child(center)
var box := VBoxContainer.new() var box := VBoxContainer.new()
box.add_theme_constant_override("separation", 10) box.add_theme_constant_override("separation", 10)
center.add_child(box) center.add_child(box)
@@ -195,6 +207,7 @@ func _process(delta: float) -> void:
# 打开选路口(cands = 候选节点引用数组;runner 只读用于整图渲染与状态栏刷新) # 打开选路口(cands = 候选节点引用数组;runner 只读用于整图渲染与状态栏刷新)
func open_choices(cands: Array, runner) -> void: func open_choices(cands: Array, runner) -> void:
var was_visible := visible # 进入转场判据(就地刷新/模态栈恢复不重放淡入)
_runner = runner _runner = runner
_hover_id = -1 _hover_id = -1
_reset_abandon_confirm() _reset_abandon_confirm()
@@ -213,6 +226,11 @@ func open_choices(cands: Array, runner) -> void:
_fit_canvas(_runner.nodes()) _fit_canvas(_runner.nodes())
_refresh_status() _refresh_status()
visible = true visible = true
if not was_visible:
# 独立屏淡入(spec Transitions:约 200ms;alpha 淡入非明度变化——art-bible §2 明度恒定)
modulate.a = 0.0
var ftw := create_tween()
ftw.tween_property(self, "modulate:a", 1.0, UiKit.motion(0.2))
# 画布随图自适应(level-map.md Layout:画布宽随层数、高随同层最大节点数——公式与 _layout 同源; # 画布随图自适应(level-map.md Layout:画布宽随层数、高随同层最大节点数——公式与 _layout 同源;
@@ -300,7 +318,12 @@ func _candidate_at(local_pos: Vector2) -> int:
func _submit_choice(nid: int) -> void: func _submit_choice(nid: int) -> void:
visible = false # 形态升格分支(2026-09-28 独立屏):战斗/Boss 节点 = 出屏切回战斗屏;奖励/恢复/商店节点 =
# 本屏保持激活——兑付覆盖层由节点事件在本屏域内弹出(模态栈遮挡恢复语义复用),兑付确认后回图刷新
var node := _node_by_id(nid, _runner.nodes())
var t := String(node.get("type", ""))
if t != "reward" and t != "rest" and t != "shop":
visible = false
route_chosen.emit(nid) route_chosen.emit(nid)
+7 -4
View File
@@ -90,8 +90,11 @@ func _ready() -> void:
await get_tree().process_frame await get_tree().process_frame
# 停点前移批 2026-09-23:关卡开始 = 入口选路停点——press_candidate 图上命中路径选入口候选进首战(候选按钮退役 2026-09-28) # 停点前移批 2026-09-23:关卡开始 = 入口选路停点——press_candidate 图上命中路径选入口候选进首战(候选按钮退役 2026-09-28)
#(首格可点 AC 口径随之含入口选路一步:菜单点击 → 停点呈现 → pick → 棋盘安装) #(首格可点 AC 口径随之含入口选路一步:菜单点击 → 停点呈现 → pick → 棋盘安装)
if main.route_overlay.visible: if main.level_screen.visible:
main.route_overlay.press_candidate(0) # 独立性断言(形态升格批 2026-09-28):独立屏激活期战斗屏宿主整体隐藏
if main._battle_root.visible:
_problems.append("独立屏激活期战斗屏宿主未隐藏(零透出契约失守)")
main.level_screen.press_candidate(0)
await get_tree().process_frame await get_tree().process_frame
await get_tree().process_frame await get_tree().process_frame
var t_entry := Time.get_ticks_msec() - t1 var t_entry := Time.get_ticks_msec() - t1
@@ -113,9 +116,9 @@ func _ready() -> void:
main.node_overlay.press_continue() main.node_overlay.press_continue()
_flags_placed.clear() _flags_placed.clear()
continue continue
if main.route_overlay.visible: if main.level_screen.visible:
# 节点图选路口(零自动推进停点)——图上命中路径交替选候选(岔路使用行为;布局决策 2 改造) # 节点图选路口(零自动推进停点)——图上命中路径交替选候选(岔路使用行为;布局决策 2 改造)
main.route_overlay.press_candidate(_route_clicks % 2) main.level_screen.press_candidate(_route_clicks % 2)
_route_clicks += 1 _route_clicks += 1
_flags_placed.clear() _flags_placed.clear()
continue continue
+5 -5
View File
@@ -31,8 +31,8 @@ func _ready() -> void:
# push_input 真实鼠标路径 motion+click → 悬停预承诺置位 → route_chosen 到达组合根。 # push_input 真实鼠标路径 motion+click → 悬停预承诺置位 → route_chosen 到达组合根。
# 由来=真机 bug:深层节点(L1 Boss 内容 x=802)在固定画布命中区外,press_candidate 直调 # 由来=真机 bug:深层节点(L1 Boss 内容 x=802)在固定画布命中区外,press_candidate 直调
# 命中函数探针全绿、真鼠标点不中——本 case 封住输入管线,⑬封住几何拟合。 # 命中函数探针全绿、真鼠标点不中——本 case 封住输入管线,⑬封住几何拟合。
if main.route_overlay.visible: if main.level_screen.visible:
var ro = main.route_overlay var ro = main.level_screen
var cands: Array = main.runner.next_candidate_ids() var cands: Array = main.runner.next_candidate_ids()
var nds: Array = main.runner.nodes() var nds: Array = main.runner.nodes()
var tgt := {} var tgt := {}
@@ -56,13 +56,13 @@ func _ready() -> void:
main.get_viewport().push_input(evc, true) main.get_viewport().push_input(evc, true)
await get_tree().process_frame await get_tree().process_frame
await get_tree().process_frame await get_tree().process_frame
if main.route_overlay.visible or main.runner.awaiting_choice(): if main.level_screen.visible or main.runner.awaiting_choice():
_pre_fail("图上真实点击未提交选路(overlay 可见=%s 选路态=%s)" % [str(main.route_overlay.visible), str(main.runner.awaiting_choice())]) _pre_fail("图上真实点击未提交选路(overlay 可见=%s 选路态=%s)" % [str(main.level_screen.visible), str(main.runner.awaiting_choice())])
for i in range(3): for i in range(3):
await get_tree().process_frame await get_tree().process_frame
# ---- ⑬ 画布随图拟合(同 bug 的几何面回归):复刻分层公式独立推导最深节点内容 x, # ---- ⑬ 画布随图拟合(同 bug 的几何面回归):复刻分层公式独立推导最深节点内容 x,
# 断言画布 min 尺寸覆盖之(open_choices 每 stop 拟合——L1 单链 Boss x=70+6*122=802 曾越界) # 断言画布 min 尺寸覆盖之(open_choices 每 stop 拟合——L1 单链 Boss x=70+6*122=802 曾越界)
var ro2 = main.route_overlay var ro2 = main.level_screen
var nds2: Array = main.runner.nodes() var nds2: Array = main.runner.nodes()
var layer2 := {} var layer2 := {}
var inbound2 := {} var inbound2 := {}
+12 -7
View File
@@ -15,7 +15,7 @@ func _ready() -> void:
await get_tree().process_frame await get_tree().process_frame
var problems: Array = [] var problems: Array = []
# ---- 放弃入口(2026-09-21 用户拍板——台账 119):默认隐藏(非冒险模式无冒险可放弃——开始层/调试两层)---- # ---- 放弃入口(2026-09-21 用户拍板——台账 119):默认隐藏(非冒险模式无冒险可放弃——开始层/调试两层)----
if main.route_overlay.abandon_btn == null or main.route_overlay.abandon_btn.visible: if main.level_screen.abandon_btn == null or main.level_screen.abandon_btn.visible:
print("[ui_probe] FAIL — 放弃按钮默认应隐藏(非冒险模式关)") print("[ui_probe] FAIL — 放弃按钮默认应隐藏(非冒险模式关)")
get_tree().quit(1) get_tree().quit(1)
return return
@@ -63,24 +63,29 @@ func _ready() -> void:
await get_tree().process_frame await get_tree().process_frame
# ---- 入口选路停点(停点前移批 2026-09-23:关卡开始即出节点图——level_started 后、首战前弹选路层; # ---- 入口选路停点(停点前移批 2026-09-23:关卡开始即出节点图——level_started 后、首战前弹选路层;
# press_candidate 图上命中路径 pick 入口单候选进首战——候选按钮退役 2026-09-28)---- # press_candidate 图上命中路径 pick 入口单候选进首战——候选按钮退役 2026-09-28)----
if not main.route_overlay.visible: # ---- 独立性断言(形态升格批 2026-09-28:独立屏激活期战斗屏宿主整体隐藏,零透出)----
problems.append("关卡开始未弹入口选路停点(route_overlay 应可见)") if not main.level_screen.visible:
problems.append("关卡开始未弹入口选路停点(level_screen 应可见)")
elif main._battle_root.visible:
problems.append("独立屏激活期战斗屏宿主未隐藏(零透出契约失守)")
elif main.runner == null or not main.runner.awaiting_choice() \ elif main.runner == null or not main.runner.awaiting_choice() \
or main.runner.next_candidate_ids().size() != 1: or main.runner.next_candidate_ids().size() != 1:
problems.append("入口停点状态失守(选路态 + 入口单候选)") problems.append("入口停点状态失守(选路态 + 入口单候选)")
else: else:
main.route_overlay.press_candidate(0) main.level_screen.press_candidate(0)
await get_tree().process_frame await get_tree().process_frame
await get_tree().process_frame await get_tree().process_frame
if not main._battle_root.visible or main.level_screen.visible:
problems.append("选路提交战斗节点后未切回战斗屏(屏级互斥恢复失守)")
# ---- 放弃入口·冒险模式可见 + 战斗中按下被拒(真实按钮路径——入口挂选路停点,战斗中拒绝为防御分支)---- # ---- 放弃入口·冒险模式可见 + 战斗中按下被拒(真实按钮路径——入口挂选路停点,战斗中拒绝为防御分支)----
if main.route_overlay.abandon_btn == null or not main.route_overlay.abandon_btn.visible: if main.level_screen.abandon_btn == null or not main.level_screen.abandon_btn.visible:
problems.append("冒险模式放弃按钮未开放(选路停点入口在案)") problems.append("冒险模式放弃按钮未开放(选路停点入口在案)")
else: else:
main.route_overlay.abandon_btn.pressed.emit() main.level_screen.abandon_btn.pressed.emit()
await get_tree().process_frame await get_tree().process_frame
if main.adventure == null or String(main.adventure.state) != "running": if main.adventure == null or String(main.adventure.state) != "running":
problems.append("首战中放弃未被拒(战斗域单事务在飞——state 应保持 running)") problems.append("首战中放弃未被拒(战斗域单事务在飞——state 应保持 running)")
# ---- 既有断言(回归基线;A 区进度条断言随 停点前移批 2026-09-23 退役——节点链进度由关卡画面选路覆盖层承载)---- # ---- 既有断言(回归基线;A 区进度条断言随 停点前移批 2026-09-23 退役——节点链进度由关卡画面独立屏承载)----
if main.board_view._cells.is_empty(): if main.board_view._cells.is_empty():
problems.append("棋盘格未生成(cells=0)") problems.append("棋盘格未生成(cells=0)")
if main.board_view.board() == null: if main.board_view.board() == null:
+2 -2
View File
@@ -65,8 +65,8 @@ func _ready() -> void:
await get_tree().process_frame await get_tree().process_frame
await get_tree().process_frame await get_tree().process_frame
# 停点前移批 2026-09-23:关卡开始 = 入口选路停点——真实按钮路径选入口候选进首战再读呈现快照 # 停点前移批 2026-09-23:关卡开始 = 入口选路停点——真实按钮路径选入口候选进首战再读呈现快照
if main.route_overlay.visible: if main.level_screen.visible:
main.route_overlay.press_candidate(0) main.level_screen.press_candidate(0)
await get_tree().process_frame await get_tree().process_frame
await get_tree().process_frame await get_tree().process_frame
var hero: Dictionary = main.settlement.presentation_snapshot().hero var hero: Dictionary = main.settlement.presentation_snapshot().hero