交互资格:客户端可以发现目标,DS 必须重新证明
自动关联目录:交互资格:客户端可以发现目标,DS 必须重新证明
核心判断:客户端发现“看起来可以交互”的目标,是为了低延迟提示;DS 证明“此刻允许修改玩法状态”,才是资格判定。两者可以使用不同精度,但不能共用一个布尔结果。
一、业务冲突:交互提示要立即出现,玩法结果又不能相信客户端
拾取、机关、座椅、拖拽和 NPC 对话都希望玩家一靠近就显示提示。若 DS 持续为每个玩家计算所有触发区,服务器碰撞和查询成本会随玩家数、交互物数增长;若完全相信客户端选中的目标,修改坐标、伪造身份或重放旧请求就可能绕过距离、占用和状态限制。
真正需要拆开的是两个问题:Candidate 回答客户端该提示谁,Eligibility 回答 DS 是否接受本次命令。候选允许偶尔误报,权威资格不能误放行。
二、实际实现:已有两条路径,但权威校验强度不一致
一条实际路径把 Shape Overlap 只留在本地端,DS 主动关闭对应碰撞以节省成本。角色维护进入范围的弱引用列表,按优先级和本地状态选择目标,再通过可靠 RPC 发送目标、操作类型、松散参数和时间戳;公共服务器入口主要过滤过旧请求,具体组件决定是否执行。
另一条路径由管理器统一路由,客户端先做可交互检查,必要时再发 RPC;服务器组件会用权威角色位置重新检查点是否位于交互 Box 内,并为网络误差扩大少量容差。这条路径已经证明:DS 不需要保留持续 Overlap,也能在提交时重建几何资格。
两条路径共存暴露的问题不是“有没有 Authority 判断”,而是公共命令契约没有规定必须校验哪些事实。有的实现只检查忙闲与角色状态,有的会重算范围;还有请求把玩家标识作为参数上传,而调用者身份本应从 UE Owning Connection 推导。
sequenceDiagram
participant C as 本地客户端
participant D as DS 命令入口
participant R as 规则/占用服务
C->>C: Overlap/射线生成候选并显示提示
C->>D: Command(TargetId, ActionId, ClientSeq, ObservedRevision)
D->>D: 从 Owning Connection 得到玩家身份
D->>R: 重建世界、距离、状态、冷却与占用资格
alt 全部成立
R-->>D: Reserve + Commit
D-->>C: Receipt(Accepted, Revision)
else 任一失败
D-->>C: Receipt(Rejected, Reason, CurrentRevision)
end三、值得保留:把持续感知成本留在客户端
交互组件默认不 Tick、只监听本地玩家 Overlap,DS 关闭无用触发碰撞,这个方向是对的。它把高频、允许近似的发现工作放到最需要即时反馈的一端,同时避免 DS 为纯提示维护大量接触对。
服务器提交时使用权威位置做 Box 检查并加入有限容差,也比复制客户端 Overlap 结果更稳健。Push Model 只在状态变化时标脏、角色交互状态携带服务器时间,同样值得保留。
四、结构性缺陷:时间新鲜不等于业务合法
只检查 RPC 年龄不能证明玩家仍在目标附近,也不能证明请求来自 payload 中声明的玩家。未来时间戳还可能产生负延迟;固定超时也没有区分高延迟可容忍动作与竞技动作。更关键的是,目标在客户端选中后可能已销毁、换代、被占用或迁到另一个 World。
资格判断散落在交互组件和脚本覆写中,还会形成 fail-open 风险:新交互物忘记补某项校验就能被调用。客户端与 DS 各自实现一套 CanInteract,条件迟早漂移;客户端版本只应用于提示,DS 版本才必须是完整规则。
五、目标设计:命令入口统一重建 Authority Facts
每个动作注册不可绕过的 InteractionPolicy。公共入口先完成身份、世界、目标代际、速率和 payload Schema 校验,再调用动作策略检查距离、视线、角色 Tag、对象状态、冷却与 Lease。客户端可复用同一份纯数据规则生成提示,但其结果只标记为 PredictedEligible。
Receipt Submit(Connection sender, InteractionCommand cmd)
{
Player player = Sessions.ResolvePlayer(sender); // 不读 cmd.playerId
Target target = Registry.Resolve(cmd.targetId, cmd.targetGeneration);
if (!player || !target || player.world != target.world) return Reject(InvalidIdentity);
if (!Schema.Validate(cmd.actionId, cmd.payload)) return Reject(InvalidPayload);
if (!RateLimit.Allow(player, cmd.actionId)) return Reject(TooFrequent);
AuthorityFacts f = CaptureFacts(player, target);
if (!Policies[cmd.actionId].Validate(f, cmd.observedRevision))
return RejectWithCurrentState(f);
return ExecuteUnderLease(player, target, cmd);
}诊断必须记录校验阶段、拒绝原因、权威距离、目标 Revision、命令年龄和验证耗时,而不是只记“交互失败”。
六、决策规则:客户端负责召回率,DS 负责零误放行
| 问题 | 客户端候选 | DS 权威资格 |
|---|---|---|
| 身份 | 本地 Pawn | Owning Connection/Session |
| 范围 | Overlap、射线、视锥,可带滞回 | 权威 Transform + 动作容差 |
| 目标 | 本地对象引用 | 稳定身份 + Generation + World |
| 状态 | 最近复制快照 | 当前权威 Revision |
| 占用/冷却 | 用于提示,可短暂过期 | 原子 Reserve 与提交时复查 |
| 失败 | 隐藏或修正提示 | 明确 Receipt,不产生副作用 |
这段实践应保留“客户端感知、DS 少做持续碰撞”的性能判断,但下一次应更早问:服务器如何用低成本事实重新证明本次动作,而不是把客户端已经选中目标误当成证明。