Skip to content
This repository was archived by the owner on Sep 20, 2026. It is now read-only.

fix(combat-insights): stabilize recap impact tooltips - #212

Merged
pengx17 merged 3 commits into
masterfrom
codex/fix-combat-impact-orphan-header
Aug 5, 2026
Merged

pengx17 merged 3 commits into
masterfrom
codex/fix-combat-impact-orphan-header

Conversation

@pengx17

@pengx17 pengx17 commented Aug 4, 2026

Copy link
Copy Markdown
Member

Context

Recap 中的 Combat Impact 复用游戏原生 Auxiliary Tooltip。快速切换卡牌时,原生 tooltip 的异步创建、淡入、销毁与 BPP 的准备/接管可能落在同一帧,先后出现两类用户可见故障:

  • 偶发残留只有 Combat Impact 标题的白色原生框体;
  • 后续快速切换时整套 Combat Impact 不再显示,日志记录 CanvasGroupGate 构造阶段的 NullReferenceException

本 PR 基于实际 Tooltip_Aux_P prefab 结构和运行日志修复该竞态,并统一 Regen 总量的数字格式。

改动重点

  • Native paired tooltip host:让 auxParent gate 覆盖完整原生视觉树,并在 stale request teardown 期间持续保持关闭。
  • 同帧生命周期:BPP 自有 CanvasGroup 恢复时立即销毁,避免下一次请求复用一个已排队销毁的组件。
  • 失败恢复:原生 controller 正在销毁时软失败并有限重试,不再以异常终止当前 hover。
  • 诊断链路:增加带 card template ID 的 Debug 交互轨迹和细分 open-failure reason code。
  • 指标格式:Applied Regen 总量不再显示多余的 +

协作过程

sequenceDiagram
    participant U as 用户实机验收
    participant C as Codex
    participant F as Claude Code / Fable
    participant G as 游戏原生 Tooltip

    U->>C: 报告偶发 header-only 白框
    C->>G: 先修正取消路径的 conceal/restore 顺序
    U->>C: 白框仍出现,快速切换后 tooltip 还会完全消失
    C->>G: 尝试 controller-root 外层 gate
    G-->>C: 运行日志暴露 CanvasGroupGate NRE
    U->>F: 委托基于 prefab 与日志重新定位
    F->>G: 确认完整视觉树属于 auxParent,发现 deferred Destroy 同帧复用竞态
    F->>C: 改为 held auxParent gate + DestroyImmediate + bounded retry
    C->>G: 独立复核 diff、运行日志和全部测试门禁
Loading

方案讨论

ADR-001:可见性 gate 应挂在哪里

Context:原先假定 auxParent 只包含正文,因此尝试在 controller root 上增加独立 CanvasGroup。

Considered Options

  1. 在取消时把原生 serialized CanvasGroup 设为 0;
  2. 在 controller root 增加外层 gate;
  3. 持续关闭 auxParent 上的 BPP gate,直到 teardown 或下一次合法 show 接管。

Decision:选择 3。实际 prefab 中 Tooltip_Aux_ContentauxParent)拥有 Background、TitleText、BodyText 和 Divider;controller root 位于嵌套 Canvas 之外,root CanvasGroup 不会传播到真实视觉树。

Consequences:取消路径不再提前恢复 gate;同一 controller 的快速重用直接复用仍关闭的 gate,foreign native show 则通过 ReleasePrepared 恢复。

ADR-002:处理 Unity deferred Destroy

ContextObject.Destroy 到帧末才移除组件。快速切换在同一帧重新 prepare 时,GetComponent<CanvasGroup>() 会拿到即将销毁的旧 gate;帧末 gate 消失后,原生淡入变成无保护状态。controller 本身正在销毁时,AddComponent 还可能返回 Unity 的 dead reference,随后读取属性触发 NRE。

Decision:BPP 自有 gate 在恢复时使用 DestroyImmediate;gate 创建允许返回失败,并由 TryOpen 输出明确的 NativePairedTooltipOpenFailure

Consequences:同帧重建不会继承尸体组件;临时的 dying-controller failure 会在当前 hover 内最多重试两次,持续故障仍会停止,避免无限 hide/show 循环。

最终方案

sequenceDiagram
    participant H as Hover request
    participant N as Native Auxiliary Tooltip
    participant P as NativePairedTooltipSession

    H->>N: request show
    N->>P: Harmony prefix / PrepareAuxiliary
    P->>P: capture native state and close/reuse auxParent gate
    N->>N: native position + fade + HasShown
    P->>P: TryOpen and build complete paired content
    alt live controller and complete content
        P->>P: reveal gate after layout settles
    else controller is dying
        P->>P: conceal + rollback with typed failure
        H->>N: bounded re-request
    else stale request
        P->>P: keep gate closed through native teardown
    end
Loading

Combat Impact 的 feature 层仍只负责请求状态、重试预算和 reason-code 日志;原生 geometry、gate 与恢复生命周期继续由 GameInterop/Tooltips 的共享 session 管理。

