原版包围盒与静态四叉树候选查询

后续:世界候选收集与扫掠选择控制已接入完整候选准备与选择控制,并确认新建空碰撞列表的零包围盒;以下相关结论已更正,原批次计数保留为历史记录。

本批结果

新增 NativeBounds、NativeCollisionQuadtree、NativeGodotCollisionBounds,并将碰撞资源的原始顶点包围盒接入现有 F2 数据层。

验证结果:

  • 2,976 组原版 Room 包围盒入口/运算参考,入口门结果一致。
  • 其中 1,884 组实际进入计算的局部端点、世界包围盒逐位一致,覆盖 PC24/PC53/PC64。
  • 395 组静态四叉树查询的输出顺序、重复索引和边界条件一致。
  • 6 组原始顶点包围盒参考,包含真实 ROCK_03_COLLISION 的 42 顶点。
  • Godot 资源探针增加实际姿态/包围盒/候选串接,目前 21 项通过。
  • 累计 1,382 条核心断言、212 项相关 Python 回归通过;Godot 构建零警告、零错误,coresimlint 通过。

本批完成包围盒数学、入口布尔门和静态查询内核;四叉树的原版构造、完整 5F2C70 动态候选上下文及后续扫掠仍未完成。

碰撞包围盒的真实来源

5757F0 从 Ogre 网格导出顶点与索引,逐个添加进 CCollisionList,然后调用 469430。

469430 的整体包围盒:

  1. 用第一个已存顶点同时初始化 min 和 max。
  2. 遍历全部已存顶点,严格小于/大于才更新端点。
  3. 不限于三角形引用的顶点,也不按材质删除顶点。
  4. 相等值保留最早值,包括正负零的位值。

F2NativeCollision.LocalBounds 现在按这条路径计算,不读取 Godot 视觉网格的 AABB。

后续已确认 TL2 的 Ogre Vector3 默认构造 10001CC0 会清零:新建空 CCollisionList 保留零包围盒;已有列表清空顶点后,469430 则保留其旧包围盒。核心原先的保守拒绝已移除,分别由 FromVertices 和 UpdateFromVertices 表达两种情况。有效 F2 数据仍要求有限顶点,不能由这些检查推导所有无效几何均已支持。

两个 Room 入口门

623840(碰撞来源)允许条件:

text
(有碰撞模型 && !VisualBake && !AlwaysBake && !CollisionBake)
OR
(有视觉模型 && 有动画)

623AF0(视觉来源)允许条件:

text
有视觉模型 &&
((!VisualBake && !AlwaysBake && !CollisionBake) OR 有动画)

两者并不等价。

尤其碰撞入口通过“视觉模型有动画”分支后,后续仍读取碰撞模型,不会自动改用视觉包围盒。缺碰撞模型的这种组合在原生里会解引用空指针;参考未伪装成正常结果,移植端必须保持明确的缺输入边界。

本批核心门是这些已获取状态的布尔计算,不宣称完成所有虚 getter、重入修改和分配失败的生命周期。

先扩边、再缩放、再旋转和平移

原始端点为 min/max,先分别做:

text
min' = min - (0.2f, 0.1f, 0.2f)
max' = max + (0.2f, 0.1f, 0.2f)

每个分量存为 float32 后,再分别乘物件的局部存储缩放,并再次存为 float32。

后续用节点派生方向构造旋转矩阵,派生位置写进矩阵平移列,再变换八个角点。这里的平移在矩阵乘法最后一次 float32 存储之前参与,不能用另一个“先算旋转,再另加位置”的路径替代。

负缩放可以令局部 min' 大于 max',不能先交换端点,也不能取绝对缩放。八角点投影会重新得到正确的世界 min/max。零缩放也保留,不为求逆或显示方便而钳制。

八角点的顺序

511750 按以下顺序处理,a/b 是已经扩边和缩放的两端:

text
a
b
(a.x, a.y, b.z)
(a.x, b.y, b.z)
(a.x, b.y, a.z)
(b.x, a.y, a.z)
(b.x, b.y, a.z)
(b.x, a.y, b.z)

第一个变换结果初始化世界 min/max。之后 6834B0 只按严格比较更新;顺序会影响相等值及正负零,不能随意改成无序角点集合。

原 CCullingBounds +8/+20 保存局部两端,+32/+44 保存世界两端;Room +364 指向这份几何,+360 指向供外部查询的 Ogre AABB。原参考同时核对了新分配和已有对象更新的输出,不过核心值对象尚不承担原指针身份与分配生命周期。

