8.3 KiB
Bug Report
Summary
Title: 结算战斗段首行下发时 StatusSlotRow 按索引误取 tooltip 节点崩溃,结算协程中断 → 结算阶段永久软锁 ID: BUG-0001 Severity: S1-Critical(核心循环阻断——结算阶段永久假死,只能杀进程) Priority: P1-Immediate Status: Closed Reported: 2026-09-12 Reporter: 用户(设计师本人,垂直切片试玩)
Classification
- Category: Crash / 软锁(soft lock,无崩溃弹窗的假死)
- System: C 区状态效果槽(StatusSlotRow)呈现层 × B1 结算编排(信号链中断面)
- Frequency: 条件必现(前置 = 状态槽悬停过一次 + 其后任一结算行刷新时英雄侧效果 ≥1;悬停是新增益出现后的自然操作,实际对局高频可触)
- Regression: Yes——
status_slot_row.gd与 C 区数据框为 2026-09-12 五区改造批新增;2026-09-11 build 无此视图
Environment
- Build: 垂直切片 · battle.md 五区改造批(2026-09-12);卡死会话种子 42,19:22:19 开局
- Platform: PC · Win10 · Godot 4.7.2 (OpenGL Compatibility · RTX 2060 SUPER)
- Scene/Level: 完整关卡模式(第一关)· 轮末结算(道具窗口跳过后、战斗段首行)
- Game State: 第 1 场战斗结算中;道具窗口以「跳过」关闭;英雄侧存在本结算新授予的状态效果(槽可见)
Reproduction Steps
Preconditions: 对局中英雄侧至少有 1 个状态效果(如安全格授予的增伤),状态槽在 C 区可见。
- 鼠标悬停任一状态效果槽(tooltip 弹出——此步把
_tip挂入 StatusSlotRow 子节点表) - 正常结束本轮(停手或 AP 耗尽),进入轮末结算
- 结算段任一 step_row 下发(如道具窗口跳过 → 战斗段「主角攻击」行)触发
_refresh_presentations - 游戏无声冻结:棋盘保持去饱和、阶段文案停在「结算中」,所有输入通道无效
Expected Result: 结算七段完整回放,逐行兑现后进入下一轮(或终局 overlay)。
Actual Result: status_slot_row.gd:118 脚本错误沿信号链回卷,_finish_settlement 在战斗段(⑤)中途被中止——_settling 恒为 true、A3 恒为 PHASE_WAITING,结算永不恢复。
Technical Context
- Likely affected files:
prototypes/dungeon-sweeper-vertical-slice/presentation/views/status_slot_row.gd:57-63(set_effects用get_child(i)按位取槽——_tip入树后索引错位)prototypes/dungeon-sweeper-vertical-slice/presentation/views/status_slot_row.gd:80-88(_sync_slots显式保留_tip,与上行索引假设自相矛盾)prototypes/dungeon-sweeper-vertical-slice/presentation/views/status_slot_row.gd:114-118(_fill_slot对 cast 为 null 的col调get_child)- 中断面:
core/battle_orchestrator.gd:233-263(_finish_settlement——错误回卷致_settling悬挂)
- Related systems:
main.gd _on_step_row → _refresh_presentations(每结算行同步刷新);level_runner.gd:128转发 lambda;hud_presenter.gd:29 - Possible root cause(已由崩溃回溯证实):
tooltip 节点
_tip在_init不入树,仅_on_slot_hover首次悬停时add_child(_tip),与动态重建的槽共用同一子节点表。此后set_effects内get_child(i)取到的第 0 个"槽"实为_tip(PanelContainer,cast 通过),_fill_slot中_tip.get_child(0)是 Label →as VBoxContainer= null → 118 行col.get_child(0)报Cannot call method 'get_child' on a null value。 Godot 4 对信号回调内的运行时错误整条 GDScript 调用栈回卷:按钮 lambda →submit_window_selection→_finish_settlement→_combat_segment全部中止,B1 无"结算恢复"路径,软锁。
Evidence
- Logs(
user://logs/,DungeonSweeperVerticalSlice):godot.log(19:22 会话)完整回溯:_fill_slot (status_slot_row.gd:118)←set_effects←set_status_effects←HudPresenter.refresh←main._on_step_row←level_runner lambda←_emit_row←_combat_segment (battle_orchestrator.gd:474)←_finish_settlement (242)←submit_window_selection (230)←_on_window_skipped← skip 按钮 lambdaplaytest_42_20260912T192219.jsonl:ux_window_choice{skip:true}之后再无任何事件(无 settlement 快照、无 ux_round_exit、无下一轮)——结算协程死亡实锤
- Visual: 棋盘去饱和锁定、阶段指示「第 N 轮 · 结算中」、明细带展开但停在该结算首行;与 2026-09-11 崩溃 #1 的"输入锁已上 + 呈现层崩溃 = 无声冻结"同族
Related Issues
production/qa/playtests/playtest-2026-09-11-designer.md崩溃 #1/#2——同一失败家族(结算期呈现层错误 + 核心中断/输入锁死),当时的防复发三条硬性要求(载荷 schema 成文 / 场景级 headless 测试 / 状态可见性)第三条仍未覆盖本路径presentation/views/board_view.gd:476_make_cursor_texture启动期canvas_item is null渲染错误(P3,独立缺陷,与本软锁无关)status_slot_row.gd:57-63潜在次生缺陷:set_effects循环上界是effects.size()而_sync_slots只建min(size, 4)个槽——英雄侧效果 >4 时(off_guard max_stack 16)同函数必崩- 场景探针(ui_probe/ui_click_probe)未覆盖「悬停 → 结算刷新」路径——冒烟再次漏掉呈现层时序态
Notes
- 修复方向(最小面):槽与 tooltip 分离承载——
_tip改挂 StatusSlotRow 之外的独立层,或set_effects/_sync_slots改用槽引用数组(不按子节点位序索引)。两处任选其一,后者改动面更小但必须同步修掉 >4 溢出循环上界 - 按代码规范「缺陷修复必须沉淀回归断言」:需在场景级探针补"悬停槽位后触发一次含效果行的结算,断言无脚本错误且 phase 走到下一轮"
- 本次卡死用户未点「诊断转储」(现存三份 diag 均来自探针
{"test":true}上下文,core 字段为空)——诊断按钮对真实软锁会话未被使用,不影响本次定位(godot.log 回溯已足够)
Fix Record(2026-09-12,Status 仍 Open——正式流转待 /bug-report verify)
- 修复:
status_slot_row.gd槽生命周期改由_slots: Array[PanelContainer]引用数组唯一管理,set_effects/_sync_slots/set_cleared_feedback全部弃用子节点位序索引;顺带修掉 effects > MAX_SLOTS 时循环越界次生缺陷(超出截断至 4 槽,符合"最多 4 槽可见"规格) - 回归断言:
smoke/ui_probe.gd新增 BUG-0001 回归段(建槽 → 悬停入 tooltip → 重刷断言槽位/填充 → 超上限截断 → 空态清槽) - 阴性对照:回滚修复版跑探针,精确复现野外崩溃(
status_slot_row.gd:118同行同错);恢复修复版后探针通过——断言有效 - 验证:smoke_all 161 检查 0 失败 · ui_probe PASS · ui_click_probe(seed 7)关卡通关/问题=0/选用失效=0/无卡死 · ui_tune_probe PASS ×2
- 待办:人类完整试玩复跑(playtest 报告 Top1 优先级)后走 verify/close
Verification Record(2026-09-12)
判定:VERIFIED FIXED
- 复现路径核查:
status_slot_row.gd中set_effects/_sync_slots/set_cleared_feedback已无子节点位序索引(grep 复核,仅_slots引用数组访问)——原崩溃代码路径不复存在 - 阴性对照:修复前版本在 ui_probe 回归段精确复现野外崩溃(
status_slot_row.gd:118同行同错),证明断言与定位有效 - 相关测试:smoke_all 161 项检查 0 失败(关闭时复跑)· ui_probe PASS(含 BUG-0001 回归段)· ui_click_probe(seed 7)关卡通关/问题=0/无卡死 · ui_tune_probe PASS
- 人工确认:用户试玩确认"没有问题"
Closure Record
Closed: 2026-09-12
Resolution: Fixed — StatusSlotRow 槽生命周期改由 _slots 引用数组唯一管理,弃用子节点位序索引(tooltip 入树不再错位);顺带修复 effects > MAX_SLOTS 循环越界(截断至 4 槽)
Fix commit / PR: 本地工作区直接修复(项目非 git 仓)
Verified by: qa-tester
Closed by: 用户
Regression test: prototypes/dungeon-sweeper-vertical-slice/smoke/ui_probe.gd(BUG-0001 回归段)
Status: Closed