Files
gplan-2/production/qa/bugs/BUG-0001.md
T
2026-09-14 18:18:49 +08:00

8.3 KiB
Raw Blame History

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 区可见。

  1. 鼠标悬停任一状态效果槽(tooltip 弹出——此步把 _tip 挂入 StatusSlotRow 子节点表)
  2. 正常结束本轮(停手或 AP 耗尽),进入轮末结算
  3. 结算段任一 step_row 下发(如道具窗口跳过 → 战斗段「主角攻击」行)触发 _refresh_presentations
  4. 游戏无声冻结:棋盘保持去饱和、阶段文案停在「结算中」,所有输入通道无效

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 按钮 lambda
    • playtest_42_20260912T192219.jsonl:ux_window_choice{skip:true} 之后再无任何事件(无 settlement 快照、无 ux_round_exit、无下一轮)——结算协程死亡实锤
  • Visual: 棋盘去饱和锁定、阶段指示「第 N 轮 · 结算中」、明细带展开但停在该结算首行;与 2026-09-11 崩溃 #1 的"输入锁已上 + 呈现层崩溃 = 无声冻结"同族
  • 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