验证情况

  • ./run.sh format-check
  • ./run.sh test(全量测试通过)
  • NativePairedTooltipHost.Tests:29/29
  • PostCombatImpact.Tests:149/149
  • Architecture.Tests:153/153
  • Debug build:0 warnings / 0 errors,并复制到游戏插件目录
  • Release build:0 warnings / 0 errors
  • git diff --check
  • 最新实机日志在快速 hover 序列中持续记录 recap_hover_observed → shown/settled → dismissed,未再出现 CanvasGroupGate NRE 或 tooltip_render_exception

已知局限 / 后续工作

  • 指针在 tooltip settle 前已经离开目标时,只有 hover/dismissed 而没有 shown 属于预期取消,不应视为丢失。
  • Debug 的 card-identified interaction trace 用于收敛该竞态;Release 仍只保留 degradation 级日志。
  • PR 保持 Draft,待最终实机确认快速切换不再出现 header-only 白框或持续缺失后转 Ready。

pengx17 and others added 3 commits August 5, 2026 00:16
…nt opens

Five distinct defects behind the recurring Combat Impact orphan flash and
missing-details reports, root-caused against the Tooltip_Aux_P prefab dumped
from the game's asset bundles:

- CanvasGroupGate.Create fails soft (null) when AddComponent lands on a
  controller whose Destroy is pending, instead of throwing NRE into the
  feature's catch-all; TryOpen conceals and rolls back with a typed
  NativePairedTooltipOpenFailure reason the view logs at Warning.
- Transient open failures requeue the pending show (bounded per hover
  revision) instead of suppressing details until pointer exit.
- The controller-root shell gate is removed: the root is a world-space
  Transform above the prefab's nested Canvas, so a CanvasGroup there never
  affected rendering. auxParent (Tooltip_Aux_Content) owns the complete
  visual tree and its gate is the one effective concealment.
- CancelPreparedAuxiliary keeps the auxParent gate closed through native
  teardown; PrepareAuxiliary reuses a matching held gate closed instead of
  restore-then-recreate.
- Owned gates DestroyImmediate on restore: a deferred Destroy left a corpse
  that a same-frame re-prepare adopted via GetComponent, silently losing the
  gate at frame end — the ungated native fade-in was the one-frame white
  flash on Legendary frames.

Adds a card-identified Debug interaction trail (interaction.traced) plus
probes for silent exits (pending_show_blocked/aborted, unmatched shows).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Regen totals now match the unsigned total format used by Shield/Heal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pengx17
pengx17 marked this pull request as ready for review August 4, 2026 18:53
Copilot AI lite review requested due to automatic review settings August 4, 2026 18:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the PostCombatImpact “Combat Impact” recap tooltip takeover flow when reusing the game’s native Auxiliary Tooltip, addressing same-frame lifecycle races (prepare/fade-in/hide/destroy) that could previously lead to a header-only native shell or a lost tooltip due to gate creation failures. It also normalizes the displayed formatting for total applied regen.

Changes:

  • Make NativePairedTooltipHost reuse and hold an auxParent-scoped visibility gate through stale teardown, add typed TryOpen failure reporting, and fail soft on dying controllers (including immediate cleanup of owned gates).
  • Add bounded transient re-request behavior + richer interaction tracing/reason codes in PostCombatImpact when the native auxiliary is temporarily unusable.
  • Remove the redundant “+” from the “Applied Regen total” metric formatting and update tests accordingly.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/PostCombatImpact.Tests/CombatImpactMetricFormatterTests.cs Updates expected strings for regen totals to match new sign/format behavior.
tests/Architecture.Tests/NativePairedTooltipArchitectureTests.cs Adds/extends architecture tests to lock in auxParent gating, cancel/release ordering, soft-failure semantics, and bounded retry behavior.
src/BazaarPlusPlus/Patches/PostCombatImpact/NativePostCombatImpactTooltipView.cs Logs previously-silent show rejections and maps host open-failures to feature reason codes.
src/BazaarPlusPlus/GameInterop/Tooltips/NativePairedTooltipHost.cs Core stabilization: auxParent gate reuse/holding, conceal-before-rollback, soft gate creation, DestroyImmediate for owned gates, and typed open-failure reporting.
src/BazaarPlusPlus/GameInterop/Tooltips/NativePairedTooltipContracts.cs Introduces NativePairedTooltipOpenFailure enum for typed TryOpen failure reporting.
src/BazaarPlusPlus/Game/PostCombatImpact/PostCombatImpactLogEvents.cs Adds new reason codes and a card-identified interaction trace event/fields.
src/BazaarPlusPlus/Game/PostCombatImpact/PostCombatImpactController.cs Adds bounded transient retries on show failure and richer per-card interaction tracing.
src/BazaarPlusPlus/Game/PostCombatImpact/Data/CombatImpactMetricFormatter.cs Suppresses “+” sign for RegenApplyAmount applied-effect attribute changes.
docs/drafts/2026-07-30-recap-right-click-not-received.md Adds detailed investigation notes and timeline for the tooltip race regressions/fixes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@pengx17
pengx17 merged commit 5c630db into master Aug 5, 2026
1 check passed
@cauyxy
cauyxy deleted the codex/fix-combat-impact-orphan-header branch August 6, 2026 11:16
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants