初始碰撞对象查询与完整几何世界扫掠
本批结果
补齐 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 非零 |
| 接受最近命中后的 surface | 2000 | 100 |
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:对象几何入口
顺序与前置条件
- 调用 object 虚表 +0x11C,确认存在碰撞模型。
- 检查 +0x189/+0x19B;ignoreFlags 可以绕过这两个门,但不能绕过模型不存在。
- 求 start/end 逐轴 min/max,查询盒扩张 2×radius。两倍半径在 x87 中保留,减/加后分别存 f32。
- 先调用虚表 +0x140 取得 bounds maximum,再调用 +0x13C 取得 minimum,做完整 XYZ 包围盒相交。
- 通过后才取节点派生四元数,建立 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
| 项目 | 初始对象 5125F0 | Room Piece 622670 |
|---|---|---|
| 外层查询盒 | 2×radius 扩张 | 没有这一对象盒门 |
| 距离门 | 无 50 单位门 | end 与原点的 X/Z 各不超过 50 |
| 局部变换 | 仅四元数 | 四元数 × 保存 scale |
| 附加原点 | +0x110 对象的保存位置 | 无此步骤 |
| 局部半径 | 原 radius | radius / storedScale.Z |
| normal 回世界 | Matrix4-vector | Quaternion-vector |
| surface | 下层面值丢弃,上层按链给 2000/100 | 面值或 NOPATH→100 |
核心接线
NativeInitialCollision.cs 提供:
- INativeInitialCollisionLists/Link:实时链表读取边界。
- INativeInitialCollisionObject/Position:模型、bounds、姿态和偏移 getter。
- NativeRayMath.InitialObjectSweep:实际对象几何。
- NativeInitialCollision.Run:双链过滤和最近选择。
NativeGeometryWorldSweepHost.cs 提供三路实际实现:
- ProbeInitial → NativeInitialCollision.Run(filtered=true)。
- SweepStatic → MeshSweep,传入原候选次序及世界扩张盒。
- 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。