perf(homing): 退避到期时刻加抖动,消除每 12 帧复发的集体重试尖峰
二次发现:固定 200ms 退避并没有消除尖峰,只是让它变成周期性复发。按真实 60fps 起搏复测,1500 弹 visited 场景逐帧耗时在第 1/13/25/37 帧稳定出现 ~21ms,永不衰减: 21.9 2.8 2.9 ... 3.1 | 21.8 2.8 ... | 21.0 2.9 ... | 20.9 3.7 3.9 3.8 根因是朴素退避会把子弹锁进同相而非打散 —— 同帧失败的子弹拿到同一个 now,到期时刻 完全相同,一个退避周期后又整齐地一起重试;同波齐射本就天然同相,此后再无机会错开。 摊薄后的均值数字恰恰掩盖了这一点。 修法是抖动到期时刻,铺满 [200,400) ms ≈ 恰好一个退避周期 ≈ 12 帧。 用「bullet_idx 在活跃弹数中的占比」而非 bullet_idx % HOMING_RETRY_MS:后者在弹数少于 窗口宽度时只铺开 _active_count 毫秒(20 颗弹 → 20ms ≈ 1.2 帧,等于没打散)。 实测打散质量:1500 颗同帧集体失败 → 互异到期值 1493 个、覆盖 14 帧、单帧最多 120 颗 (理想 125)。 单帧峰值(60fps 起搏 40 帧,稳态即排除初次锁定帧): ① 全射程外 1500 13.6~15.7ms 复发 → 4.40ms 300 3.2ms → 0.92ms ② 全 visited 1500 20.9~21.9ms 复发 → 5.61ms 300 4.6ms → 1.15ms 稳态超 16.67ms 的帧数均为 0/39。 首次锁定即时的行为未受影响(抖动只作用于失败后的重试),速率守恒、单帧转角上限、 退避窗口内零查询、到期后重试成功并 erase 键,断言全绿。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,10 @@ extends Node
|
||||
|
||||
const BULLET_STRIDE: int = 12 # 禁止裸整数 12(跨切片约束)
|
||||
const MAX_BULLETS: int = 2048 # 预分配 SoA 大小
|
||||
const HOMING_RETRY_MS: int = 200 # 归航重选失败后的退避时长(60fps 下把最坏情况摊薄约 12×)
|
||||
# 归航重选失败后的退避时长(60fps 下把最坏情况摊薄约 12×)。
|
||||
# 一值两用且刻意耦合:既是基础退避时长,也是抖动窗口宽度 —— 到期时刻均匀铺满
|
||||
# 「恰好一个退避周期」正是消除集体重试尖峰所需的性质,拆成两个常量反而会掩盖这层关系。
|
||||
const HOMING_RETRY_MS: int = 200
|
||||
|
||||
# SoA 热数组(由 BulletManagerCs 直接读写)
|
||||
var _data: PackedFloat32Array = PackedFloat32Array()
|
||||
@@ -249,7 +252,16 @@ func _apply_homing(bullet_idx: int, base: int, delta: float) -> void:
|
||||
cold["homing_target_id"] = tid
|
||||
_bullet_contexts[bullet_idx] = cold
|
||||
if tid < 0:
|
||||
cold["homing_retry_at"] = now + HOMING_RETRY_MS
|
||||
# 抖动「到期时刻」而非让所有子弹共用固定 HOMING_RETRY_MS:同帧集体失败的子弹
|
||||
# 拿到同一个 now,退避期完全一致,12 帧后又整齐地一起重试 —— 退避会同步而非
|
||||
# 打散,同波齐射天然同相故尖峰永不消散(实测 1500 弹 visited 场景 40 帧里 4 帧
|
||||
# 达 22.5ms,直接击穿 16.67ms 帧预算)。
|
||||
# 用「bullet_idx 在活跃弹数中的占比」而非 bullet_idx % HOMING_RETRY_MS:后者在
|
||||
# 弹数少于窗口宽度时只铺开 _active_count 毫秒(20 颗弹 → 20ms ≈ 1.2 帧,等于没
|
||||
# 打散),占比式则不论弹数多少都铺满整窗。取模仅作越界兜底(循环内 bullet_idx
|
||||
# < _active_count 已保证不越界),使到期时刻无条件落在 [200, 400) ms。
|
||||
var jitter: int = (bullet_idx * HOMING_RETRY_MS) / maxi(1, _active_count)
|
||||
cold["homing_retry_at"] = now + HOMING_RETRY_MS + jitter % HOMING_RETRY_MS
|
||||
return
|
||||
cold.erase("homing_retry_at")
|
||||
tpos = EnemyManager.get_pos_by_id(tid)
|
||||
|
||||
Reference in New Issue
Block a user