diff --git a/docs_dev/specs/2026-07-30-bullet-homing-design.md b/docs_dev/specs/2026-07-30-bullet-homing-design.md index a951447..37cb7e8 100644 --- a/docs_dev/specs/2026-07-30-bullet-homing-design.md +++ b/docs_dev/specs/2026-07-30-bullet-homing-design.md @@ -264,7 +264,30 @@ MODIFIER(`type 1`)的 meta schema 加: 2. **它一次性、不复发。** 与被修掉的那个每 12 帧永久复发的尖峰性质不同 —— 后者是持续卡顿,前者最多是一次可忽略的掉帧。 3. **压下它的代价是手感回退或语义变更。** 三条可行路径(首帧也抖动 / 连续失败 N 次后彻底放弃归航 / 给 `visited_targets` 设上限)分别牺牲「首次锁定即时」、改变归航语义、改变 bounce 语义。按 CLAUDE.md「先测量后优化,不做无凭据的过早优化」,为一个合成场景付这个代价不划算。 -**重新立项的触发条件**:若日后出现「单帧创建数百颗以上归航子弹」的真实玩法(如某个 Endless 全局词条或 Boss Rush 构筑),且实测首帧超 16.67 ms,则按上述三条路径之一立项处理。 +**重新立项的触发条件(按弹数算,不要按场景猜)**:成本严格线性于「**同一帧**创建、且首次选目标**全部失败**的归航弹数」,约 **9.5 µs/颗**。据此可直接算出击穿点,无需揣摩「典型齐射规模」: + +| 同帧首选失败的归航弹数 | 该帧额外耗时 | 判断 | +| --: | --: | :-- | +| 200 | ≈ 1.9 ms | 无感 | +| 500 | ≈ 4.8 ms | 可接受 | +| 1000 | ≈ 9.5 ms | 接近半帧,警戒 | +| **1750** | **≈ 16.7 ms** | **满帧预算,必然掉帧** | + +即**约 1750 颗**是理论击穿点(`MAX_BULLETS = 2048`,故容量内可达)。注意实测 1500 颗 `visited` 场景为 22.0 ms,高于线性外推的 14.3 ms —— 差额来自 `visited_targets` 的线性 `in` 扫描,故 ② 类场景的实际击穿点更低。 + +**当同帧齐射的归航弹数逼近千级时重新立项**,按上述三条路径之一处理。 + +### 5.7 潜在项(当前休眠,**不修**):退避用墙钟,不免疫时间缩放 + +退避基于 `Time.get_ticks_msec()` —— **墙钟,不受 `Engine.time_scale` 影响**。而 `TimeManager` 正是为「慢动作 / 冻结时间 Core 效果」驱动 `Engine.time_scale` 的(`time_manager.gd:4,16`)。 + +**当前不是问题**:grep 确认 `TimeManager.set_time_scale` 全项目**零调用方**(仅 `time_manager.gd:23` 定义),慢动作 Core 尚未落地。 + +**若日后落地**:设 0.2× 慢动作,200 ms 墙钟只跨约 2.4 个物理帧而非 12,每帧重选量涨约 5×,§5.4 的抖动摊薄基本失效 —— 且表现为**慢动作期间掉帧**,即最不该掉帧的时候掉。 + +**替代方案(触发时再实施,现在不动)**:改用物理帧计数而非墙钟 —— `HOMING_RETRY_FRAMES: int = 12`,抖动公式变成帧原生的 `(bullet_idx * HOMING_RETRY_FRAMES) / maxi(1, _active_count)`。天然免疫时间缩放、比 `Time.get_ticks_msec()` 略便宜,并消除 ms ↔ 帧的量纲错配(现在「200 ms ≈ 12 帧」只在 60fps 且 `time_scale == 1` 时成立)。本项目已有现成计数器 `TimeManager.game_tick`(`time_manager.gd:9,15`),其注释明写「不受 time_scale 影响」,正合用。 + +**触发条件**:慢动作 / 冻结时间 Core 落地时,即 `set_time_scale` 出现第一个调用方。 ## 6. 非目标(YAGNI)