AI诊断:没有关联 ID 的绿线解释不了失败

自动关联目录:AI诊断:没有关联 ID 的绿线解释不了失败

核心判断:Gameplay Debugger、Visual Logger 和场景 DebugDraw 能回答“此刻看到了什么”,但无法单独回答一次攻击为什么没发生。必须用同一条关联链串起感知、选敌、状态迁移、MoveRequest、NavLink 和 AbilityExecution。

一、业务冲突:AI 故障通常跨越多个帧和多个系统

玩家看到“怪物站着不动”,原因可能是没有收到 Stimulus、候选被过滤、追击人数已满、路径查询失败、门 Lease 卡住、状态被打断或 Ability 激活失败。现场画一条路径只能证明当前导航状态,Actor 名称日志也无法区分池化前后的两次生命。问题必须能沿决策因果回放,而不是靠录屏猜测。

二、实际实现:局部工具齐全,缺少跨系统关联

工程已接入自定义 Gameplay Debugger 分类与调试组件,用宏控制非发布构建;路径层写入 Visual Logger,并可通过 CVar 绘制 Recast 多边形、目标和路径。这些选择符合 UE 调试生态,也避免把大量诊断状态复制到正式客户端。

但现有日志多以 Actor 名称为主,调试目标入口还有未完成分支;感知、路径和状态分别输出自己的局部信息,没有统一的 DecisionId、MoveRequestId、AbilityExecutionId 与 Actor Incarnation。于是一次旧路径回调落到池化后的 Actor 上,日志看起来仍是同一个对象。

flowchart LR
    P[StimulusId] --> D[DecisionId]
    D --> T[TargetSelection]
    D --> S[StateRevision]
    S --> M[MoveRequestId]
    M --> L[LinkLeaseId]
    S --> A[AbilityExecutionId]
    T --> O[Outcome + Reason]
    L --> O
    A --> O

三、值得保留:诊断留在引擎原生观察面

Gameplay Debugger 适合显示当前 Blackboard、状态、目标和预算层级;Visual Logger 适合带世界位置记录路径与事件;CVar 与 DebugDraw 适合临时检查 NavMesh。它们各有价值,不应另造一个永远落后于 UE 的可视化系统。正确升级是让三种观察面读取同一份结构化诊断数据。

四、结构性缺陷:只有当前值,没有原因与历史

“当前目标为空”不能说明是从未感知、资格过滤、路径不可达还是主动清空;“MoveTo Failed”也缺少 Filter 版本、NavMesh 版本与失败原因。大量逐帧字符串日志既昂贵又难聚合,发生 DS 尖峰时反而加重问题。把完整诊断数组复制给客户端同样不可取,会污染带宽和安全边界。

诊断还必须区分正常降级与故障。以合成配置为例,AI 因低模拟层级 500 ms 才决策一次是预算选择,不应被报告为卡顿;高优先级战斗 AI 超过截止时间才是违约。该数字只解释诊断语义,不代表历史配置。没有 Tier、Deadline 和 BudgetDecision,性能问题与逻辑问题无法分离。

五、目标设计:每个 AI 保存有界因果环形缓冲

DS 为关键动作生成稳定 ID,并把结构化事件写入固定容量 Ring Buffer:时间、ActorIncarnation、DecisionId、阶段、结果、ReasonCode、耗时和预算层级。正常运行只保留内存计数与抽样;管理员或本地调试时按单个 AI 拉取最近事件。Gameplay Debugger 展示摘要,Visual Logger 投影空间事件,性能系统聚合 ReasonCode 和分位数。

void TraceDecision(AIContext& ai, DecisionEvent event)
{
    event.actorId = ai.stableId;
    event.incarnation = ai.incarnation;
    event.decisionId = ai.currentDecision;
    event.simTier = ai.simTier;
    event.serverTime = Clock::Now();
    ai.traceRing.Push(event); // fixed capacity, overwrite oldest
}

void OnPathResult(PathResult r, RequestToken token)
{
    TraceDecision(AI, {Phase::PathResult, r.reason, r.costUs});
    if (!AI.IsCurrent(token)) return TraceStale(token);
    Consume(r);
}

六、决策规则:观察面共享结构,不共享全部数据

观察面回答的问题最小字段
Gameplay DebuggerAI 现在为何停在此状态Tier、状态、目标、最后 ReasonCode
Visual Logger路径、感知与世界位置如何变化RequestId、位置、Filter/Nav 版本
Ring Buffer失败前后发生了什么Incarnation、关联 ID、阶段、结果、耗时
聚合指标哪类故障或预算最常发生ReasonCode、计数、P50/P95/P99、Tier
网络抓取某连接为何没看到表现Relevancy、Dormancy、StateRevision
自动化测试关键链路是否满足时限与不变量Deadline、状态序列、禁止事件

最有价值的 AI 日志不是“进入了某状态”,而是“哪次决策基于哪些事实,申请了哪次移动或攻击,最后以什么原因结束”。关联 ID 把一组引擎工具从截图能力提升为因果分析能力。