docs: 计划回填实际执行的验收脚本 + 订正权威文档自相矛盾处 + 性能数字改区间
【计划 Task 4】原稿四个脚本有缺陷,照它复跑会得到假失败与空通过,现已替换为 实际执行并通过的版本,每处旁注「原脚本为何有缺陷」,并勾上完成的复选框: - Step 2 敌人摆位 (400,200) 距原点 447px 已出 homing_range=400,实测 FAILS=1; 这是测试摆位错误而非实现缺陷(精确距离过滤行为正确)。改到 (300,150)。 - Step 6 内联复刻了折叠逻辑与商店谓词——在测自己;改调真实 _apply_modifier 与 ShopManager._available_pool(),另补 compile_wand→execute_compiled 端到端。 - Step 7 无墙钟起搏,40 帧全停在退避窗内、守卫沦为空断言;且几何让子弹直穿 敌人簇,多计约 1.5ms 碰撞开销。改为 60fps 起搏 + spec §5.2 环形摆位, frame1 与稳态峰值分开报。 - Step 8 只对源码 grep 字符串,不是往返;改为真实实例化 spell_tab 对真实 spells.json 做 8 条 type-1 的回读→写回比对。 回溯表补「观测方式」列,标出⑥是唯一一条状态推断而非直接观测。 【architecture_design.md】同一文档内自相矛盾: - CastStats 块 homing_force(「归航强度,向最近敌人偏转」)与 §4.2 新写的 「最大转向角速度 + 锁定式目标」冲突 → 订正为 homing_add(弧度/秒); 顺带订正确证过的 pierce_add / bounce_add(原写 *_count)。该块其余字段 仍有既有漂移,加 ⚠️ 注明未核对、以 cast_stats.gd 为准,不扩大改动范围。 - ProjectileDef 的 homing_force 删除——projectile_def.gd 无任何 homing 字段, 归航冷数据由 _push_projectile 直接写入 BulletManager。 - §4.2 bounce 协同散文补上实际存在的 if cold.has("homing_strength") 前置守卫。 - get_nearest_pos 代码片段:_enemy_count → _active_count(前者不存在)、 未命中返回值 Vector2.ZERO → origin、删除不存在的 _visible_flags 过滤。 【spec】 - §4 第⑥条措辞「不触发 query_circle」字面为假(_check_collision 每帧无条件 发一次),改为「不因归航触发」,并写明这是唯一一条间接验证及其封闭性论证。 - §5.5 峰值改为区间表述(① 4~5ms / ② 5~7ms @1500),注明编辑器/调试构建、 运行间离散(同场景三次得 5.61/5.09/7.29ms),判定回归看是否出现周期性复发 尖峰与是否越过 16.67ms,勿拿单值比对;并记录几何对测量的影响。 - §5.4 勘误:「1493 个互异到期值」不可能成立(jitter 只能产出约 200 个整数值, 同帧失败又共用同一 now),复测为跨度 223ms / 222 个互异值,结论不变。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,9 @@ graph TD
|
||||
|
||||
#### A. 基础单元 (SpellNode)
|
||||
这是所有"部件"的基类。
|
||||
|
||||
> **⚠️ 本块与实现存在既有漂移(2026-07-30 部分订正)**:`pierce_add` / `bounce_add` / `homing_add` 三项已按 `scripts/domain/spell_system/cast_stats.gd` 实测订正(原写 `pierce_count` / `bounce_count` / `homing_force`)。**其余字段仍未核对**:实现里没有 `projectile_speed` / `spread_angle` / `projectile_size` / `range_mult`,实际为 `spread_count` / `speed_mult` / `radius_mult` / `lifetime` / `multicast_count`。此漂移早于本次归航改动,未一并订正以免超出改动范围 —— **以 `cast_stats.gd` 为准**。
|
||||
|
||||
```gdscript
|
||||
# cast_stats.gd - MODIFIER 节点的修改目标;通过 SpellContext.stats 访问
|
||||
class_name CastStats
|
||||
@@ -82,17 +85,18 @@ var damage_add: float = 0.0 # 累加伤害加成(所有 Dmg+ 修正
|
||||
var damage_mult: float = 1.0 # 乘算伤害系数(暴击、元素弱点等乘入)
|
||||
var projectile_speed: float = 600.0 # 弹道飞行速度(像素/秒)
|
||||
var spread_angle: float = 0.0 # 散射角度(度,0 = 直线)
|
||||
var pierce_count: int = 0 # 穿透次数(0 = 首次命中即销毁)
|
||||
var bounce_count: int = 0 # 弹射次数(0 = 不弹射)
|
||||
var homing_force: float = 0.0 # 归航强度(0 = 直线,>0 向最近敌人偏转)
|
||||
var pierce_add: int = 0 # MODIFIER 累加的穿透次数(叠加到 ACTION 自带 pierce)
|
||||
var bounce_add: int = 0 # MODIFIER 累加的弹跳次数(叠加到 ACTION 自带 bounce)
|
||||
var homing_add: float = 0.0 # MODIFIER 累加的归航【最大转向角速度】,单位 弧度/秒
|
||||
# (叠加到 ACTION 自带 homing;语义见 §4.2 Homing 受限角速度制导)
|
||||
var projectile_size: float = 1.0 # 弹道大小系数(影响碰撞半径与贴图缩放)
|
||||
var range_mult: float = 1.0 # 射程乘算系数(弹道消失前飞行距离倍率)
|
||||
var crit_chance: float = 0.0 # 本次施法暴击率追加量(0.0~1.0,叠加而非覆盖)
|
||||
|
||||
func reset() -> void:
|
||||
damage_add = 0.0; damage_mult = 1.0; projectile_speed = 600.0
|
||||
spread_angle = 0.0; pierce_count = 0; bounce_count = 0
|
||||
homing_force = 0.0; projectile_size = 1.0; range_mult = 1.0; crit_chance = 0.0
|
||||
spread_angle = 0.0; pierce_add = 0; bounce_add = 0
|
||||
homing_add = 0.0; projectile_size = 1.0; range_mult = 1.0; crit_chance = 0.0
|
||||
|
||||
# spell_context.gd - 使用 RefCounted 避免 GC 压力
|
||||
class_name SpellContext
|
||||
@@ -192,7 +196,10 @@ var damage_type: int = DamageType.PHYSICAL # DamageType 枚举(见上方定
|
||||
var speed: float = 600.0 # 初速度(像素/秒)
|
||||
var direction: Vector2 = Vector2.RIGHT
|
||||
var spread_angle_rad: float = 0.0 # 在 direction 基础上的随机偏转幅度
|
||||
var homing_force: float = 0.0 # 每帧转向力(0 = 直线)
|
||||
# 注:归航【不】经 ProjectileDef 传递。`projectile_def.gd` 无任何 homing 字段 ——
|
||||
# SpellEvaluator._push_projectile 直接把 homing_strength / homing_range /
|
||||
# homing_target_id 写进 BulletManager 的冷数据字典(详见 §4.2)。
|
||||
# 原此处的 `homing_force`(「每帧转向力」)语义与实现均不存在,2026-07-30 删除。
|
||||
var acceleration: float = 0.0 # 速度加速度(可为负,做减速球)
|
||||
|
||||
# ── 生命周期 ──────────────────────────────────────────────
|
||||
@@ -675,7 +682,13 @@ func _node_has_tag(node: SpellNode, tag_pattern: String) -> bool:
|
||||
|
||||
> **⚠️ 失败退避必须带抖动**(spec §5.4,本次最值得记住的一条):重选**失败**时若不留记录,该子弹余生每帧都会重跑 `query_circle(r=400)`(半径 400 覆盖约 196 格 + 一次 `PackedInt32Array` 分配 ≈ 一百多次碰撞查询)。但**朴素固定退避会把子弹锁进同相、而非打散** —— 同帧失败的子弹拿到同一个 `now`,一个退避周期后又整齐地一起重试,尖峰每 12 帧永久复发。故抖动到期时刻,用「`bullet_idx` 在活跃弹数中的占比」铺满恰好一个退避周期(`HOMING_RETRY_MS` 一值两用:既是基础退避时长也是抖动窗口宽度)。实测数据见 spec §5.2 / §5.5,此处不复述以免两套数字分叉。
|
||||
|
||||
> **与 bounce 协同**:bounce 在命中瞬间硬重定向到「最近未访问敌」,若不协调,下一帧 homing 就会覆盖它、使 bounce 失效。语义定为**bounce 负责选目标、homing 负责追上去**:bounce 重定向块内改写 `cold["homing_target_id"] = next_id` 并 `cold.erase("homing_retry_at")`(新目标有效,须撤销过期退避)。
|
||||
> **与 bounce 协同**:bounce 在命中瞬间硬重定向到「最近未访问敌」,若不协调,下一帧 homing 就会覆盖它、使 bounce 失效。语义定为**bounce 负责选目标、homing 负责追上去**。bounce 重定向块内:
|
||||
> ```gdscript
|
||||
> if cold.has("homing_strength"): # 前置守卫:纯 bounce 弹不写 homing 键
|
||||
> cold["homing_target_id"] = next_id
|
||||
> cold.erase("homing_retry_at") # 新目标有效,须撤销过期退避
|
||||
> ```
|
||||
> `has("homing_strength")` 这个守卫不可省 —— 没有它,纯 bounce 子弹会被塞进一个永不被消费的 `homing_target_id`;而 `erase` 不可省是因为退避键若残留,子弹会对着 bounce 刚给的有效目标拒绝转向(该缺陷在实现期评审中实测复现并修复)。
|
||||
|
||||
* **EnemyManager 对外查询接口**:以下函数供 BulletManager、DropManager、BossManager、AudioManager 调用,均为 GDScript 侧接口(轻量读操作,非热路径):
|
||||
|
||||
@@ -713,17 +726,16 @@ func _node_has_tag(node: SpellNode, tag_pattern: String) -> bool:
|
||||
func get_nearest_pos(origin: Vector2, max_dist: float = 9999.0) -> Vector2:
|
||||
# BulletManager.get_nearest_enemy_pos() 调用;直接线性扫描 SoA _data,O(M)
|
||||
# 仅在发射时调用,非每帧热路径(homing 走 get_pos_by_id 锁定目标,不经此函数)
|
||||
var best_pos := Vector2.ZERO
|
||||
var best_pos := origin # 无敌人时返回 origin(非 Vector2.ZERO)
|
||||
var best_dist := max_dist * max_dist # 比较平方距离,避免 sqrt
|
||||
for i in range(_enemy_count):
|
||||
if _visible_flags[i] == 0: continue # 跳过屏外(可选:也可全扫)
|
||||
for i in _active_count: # _active_count,不存在 _enemy_count
|
||||
var px := _data[i * ENEMY_STRIDE + 0]
|
||||
var py := _data[i * ENEMY_STRIDE + 1]
|
||||
var d2 := (px - origin.x) * (px - origin.x) + (py - origin.y) * (py - origin.y)
|
||||
if d2 < best_dist:
|
||||
best_dist = d2
|
||||
best_pos = Vector2(px, py)
|
||||
return best_pos
|
||||
return best_pos # 实现全扫存活敌人,不按 _visible_flags 过滤
|
||||
```
|
||||
|
||||
> **EnemyManager SoA 完整槽位布局**(权威来源:`implementation_plan.md §2.3.C`,P6-N45):
|
||||
|
||||
Reference in New Issue
Block a user