初始碰撞对象查询与完整几何世界扫掠

本批结果

补齐 5E05E0 的双链表查询及 5125F0 的对象几何扫掠,并通过 NativeGeometryWorldSweepHost 将初始、静态和 Room 三条实际几何路径接回 NativeWorldSweep。

  • 1,680 组完整初始查询:输出、选中对象和 getter 调用顺序一致。
  • 1,680 组独立对象扫掠:输出和 getter 顺序一致。
  • 1,680 组整条世界几何查询:实际执行初始对象、静态网格和动态 Room,没有形状结果替身;所有输出逐位一致。
  • 各组覆盖 x87 24/53/64 位精度;候选命中/未命中、等距、链表变化、偏移物件和非单位四元数均纳入。
  • 世界测试包含动态 Room 更近并替换最近命中的情况,不只覆盖静态/初始胜出。

核心累计 1,406 条断言、232 项相关 Python 回归通过;Godot C# 构建零警告、零错误,coresimlint 通过。快照仍为 v29。

这是明确输入域的几何链完成,不是完整地图装载或全武器实机验收完成。

5E05E0:两条链表

函数真实参数为 this + start、end、radius、adjusted 输出、point 输出、normal 输出、surface 输出、可空 selectedObject 输出、filtered。旧 IDA 反编译的栈签名和变量引用已错位,不能直接照抄伪代码。

顺序访问 level+140 和 level+144 指向的链表;每个结点是 object 指针及 next 指针。

项目第一条链 level+140第二条链 level+144
基础条件object+0x189 和 +0x19B 非零相同
filtered=false允许查询允许查询
filtered=true全部跳过额外要求 object+0x18A 非零
接受最近命中后的 surface2000100

5E2150 调用时 selectedObject=null、filtered=true。所以它不查询第一条链,只查询第二条链里通过三个标志门的对象。

更正:旧 native-registration-los 已确认这些字段及列表类型,本篇此前把它们重新列为未知不准确。接口现命名为 Collideable(DAT COLLIDEABLE,+189)、CollisionActive(运行时开关,+19B)、Blocks(DAT BLOCK,+18A)。两条链分别为 CCharacter 和 CItem,不是 Room 烘焙标志;完整世界成员及活动位生命周期仍需接线。详见碰撞属性与链表接线

遍历和输出规则

  • scratch 的三个 Ogre Vector3 入口清零,nearest=99999。
  • 每个通过门的对象调用 5125F0,ignoreFlags=false。
  • 距离使用 f32(point-start) 的原 Ogre 长度,比较保留 x87 结果与已存储的 f32 nearest。
  • 只有严格更近才接受;等距保留先命中的对象,第二条链不自动覆盖第一条链。
  • 接受时先更新可选 selectedObject,再更新 nearest、surface 和三个输出。
  • 5125F0 不接收 surface 指针;它的模型面 surface 不会泄漏到本层。这里与 5E2150 的共享 surface 后写入语义不同。
  • 未命中时保留调用方已存在的 selectedObject、向量和 surface。
  • 返回 nearest != 99999,不使用另外的任意命中旗标。

next 在当前查询完成后读取,不复制链表。第二条链的 head 也在第一条链完成后才读取。测试在模型 getter 中追加第一链结点、更换第二链 head,并改变当前结点 object 指针;原函数接受命中时会重新读取结点的 object,用于 selectedObject。这些行为都保留。

5125F0:对象几何入口

顺序与前置条件

  1. 调用 object 虚表 +0x11C,确认存在碰撞模型。
  2. 检查 +0x189/+0x19B;ignoreFlags 可以绕过这两个门,但不能绕过模型不存在。
  3. 求 start/end 逐轴 min/max,查询盒扩张 2×radius。两倍半径在 x87 中保留,减/加后分别存 f32。
  4. 先调用虚表 +0x140 取得 bounds maximum,再调用 +0x13C 取得 minimum,做完整 XYZ 包围盒相交。
  5. 通过后才取节点派生四元数,建立 Matrix4(Quaternion) 并求逆。

没有 Room Piece 的 end-X/Z 50 距离门,也没有 Room 的 VisualBake/AlwaysBake/CollisionBake 门。

原点和附加偏移

  • 调用虚表 +0x110 查询偏移对象。
  • 若非空,再调用一次 +0x110,并对这第二次返回的对象取 61DCE0(useDerived=false) 保存位置。
  • 偏移对象为空时使用 Ogre Vector3::ZERO。
  • 再对当前对象取 61DCE0(useDerived=true),以原 Ogre 向量加法与偏移相加,形成 effective origin。

不能缓存第一次 +0x110 返回值代替第二次读取,也不能把偏移位置作父层级旋转/缩放。GetOffsetObject、GetPosition 等是明确的宿主接口,核心不会猜测其来源。

原 DLL 的 Vector3::ZERO 导出位于 0x105ECC60,属于 .data 的 PE 零填充尾部。本次机器码参考按 PE loader 规则映射这段 BSS,并校验导出仍位于未初始化区;没有越过磁盘已初始化数据去读假内容。

