docs(spec): §5.6 触发条件改为弹数阈值 + 新增 §5.7 退避墙钟不免疫时间缩放
§5.6:把重新立项的触发条件从场景描述改为可直接计算的弹数阈值 —— 成本严格线性于 「同帧创建且首次选目标全部失败的归航弹数」约 9.5µs/颗,附 200/500/1000/1750 颗的 对照表,约 1750 颗为理论击穿点(MAX_BULLETS=2048 故容量内可达)。并注明 visited 类场景因 visited_targets 线性扫描,实测高于线性外推,实际击穿点更低。 §5.7 新增潜在项(休眠,不修):退避基于 Time.get_ticks_msec() 是墙钟,不受 Engine.time_scale 影响,而 TimeManager 正是为慢动作/冻结时间 Core 驱动 time_scale 的。 grep 确认 set_time_scale 当前零调用方,故今日无害;若落地 0.2× 慢动作,200ms 只跨 约 2.4 个物理帧而非 12,抖动摊薄失效,且表现为慢动作期间掉帧。 记录替代方案(触发时再实施):改用物理帧计数 HOMING_RETRY_FRAMES=12,免疫时间缩放、 略便宜、消除 ms↔帧量纲错配;项目已有现成的 TimeManager.game_tick 且注释明写不受 time_scale 影响,正合用。触发条件为 set_time_scale 出现第一个调用方。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -264,7 +264,30 @@ MODIFIER(`type 1`)的 meta schema 加:
|
|||||||
2. **它一次性、不复发。** 与被修掉的那个每 12 帧永久复发的尖峰性质不同 —— 后者是持续卡顿,前者最多是一次可忽略的掉帧。
|
2. **它一次性、不复发。** 与被修掉的那个每 12 帧永久复发的尖峰性质不同 —— 后者是持续卡顿,前者最多是一次可忽略的掉帧。
|
||||||
3. **压下它的代价是手感回退或语义变更。** 三条可行路径(首帧也抖动 / 连续失败 N 次后彻底放弃归航 / 给 `visited_targets` 设上限)分别牺牲「首次锁定即时」、改变归航语义、改变 bounce 语义。按 CLAUDE.md「先测量后优化,不做无凭据的过早优化」,为一个合成场景付这个代价不划算。
|
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)
|
## 6. 非目标(YAGNI)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user