Godot 节点后端与 TL2 定制 Ogre 可见性
后续:静态模型资源后端与真实材质上下文已接入真实静态网格/材质和对应碰撞资源数据;世界注册仍待实现。下文保留本批历史状态。
本批结果
新增 NativeGodotSceneBackend,首次将此前的 NativeSceneAttachment 控制器接到实际 Godot Node3D 的创建、父子挂接、变换、可见性和释放操作。
- Godot 4.7-stable 进程内 21 项集成检查通过。
- 在该后端内重放 4,848 组原版父姿态参考,三个 x87 精度的派生姿态均逐位一致。
- Godot 保存的显示矩阵/位置与核心输出逐位一致,包括非单位四元数、负缩放和零缩放。
- TL2 自带 Ogre 的节点可见性与换父可见性部分:6,144 组原生参考的状态和回调顺序一致。
- 累计 1,362 条核心断言、198 项相关 Python 检查通过;Godot C# 构建零警告、零错误;coresimlint 通过。
本批是实际世界宿主的节点层,不是完整地图宿主。尚未接入正式地图模型/材质构造、实际碰撞注册、世界分区、全部对象描述符和攻击型 proc。当前测试未加载主场景,没有进行像素对拍。
为什么不能直接交给 Godot 父子变换
Godot Node3D 默认继承父变换,可通过 top_level 关闭;强制更新变换要求节点已经进入 SceneTree。实现依照这些引擎接口约束处理。Godot Node3D 文档
后端保留原版局部/派生姿态作为权威数据:
- 父子姿态用已验证的 NativeRayMath.DeriveNodePose 计算。
- 显示的线性矩阵用原版四元数矩阵乘缩放矩阵的运算。
- 按行存储的原矩阵转换为 Godot Basis 的列。
- 每个 Godot 变换载体设置 TopLevel,避免引擎再次乘父变换或进行另一套非均匀缩放分解。
- 节点离树时写本地 Transform,入树后才调用 ForceUpdateTransform。
PoseLinearMatrix 从已有 RoomTransform 的正向矩阵部分提取,原 4,908 组 Room 射线/矩阵对照仍通过。显示不需要逆矩阵,因此允许零缩放;碰撞逆矩阵仍保留原来的奇异输入诊断,不将缩放偷偷改成很小的非零值。
集成测试故意给 Godot 挂载节点另设位置与缩放,验证它们不会被重复应用到原版世界姿态上。
TL2 的 Ogre 可见性并非标准 API 摘要
标准 Ogre 文档将 setVisible 描述为修改挂载对象,并可选择影响子节点;这不足以代表 TL2 的定制实现。Ogre SceneNode API
原游戏 OgreMain.dll 的 SceneNode 另外保存:
| 字段 | 用途 |
|---|---|
| byte+0x198 | 本地可见性意图 |
| byte+0x199 | 派生/传播可见性 |
| MovableObject byte+0x2D | 挂载对象自身可见性 |
SceneNode::isVisible(10031E70)要求两个节点字节都非零。_findVisibleObjects(10193680)也在入口检查它们;失败会跳过整个子树。因此不能只切换挂载模型,忽略节点层的遍历门。
本批开发中曾按通用 Ogre 摘要理解为没有持续的节点可见性位。检查原 DLL 后已推翻该假设,以游戏自带实现重做,没有将通用文档当作等价证明。
setVisible 的真实顺序
101937F0:
- 将请求值写入本地字节 +0x198。
- 查询父节点;存在时会再次查询父节点。
- 如果父节点的 +0x199 为零,保留当前 +0x199 的旧值,并禁止本次向子节点传播。
- 否则将当前 +0x199 写成请求值的非零归一化结果。
- 无论父节点是否隐藏,都把请求值传给当前挂载对象。
- 只有 cascade 为真且第 3 步没有禁止传播时,才调用子节点 setPropagatedVisible。
隐藏父级下保留旧派生值看起来反直觉,但它是原版机器码行为,不主动“纠正”为零。
setPropagatedVisible(10193960):
- 当前派生值 = 传入父可见值非零 AND 当前本地值非零。
- cascade 为真时继续传播这个派生值。
- 不修改子节点本地意图,也不改挂载对象自身可见性。
因此“父级隐藏再显示”不能让明确隐藏的子对象恢复显示;也不能因为父级隐藏就抹掉子对象自己的可见标记。
换父与脱离
10192D50 的可见性部分:
- 基础 parent 写入先发生。
- 挂到父节点时,先通知场景归属,再按父节点派生可见性传播。
- 脱离父节点时,以当前本地和派生可见性的合取继续传播,随后通知离开场景。
- 已经被派生隐藏的节点在 detach 后不会自动变可见。
NativeNodeVisibility.ParentChanged 只实现这里的可见性部分;实际场景树进入/离开由 Godot 后端执行。它不冒充完整原 Node::setParent 的所有副作用。
Godot 的具体节点结构
NativeNode_父(变换载体,TopLevel;可见性表示节点遍历门)
├─ Content
│ └─ 本节点挂载对象(自身 Visible 单独保存)
└─ NativeNode_子
├─ Content
│ └─ 子节点挂载对象
└─ ……本地/派生可见性控制载体;对象自身可见性控制挂载对象。Godot 的父级隐藏提供子树遍历效果,而原算法决定什么时候改派生字节、什么时候改自身对象字节。
后端保留明确的节点 handle、父节点、局部姿态、子列表和挂载对象身份。handle 不与原版 64 位运行时/源 ID 混用。运行时对象查找由宿主提供;集成探针使用真实 NativeFactoryRegistry,验证 long.MaxValue/long.MinValue 附近 ID 不截断。
注册尚未绑定的挂载对象不转移所有权;绑定后由后端节点拥有。清理时先逐个拆开变换载体,再释放各自内容,避免释放父节点时连带释放仍被表记录的子载体。重复 Dispose 不重复释放。
引擎内验证及发现
独立 SceneTree 探针不启动主场景,不打开存档。
21 项检查包括:
- 无效根姿态在创建 Godot 节点之前拒绝。
- Source Parent ID 设置与 TransformParent 挂接分开。
- 实际 Godot 父子节点与原版控制器输出一致。
- 非 cascade 的节点隐藏保留子节点本地状态和子挂载对象标记,但正确停止子树显示。
- 父级重显不覆盖子级显式隐藏。
- detach 保留派生隐藏,重新挂到可见父节点后恢复。
- 有 TransformParent 的隐藏对象仍留在树上。
- 新挂载对象保持自身初始 Visible,但仍受已隐藏节点的遍历门约束。
- 零缩放不钳制。
- 拒绝环形挂接。
- 释放后没有仍存活的自有载体/挂载对象,未绑定对象仍归调用方。
一次探针失败来自测试直接把底层节点挂回 owner,却没有同步 NativeSceneAttachment 的 TransformParent/AttachmentNode。后续 show 按原控制器又挂回了隐藏 group。已改成通过 SetTransformParent 进行重挂接,未用强制显示掩盖状态不一致。
4,848 组姿态参考来自 build/native_node_pose_oracle/results.json,全量三精度重放在 Godot 进程内执行,不是又写一个预期公式替代原版结果。
原生参考范围与产物
OgreMain.dll SHA256: 974c1dc77ea2818276cc7eb94a8b95b2ccf8595e28fa3f14b2cd6d516fdb67a1。
原生参考执行 101937F0、10193960、10192D50 与 MovableObject setter。容器存储、父 getter、基础 parent 写入和场景归属虚调用是明示边界;结果包含节点/对象字节和回调顺序。
文件:
- core_sim/NativeNodeVisibility.cs。
- core_sim/NativeRoomTransform.cs 的 PoseLinearMatrix。
- torchlight-2-poc/NativeGodotSceneBackend.cs。
- torchlight-2-poc/NativeSceneBackendProbe.cs、tl2_native_scene_backend_probe.gd。
- tools/build_native_node_visibility_oracle.py、native_node_visibility_oracle.c。
- tools/run_native_scene_backend_probe.py。
- core_sim.tests/NativeNodeVisibilityChecks.cs。
- tests/test_native_node_visibility.py。
日志与证据:
- build/native_node_visibility_oracle/results.json:6,144 组原版参考。
- build/native_godot_scene/result.json:21 检查、4,848 姿态,含实际引擎版本、源文件指纹和存档前后哈希。
- build/native_godot_scene_core.log:1,362 断言。
- build/native_godot_scene_python.log:198 项相关 Python 回归。
- build/native_godot_scene_build.log:构建通过。
剩余
接下来将真实 Room 视觉资源/材质与碰撞后端接到本节点层,再整合完整装载宿主、世界注册/分区、实际任务与关卡上下文,继续攻击型 proc 和全武器验收。
本批仅测试 Node3D 挂载对象及节点状态,不是 Mesh/材质或像素级视觉验收,也未替换正式地图。复杂动态脚本回调、完整 Ogre 容器迭代/失效语义和持久世界快照尚不在本后端的完成声明内。
按 godot-master 的分层要求,原版姿态/可见性控制留在核心层,Godot 后端执行引擎操作;没有用 Godot 物理查询替代原版碰撞公式。新节点状态仍属于正在构建的装载宿主;正式世界状态及其快照尚未接入,v29 未改。
武器可构造覆盖仍为 1,053/1,419,全武器目标未完成。main 原地工作,未提交、未 attach 原游戏。默认存档前后 SHA256 相同: 9840fe858b6e311c73f8c2d4ed49912d59d71365f6278392811730e012d32a7f。