变换与扫掠

  • start/end 先减 effective origin,再乘四元数矩阵的逆矩阵。
  • 不使用保存的 scale,也不缩放 radius。参考对象刻意填 scale=(2,3,4),结果仍由无 scale 路径得到。
  • 不能复用 RoomTransform(q, unitScale) 去隐式多做一次矩阵乘法;原版直接构造四元数矩阵。
  • 再次调用 +0x11C 取得当前模型,然后执行 4683F0 → 4682D0 → 4676A0。
  • 4683F0 把面 surface 写入自己的栈槽,4682D0 把辅助向量写入自己的 scratch;均不传给上层。
  • 命中点和 adjusted 乘正矩阵并加 effective origin。
  • normal 也用 Matrix4-vector 乘法,不用 Quaternion-vector;不加平移、不归一化。

由于 Ogre 两种向量乘法的中间舍入不同,即使四元数相同,也不能按数学等价随意互换。

对比 Room Piece

项目初始对象 5125F0Room Piece 622670
外层查询盒2×radius 扩张没有这一对象盒门
距离门无 50 单位门end 与原点的 X/Z 各不超过 50
局部变换仅四元数四元数 × 保存 scale
附加原点+0x110 对象的保存位置无此步骤
局部半径原 radiusradius / storedScale.Z
normal 回世界Matrix4-vectorQuaternion-vector
surface下层面值丢弃,上层按链给 2000/100面值或 NOPATH→100

核心接线

NativeInitialCollision.cs 提供:

  • INativeInitialCollisionLists/Link:实时链表读取边界。
  • INativeInitialCollisionObject/Position:模型、bounds、姿态和偏移 getter。
  • NativeRayMath.InitialObjectSweep:实际对象几何。
  • NativeInitialCollision.Run:双链过滤和最近选择。

NativeGeometryWorldSweepHost.cs 提供三路实际实现:

  1. ProbeInitial → NativeInitialCollision.Run(filtered=true)。
  2. SweepStatic → MeshSweep,传入原候选次序及世界扩张盒。
  3. SweepRoom → RoomPieceSweep(ignoreVisibility=false)。

INativeWorldSweepGeometryContext 必须提供静态几何、skip surface、两条初始对象链和 Room 输入。没有缺输入时返回“未命中”的默认实现,也不自动调用 Godot Physics。

Room 输入仍是既有 622670 显式输入内核的姿态/标志快照,实际原世界宿主的 getter 时序与对象生命周期绑定不是本次自动完成的。新 host 应按查询构造,不能当作世界注册器或持久化存档状态。

原生对照与范围

tools/native_initial_collision_oracle.c 直接执行:

  • 5E2150、5E05E0、5125F0。
  • 622670、4683F0、4682D0、4676A0、467C80。
  • 原平面/边缘算法和随游戏附带 Ogre 的矩阵/向量实现。

模型、bounds、姿态和偏移的 getter 是显式输入边界,同时记录顺序;查询中的链表修改也由明确的测试输入控制。几何结果从原指令计算,不是由 getter 预置命中。

世界参考使用四个对象、一份静态三角形网格、一份可平移的 Room 网格和已提供的候选列表。它证明这三段组合的控制/数值语义,不证明原地图对象全集、四叉树拓扑或世界装载完成。

原 EXE SHA256: 186472c3057b38f4cdff4696959a943c396ae6166995f7418997b5ea853e8a5e

OgreMain.dll SHA256: 974c1dc77ea2818276cc7eb94a8b95b2ccf8595e28fa3f14b2cd6d516fdb67a1

产物:

  • core_sim/NativeInitialCollision.cs、NativeGeometryWorldSweepHost.cs。
  • core_sim.tests/NativeInitialCollisionChecks.cs。
  • tools/build_native_initial_collision_oracle.py、native_initial_collision_oracle.c。
  • tests/test_native_initial_collision.py。
  • build/native_initial_collision_oracle/results.json。
  • build/native_initial_collision_mismatches.json(空)。
  • build/native_initial_collision_core.log、native_initial_collision_python.log、native_initial_collision_godot.log。

继续推进

后续进展:原版静态碰撞四叉树构造已完成根构造、顺序插入及真实资源接线;以下保留本篇交付时的下一步,实际地图上下文仍需继续。

下一步应补静态四叉树构造、两条初始对象链的实际生产者与地图世界上下文,把已实现的查询接到导航贡献和攻击遮挡;不能再把“初始对象查询的数值算法尚未实现”当作缺口。

尚未覆盖的部分包括对象在 getter 内销毁后形成的非法指针、缺失模型二次读取、任意非有限/奇异姿态,以及索引 sweep 超出已提供容量的情况。核心对缺失数据显式诊断,不伪造原版未定义内存值。

武器可构造覆盖仍为 1,053/1,419。攻击型 proc、缺导出资产、动画和全部武器实机验收继续;本批没有据此提高武器完成数。

本批按 godot-master 的核心/数据/表现分层推进,无正式地图或 UI 改动。main 原地工作,未提交、未 attach 原游戏,默认存档 SHA256 未变: 9840fe858b6e311c73f8c2d4ed49912d59d71365f6278392811730e012d32a7f