世界候选收集与扫掠选择控制

本批结果

新增 NativeCollisionArea 与 NativeWorldSweep,连接“静态/动态候选准备 → 初始查询/静态扫掠/动态物件扫掠 → 最近命中选择”。

  • 4,096 组原版候选准备参考一致,每组执行两次准备以检查缓存与容量行为。
  • 1,170 组原版世界扫掠控制参考一致,覆盖三个 x87 精度。
  • 核心累计 1,388 条断言、217 项相关 Python 回归通过;Godot C# 构建零警告、零错误;coresimlint 通过。
  • 现有 21 项 GPU/资源回归重新运行通过。

这里完成的是候选收集和扫掠的控制层。实际 5E05E0、467C80、622670 几何查询仍是必须提供的宿主操作,不能用未命中默认值代替,也未宣称整个世界遮挡完成。

5F2C70:先清缓存,再检查主碰撞数据

执行顺序:

  1. 清静态面候选 count。
  2. 请求静态容量 128。
  3. 清动态 Room 候选 count。
  4. 检查 CLevel +92 主对象。
  5. 检查主对象 +148 碰撞数据。
  6. 读取静态索引包装对象;有树根时查询四叉树。
  7. 按原顺序扫描动态 Room 列表。

缺主对象或主碰撞数据时,两份缓存已经被清空,动态 Room 也不会作为备用路径继续扫描。

“静态包装对象缺失”和“包装对象里没有树根”不同:后者允许继续扫描动态列表,前者不能默认为空树来隐藏缺输入。

容量等于 128 也会重新分配

原函数检查的是刚清零的 count,因此请求容量 128 的分支总会执行。

7FCA70 的条件是 requested >= capacity:

  • 容量小于 128:分配 128。
  • 容量等于 128:仍重新分配。
  • 容量大于 128:保留现有存储。

动态候选清空后保留容量,追加时按 0→1、随后翻倍增长。核心保留这些可观察容量行为与分配次数,不用 List 默认的 0→4 扩容替代。

未使用容量中的原生垃圾内容不被读取或伪造;实现针对正常 count 范围,仍不是原 CRT 堆分配器的完整替身。

动态 Room 过滤

依次要求:

  • 有碰撞模型。
  • CollisionEnabled 非零。
  • 对象可见字节 +105 非零。
  • 节点 propagated 可见性非零。
  • VisualBake=false。
  • AlwaysBake=false 且 CollisionBake=false。
  • 有 AABB。
  • 与查询区完整三维相交。

动态物件需要 Y 轴相交;静态四叉树只做 X/Z 筛选,两条规则不能合并。

输入列表读取 count 是实时的,索引超过 capacity 时读取 slot 0。这会保留重复候选,不能去重。动态输入列表本身的空 Room 槽会被原函数解引用,核心明确报缺输入;不要与后续扫掠阶段会跳过的空候选槽混淆。

5E2150 的三个查询阶段

  1. 可选初始查询 5E05E0。
  2. 静态候选扫掠 467C80。
  3. 动态 Room 候选逐个调用 622670。

初始查询使用第二个半径参数。静态和动态扫掠使用第一个半径参数。导航调用传入的跳过标志会跳过初始阶段,但不会跳过静态/动态阶段。

原静态扫掠的函数返回值被忽略;是否有结果取决于它写入的独立距离 marker 是否仍是 99999。

扫掠 AABB 先用起点初始化 min/max,以严格比较纳入终点,然后在三个轴分别按第一个半径扩边,并按原 x87 精度/float32 存储。

最近命中与输出写入不是同一件事

最近距离初值为 99999。

初始查询命中时,直接计算并存入其距离,复制三个结果向量,并把辅助向量写成 (1,1,1)。

静态/动态阶段仅在新距离严格更小时替换三个结果向量。比较使用 Ogre 返回的保留 x87 距离,而已有最近距离是 float32;不能先把两边都转成普通 float 后再比。

因此即使两个 float32 距离相同,保留的 x87 值仍可能影响选择。动态列表 count 是实时读取,回调加入的候选会继续被访问;空动态候选槽则跳过。

共享 surface 的后写入行为

surface 指针直接传给每个查询。后面的查询即使没有胜过当前最近命中,也可能改写 surface;不会自动恢复最近命中的旧 surface。

例如原参考中:

  • 静态点在 x=1,成为最近命中。
  • 动态点在 x=3,不替换命中点。
  • 动态查询仍把 surface 改写成自身值。

