单位主虚表、模型来源与动态包围盒
本批结果
从构造器主虚表写入确认角色、装备和金币的模型 getter;实现 CBaseUnit 包围盒刷新及角色专用水平朝向矩阵,并将缓存接给现有几何查询。
- 1,440 组三精度原生对照:矩阵、包围盒输出、零回落和 getter 顺序逐位一致。
- 核心累计 1,426 条断言、252 项相关 Python 回归通过。
- Godot C# 构建零警告、零错误,coresimlint 通过。
- 共用八角点运算后,既有 Room bounds 对照及 22 项 Godot GPU/资源回归继续通过。
- 计算后的单位缓存与明确提供的碰撞几何组合执行实际扫掠通过。
模型 getter 来源已经确认;完整的对象模型装载/释放、局部包围盒初始化和活动位生命周期仍由后续真实世界宿主完成,没有在本批伪装成完整地图接入。
必须使用主虚表
CCharacter 和 CEquipment 都有多重继承附属虚表。IDA 名称不带后缀的那一张分别写入对象 +0x1F4/+0x274,不能拿它当对象起始处的主虚表。
必须从构造器的 mov [esi], vtable 写入取证。本次脚本直接核对原始指令 C7 06 + 地址,再读取该表的方法槽。
| 类 | 主表 | 构造写入 | +110 显示/偏移模型 | +11C 碰撞模型 | +180 bounds 矩阵 |
|---|---|---|---|---|---|
| CCharacter | 217275C | 555553 | 555EE0 | 555EF0 | 519F90 |
| CMonster | 21765A4 | 5B0D4D | 555EE0 | 555EF0 | 519F90 |
| CPlayer | 2177804 | 5C2F22 | 555EE0 | 555EF0 | 519F90 |
| CEquipment | 2174864 | 585162 | 585700 | 585720 | 514500 |
| CItemGold | 2176024 | 5A89A6 | 5A8350 | 5A8360 | 514500 |
这些类的 +13C/+140 均使用 511E80/511EA0 包围盒 getter。
只看错位的附属表,会把向量 getter、空函数或其他接口函数误当成模型/bounds getter。本次在实现前已从构造器纠正,没有把这些错误映射接入正式场景。
显示模型与碰撞模型不是一个槽
| 实例 | +110 返回值 | +11C 返回值 |
|---|---|---|
| 角色/怪物/玩家 | 对象 +0x220 的显示模型指针 | 对象 +0x624 的碰撞模型指针 |
| 装备 | 对象 +0x2E0 的显示模型指针 | 对象 +0x2E8 的碰撞模型指针 |
| 金币 | 对象 +0x27C 的显示模型指针 | 5A8360 直接返回 0,无碰撞模型 |
注意伪代码 this[393] 若 this 为 DWORD 指针,对应字节偏移 0x624;它不是字节偏移 +393 的 COLLIDEABLE。必须按原寻址宽度解释。
所以“显示模型存在”不能补成“碰撞模型也存在”,金币的显示模型尤其不能被自动拿来做原初始对象查询的碰撞代理。
独立的 CCullingBounds
CBaseUnit+0x1B8 保存 CCullingBounds 指针:
- +8/+20:local minimum/maximum。
- +32/+44:world minimum/maximum。
- 5116F0 构造时把 local 同时复制到 world。
511E80/511EA0:
- 缓存存在,返回 world min/max。
- 缓存不存在,返回 Ogre Vector3::ZERO 的地址,读取值为 (0,0,0)。
这不是返回空指针,也不是回退到显示 Mesh AABB。NativeUnitBounds.Minimum/Maximum 保留原零向量行为;碰撞模型存在不会自动创建 culling cache。
511F00 刷新时序
- 若 +0x1B8 为空,直接返回,不查询位置、模型或矩阵。
- 61DCE0(useDerived=true) 获取对象位置。
- 读取主表 +110 的偏移模型。
- 第一次非空时再读取第二次;第二次非空则读取第三次,并取其 61DCE0(useDerived=false) 保存位置;第二次为空则加 ZERO。
- 将该偏移加到对象位置。
- 调用主表 +180 得到 bounds 矩阵。
- setTrans 用计算后的位置覆盖矩阵平移列。
- 重新读取当前 +0x1B8,并对其 local bounds 执行原 511750 八角点变换,更新 world bounds。
与 5125F0 的两次偏移模型读取不同,这里可能读取三次。不能缓存第一次返回值覆盖后续读取。
原生对照还模拟第一次模型 getter 中替换 culling cache,验证最后更新的是当前指针而非函数入口缓存的旧指针。实际模型指针及位置 getter 是明确的参考输入边界;没有宣称各种非法销毁或异步写入都能安全重现。
两种 bounds 矩阵
角色:519F90
从保存的 FORWARD(对象 +0xB0)取 X/Z,先把 Y 覆盖为零:
f = Ogre.normalise((forward.x, 0, forward.z))
right = Ogre.crossProduct(f, UNIT_Y)
matrix.columns = (right, UNIT_Y, f)
translation = 0
bottom_row = (0,0,0,1)归一化使用原 Ogre length 与 double 1e-8 阈值、原 x87 运算和 float32 存储。
- 不使用保存的 scale。
- 不直接使用节点四元数。
- 不复制对象完整缓存矩阵。
- FORWARD.Y 在参与浮点运算前已被覆盖,所以即使原值是 NaN 也不会影响结果;实现只验证实际使用的 X/Z。
- 水平朝向为零时保留零 X/Z 轴,包围盒可能坍缩;不凭空补单位矩阵或默认朝向。
装备/金币:514500
复制对象 +0xBC 起的 64 字节,即保存的 4×4 矩阵。511F00 随后覆盖平移列;原来的平移值不能再额外叠加一次。
INativeUnitBoundsSource.GetBoundsMatrix 必须由实际类型选择正确来源。窄相位 5125F0 使用节点派生四元数,是另一条规则;不能因为两者都“涉及旋转”就合并。
八角点运算共享
NativeRayMath.TransformCullingBounds 复用原 511750 顺序:
min, max,
(min.x,min.y,max.z),
(min.x,max.y,max.z),
(min.x,max.y,min.z),
(max.x,min.y,min.z),
(max.x,max.y,min.z),
(max.x,min.y,max.z)每点用原 Ogre Matrix4-vector 运算,之后按严格大小比较扩大 world min/max。没有预先交换负缩放导致的端点,也没有使用 Godot AABB 转换取代。
既有 RoomBounds 改为调用该公共函数;Room 自己的 padding、保存 scale 和派生姿态计算没有改变。旧 Room 的原生对照继续覆盖此次提取共用函数的风险。
接入接口与状态归属
- NativeUnitCullingBounds:可重建的 local/world 几何缓存。
- INativeUnitBoundsSource:真实位置、偏移模型、当前缓存及矩阵来源。
- INativeUnitCollisionSource:补充独立碰撞模型、活动标志及窄相位派生四元数。
- NativeUnitCollisionBinding:把这些源接入已有 INativeInitialCollisionObject,不替代对象所有权。
缓存是从真实局部 bounds 与变换重建的派生状态,不能当作持久化角色状态的新权威来源。原模型的资源装载/引用计数、指针所属运行时类和局部 bounds 初始化仍由宿主负责。
接线测试使用明确给定的三角形模型,加上本批真实算法算出的 culling cache,执行 InitialObjectSweep。它没有用预设命中结果,但也不是完整角色的模型/动画装载验收。
已定位但未完成的装载部分
角色 55E110 的碰撞模型装载规则已定位:
- COLLISIONFILE 非空时,拼接 RESOURCEDIRECTORY + "/" + COLLISIONFILE + ".mesh"。
- 缺省路径为 media/models/collider.mesh。
- 通过 649520 的碰撞资源缓存获得模型,存入 +0x624。
- 随后读取 COLLISION_ADD(默认 0.25),计算局部 culling 范围,再调用 511F00。
本批没有完整实现后续局部范围对称化、所有资源缓存/变体/释放或装备的对应装载阶段。不能因 getter 和 world bounds 已实现就声称这些生命周期工作也已完成。
证据与产物
- core_sim/NativeUnitBounds.cs。
- core_sim/NativeBounds.cs 的共用八角点提取。
- core_sim.tests/NativeUnitBoundsChecks.cs。
- tools/build_native_unit_bounds_oracle.py、native_unit_bounds_oracle.c。
- tests/test_native_unit_bounds.py。
- build/native_unit_bounds_oracle/results.json,含构造器主虚表证据。
- build/native_unit_bounds_mismatches.json(空)。
- build/native_unit_bounds_core.log、native_unit_bounds_python.log、native_unit_bounds_godot.log、native_unit_bounds_visual.log。
参考执行原 511F00/511750/511E80/511EA0/519F90/514500 和 Ogre 运算;位置与偏移 getter 使用明确输入并记录顺序。UNIT_Y 来自原 DLL 已初始化常量数据,ZERO 来自核实后的 PE loader 零填充区。
EXE SHA256: 186472c3057b38f4cdff4696959a943c396ae6166995f7418997b5ea853e8a5e。
OgreMain.dll SHA256: 974c1dc77ea2818276cc7eb94a8b95b2ccf8595e28fa3f14b2cd6d516fdb67a1。
继续推进
后续进展:角色碰撞资源请求与局部范围初始化已补路径、真实几何及局部范围计算,并以默认 collider 贯通查询;完整共享模型管理和实例生命周期仍需继续。
下一批应补实际单位碰撞资源装载与局部 culling 范围初始化、活动位生命周期及完整地图世界宿主。主虚表归属、模型槽和 world bounds 刷新算法不再列为未知。
武器可构造覆盖保持 1,053/1,419;正式攻击遮挡、攻击型 proc、动画、缺导出资产及全武器验收仍需继续。部分角色表的 UniverseComplete=false 没有放宽。
按 godot-master 的分层要求,缓存/计算留在核心层,宿主提供准确类型及原资源;不从显示网格猜碰撞。本批在 main,未提交、未 attach 原游戏、未修改正式场景与 UI,快照 v29 未改。默认存档 SHA256 未变: 9840fe858b6e311c73f8c2d4ed49912d59d71365f6278392811730e012d32a7f。