静态结构是四叉树,不是八叉树

5EFC80 每个分支固定四个子槽,筛选只看 X/Z。

当前节点先按原 TriangleIds 顺序追加,再处理子节点。追加函数 468840 不去重,因此同一面索引可以多次出现;也不能排序输出,因为后续查询顺序会受到影响。

checkBounds=true:

  • 当前节点的面,逐个做 X/Z 包围盒相交测试。
  • 查询区域完整包含子节点时,递归时关闭边界检查。
  • 否则仅对相交子节点递归并继续检查。

checkBounds=false:

  • 直接追加当前节点的所有索引。
  • 四个子节点也全部按原顺序递归。
  • 不再读取逐面包围盒,即使某个索引无法用于逐面查表也不会在这条路径上被提前查表。

入口没有额外的“查询是否与当前根包围盒相交”门。本批用根自身带远处面索引的构造输入验证这一点,不能新增一个看似合理的根层剪枝。

三种包围盒判定要分开

  • 683460:X/Z 相交。
  • 683410:X/Z 完整包含。
  • 6833A0:完整三维相交,后者用于动态 Room 候选。

端点接触算相交/包含。X/Z 查询不会因为 Y 很远或 Y 为 NaN 而拒绝;X/Z 本身的 NaN 则按有序比较拒绝。不能把 Godot 的一个 3D AABB 方法套到所有入口上。

树拓扑仍是明确输入。当前实现没有用一棵自选树代替原构造算法,也没有根据少量叶节点测试宣称整关卡的静态分区已一致。

实际资源/节点接线

NativeGodotCollisionBounds 使用:

  • F2NativeCollision 的原始碰撞顶点端点;
  • 调用方明确提供的局部存储缩放;
  • NativeGodotSceneBackend 的原版派生姿态。

不使用 Mesh.GetAabb 替代碰撞来源。

GPU 进程内以真实岩石碰撞资源做了受控变换(含负缩放),确认计算结果完整包含各碰撞面的端点。随后把真实面的包围盒接到静态候选查询,验证在 Y 很远但 X/Z 覆盖时仍按面序输出。

这个串接例使用明确的叶节点输入,不是原版完整四叉树构造证明。测试恢复模型姿态后才进行既有资源截图,本批没有修改正式画面或材质。

参考边界与文件

原执行范围包含 623840、623AF0、5116F0、511750、6834B0、469430 及实际 Ogre 向量/矩阵运算;静态查询执行 5EFC80、683410、683460。

分配器、基础对象身份、位置/缩放/节点 getter 输入及输出向量追加是明示边界。Ogre AABB 构造器先读取的临时 BSS 向量在参考中零初始化;它们在可观察结果前被原 setExtents 完整覆盖,未借这些值推断原游戏全局初始化。

原 EXE SHA256: 186472c3057b38f4cdff4696959a943c396ae6166995f7418997b5ea853e8a5e

OgreMain.dll SHA256: 974c1dc77ea2818276cc7eb94a8b95b2ccf8595e28fa3f14b2cd6d516fdb67a1

实现及产物:

  • core_sim/NativeBounds.cs、NativeCollisionQuadtree.cs。
  • core_sim.io/F2NativeCollision.cs。
  • torchlight-2-poc/NativeGodotCollisionBounds.cs。
  • tools/build_native_bounds_oracle.py、native_bounds_oracle.c。
  • core_sim.tests/NativeBoundsChecks.cs、tests/test_native_bounds.py。
  • build/native_bounds_oracle/results.json。
  • build/native_bounds_core.log:1,382 断言。
  • build/native_bounds_python.log:212 回归。
  • build/native_bounds_build.log、native_bounds_visual.log。

剩余

后续进展:原版静态碰撞四叉树构造已将明确输入的叶节点示例推进到原构造/插入算法,并用于 Godot 资源探针;以下为本篇交付时的剩余状态。

继续完整候选准备 5F2C70、动态 Room 顺序与条件、四叉树原构造输入,再接世界扫掠 5E2150/物件扫掠 622670。导航贡献和攻击遮挡仍需这些真实查询宿主,不能用“未命中”的默认回调绕过。

实际世界注册、动画/共享模型、缺导出资源、攻击型 proc 和全武器验收仍在目标内。武器可构造覆盖维持 1,053/1,419,不能把本批空间查询基础设施计成新的武器完成数。

按 godot-master 的分层要求,包围盒数学与查询留在核心层,Godot 提供节点/资源数据;未以引擎近似物理替换原计算。快照 v29 未改。

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