核心保留这个行为,没有“修正”为 surface 必须与最近命中点一致。

辅助向量也独立:静态查询直接拿到它的输出指针;静态结果较远时仍可写入。初始或动态候选成为选中结果时,控制器写入 (1,1,1)。

接口已区分只允许写 surface 的初始/动态查询和能写辅助向量、marker 的静态查询,避免后续实现误写不存在的参数。

未命中与特殊值

  • 入口总将动态命中标记清零。
  • 未选择命中时,调用方原有的三个结果向量保留,不补成零。
  • surface/辅助向量的查询副作用仍可能留下。
  • 结果是否成功由最终最近值与 99999 的比较决定,不简单等同于“任意子查询返回过 true”。
  • 非有限命中点的原始控制行为也纳入参考;初始 NaN 距离、静态 NaN marker 与动态 NaN 距离不能全部按同一规则处理。

原临时三个 Vector3 在入口确实被默认构造为零。核心用独立 scratch 与可空的调用方输出区分“已知初始化”和“尚未被写入的调用方存储”。

空碰撞列表包围盒的更正

前批因为尚未检查构造器,对新建空碰撞列表的包围盒采取了缺输入拒绝。这一保守行为现已修正。

原游戏自带 Ogre Vector3 默认构造 10001CC0 执行 fldz 和三次写入,明确清零。

  • 新 CCollisionList 的 min/max 因此初始为零。
  • 469430 在顶点为空时直接返回。
  • 新空列表保留零包围盒。
  • 已有列表清空顶点后,保留此前的包围盒,不自动归零。

NativeBounds.FromVertices 用于新建列表;UpdateFromVertices(previous, vertices) 保留已有列表的空输入语义。原生顶点包围盒参考扩展为 7 组,并实际执行原默认 Vector3 构造。旧 Wiki 中“空列表初始化未知”的结论已更正。

验证范围

候选参考执行原 5F2C70、向量清空/reserve/追加、5EFC80 及原过滤辅助函数。分配存储、getter 与 memmove 为明确边界。

扫掠参考执行原 5E2150、6834B0 及实际 Ogre 向量减法、长度、拷贝和构造。三类形状查询为给定结果及输出写入的参考边界。

组合测试将实际候选准备输出交给核心扫掠控制器,验证重复动态候选、静态先选中但 surface 后写入,以及回调增长列表。没有借此声明底层球/三角形或物件扫掠已经实现。

原 EXE SHA256: 186472c3057b38f4cdff4696959a943c396ae6166995f7418997b5ea853e8a5e

OgreMain.dll SHA256: 974c1dc77ea2818276cc7eb94a8b95b2ccf8595e28fa3f14b2cd6d516fdb67a1

产物:

  • core_sim/NativeCollisionArea.cs、NativeWorldSweep.cs。
  • core_sim/NativeBounds.cs 的空列表修正。
  • tools/build_native_collision_area_oracle.py、native_collision_area_oracle.c。
  • tools/build_native_world_sweep_oracle.py、native_world_sweep_oracle.c。
  • core_sim.tests/NativeWorldQueryChecks.cs、tests/test_native_world_query.py。
  • build/native_collision_area_oracle/results.json。
  • build/native_world_sweep_oracle/results.json。
  • build/native_world_query_core.log、native_world_query_python.log、native_world_query_godot.log。
  • build/native_world_query_visual.log:GPU/资源回归。

剩余与目标

后续进展:网格与Room Piece几何扫掠已实现并对照 4676A0/467C80/622670,以下为本篇交付时的剩余状态;初始查询与完整世界上下文仍未完成。

继续实现实际几何扫掠 467C80/4676A0、物件封装 622670 及初始碰撞对象查询,并补原版四叉树构造和真实世界对象上下文,再接导航贡献与攻击遮挡。

原版模型、动画、缺导出资产、攻击型 proc 和全武器验收仍在目标中。武器可构造覆盖仍是 1,053/1,419,没有用本批基础设施工作提高武器完成数。

按 godot-master 的分层要求,候选与选择逻辑在核心层,实际几何与场景数据由明确宿主提供;没有用 Godot 物理或“总是未命中”替代缺失实现。快照 v29 未改,世界持久化仍待完整接入。

main 原地工作,未提交、未 attach 原游戏,默认存档 SHA256 未变: 9840fe858b6e311c73f8c2d4ed49912d59d71365f6278392811730e012d32a7f