共享时钟:ServerTime 不是把本地 Now 复制给客户端

自动关联目录:共享时钟:ServerTime 不是把本地 Now 复制给客户端

核心判断:跨端时间不是一个浮点数,而是“权威 epoch + 单调时钟映射 + 误差边界”;没有这三项,冷却、动画和 UI 倒计时无法判断自己是否仍在同一时间域。

一、业务冲突

客户端需要立即表现技能、倒计时和动画,DS 则负责判定截止时间。两端 Now() 起点不同,墙上时钟还可能跳变;把服务器时间偶尔复制给客户端,只能得到一次带网络延迟的样本,重连后甚至可能把上一台 DS 的 deadline 带到新权威域。

二、实际实现

实际代码用 World 级时钟服务区分协议兼容与数值 Ready。DS 创建随机 ClockEpoch,并把墙上时间只用作 epoch 起点,后续全部基于单调时钟。客户端执行 Hello/Welcome,再用四时间戳 Probe/Sample 估算偏移和往返延迟;样本绑定连接身份、序列号和 epoch,超时、乱序、旧连接和异常样本会被拒绝。

时钟没有“本地时间兜底”:未协商或估计器尚未稳定时,读取直接失败。运行中发现 epoch 改变也不会悄悄重置 deadline,而是要求重新进入会话。

三、值得保留

Fail closed 很重要。一个看似连续但来源错误的时间,比暂时不可用更危险。连接身份、协议版本、epoch 和样本序列共同防止重连旧包污染;稳定后降低采样频率,未稳定时加快探测,也兼顾了网络成本与收敛速度。

四、结构性缺陷

共享时间已形成基础设施,但消费者仍可能直接读取 World Time、平台时间或网络角色自带时间,造成多个时间真相。API 只返回一个 double 时,也丢失了当前误差、样本年龄和 epoch,调用方无法决定某个玩法是否允许在低置信度下继续。

五、目标设计

让时间点携带域与置信度:

struct AuthorityTime {
    AuthorityId authority;
    ClockEpoch epoch;
    double seconds;
    double uncertainty;
    SampleRevision sample;
};

bool IsExpired(Deadline d, AuthorityTime now) {
    return d.epoch == now.epoch
        && now.seconds - now.uncertainty >= d.seconds;
}

玩法 deadline、动画虚拟时间和表现事件保存 epoch;客户端插值使用独立 PlayoutClock,不反向修改权威时钟。高风险判定可要求更小 uncertainty,纯表现允许降级。

六、取舍与验证

暴露不确定度会让 API 更复杂,却能明确“不可用、可表现、可权威判定”三种状态。测试应注入固定偏移、漂移、抖动、非对称延迟、乱序、样本丢失、连接重建和 epoch 切换;同时审计所有跨端倒计时,确保不再混用墙上时间、World Time 与权威时间。