Files
opendance-windows/tasks/016-aabb-transforms.md
T
zijunchiang 14ee5f7ec9 fix(scene): 包围盒沿树累乘变换(修掉入树前的 ERROR 与范围偏位)+ 21 项"真机等价"测试
缺陷:SceneRuntime._compute_aabb() 用 MeshInstance3D.global_transform 计算包围盒,
而它在 _assemble() 里调用 —— 那时运行时节点**还没入树**。两个后果:
- Godot 打一行 `ERROR: Condition "!is_inside_tree()" is true. Returning: Transform3D()`
  (真机每次载入场景都刷一条,与"日志里不应有 ERROR"的排查约定冲突);
- 树外的 global_transform 返回单位变换,算出来的只是各网格的**局部** AABB,
  于是重力/风 Area3D 的碰撞盒与兜底地面会偏小或偏位 —— 对把网格摆在非原点位置的
  场景(导出 GLB 的常见情况)**风力可能作用不到物体上**。

修复:改为从根往下累乘 `Node3D.transform`,不依赖是否入树;挂在单位变换父节点下时
结果与旧语义一致。

新增 21 项无头"真机等价"测试(不需要设备):
- tests/test_scene_config.gd:在代码里手写合法 GLB(三角形 + BIN chunk)落盘后
  **真实加载**:warnings 为空、按 colliders 三种取值分别核对碰撞体(mesh / auto 命中
  *_col / auto 未命中的兜底地面 / none)、重力 Area3D 的 gravity 与方向、
  SPACE_OVERRIDE_REPLACE、风力与 WIND_FORCE_SCALE 对齐,以及**带位移网格**
  (translation [10,0,0])时重力区域与兜底地面都跟着位移(该缺陷的回归测试);
- tests/test_pose_server.gd:**时间维度**验证保活 —— 只发应用层 ping、不发关键点帧的
  客户端在 17 秒(> 15 秒静默超时)后仍保持连接("画面里没人就掉线、疯狂重连"的根因
  对照),而完全不发数据的客户端被 close(1001) 断开。

新增 build/check-version.sh:强制 project.godot 的 application/config/version
(「关于」清单与 .pck 的来源)与 project.yaml、发布版本号三者一致;已接入
build/package-release.sh(打包前)与 build/verify.sh(第 0/4 步),并做过反向验证。
此前没有任何机制保证这两个文件同步 —— 界面显示的版本可能和产物名不一致。

版本升至 0.2.1(project.godot / project.yaml / README / 产物名 / tag 全部一致),
About 清单与 .pck 内版本号均为 0.2.1(已用 strings 核对)。

验证:build/verify.sh 四步全通过(版本一致性 → 编译 → 751 项断言 → 180 帧启动无错误);
产物 opendance-windows-v0.2.1.zip(LZMA,26,627,191 B)
sha256 d49885327047f5a52de6c732431ea080cd1a030ae1c581a8bc0ec0d99bfb792c。
2026-09-17 03:59:29 +08:00

3.1 KiB
Raw Blame History

T-WIN-016 场景包围盒沿树累乘变换

  • 状态: done
  • 依赖: T-WIN-013
  • 文档: v1.6 §5.6(场景 GLB 正常加载 / 物理参数生效)

缺陷

SceneRuntime._compute_aabb() 用 MeshInstance3D.global_transform 计算包围盒, 而它在 _assemble() 里被调用 —— 那时运行时节点还没被加入场景树。两个后果:

  1. Godot 会打一行错误日志(每次载入场景都会刷):

    ERROR: Condition "!is_inside_tree()" is true. Returning: Transform3D()
    

    这与「日志里不应出现 ERROR」的排查约定冲突,真机排查时是噪声;

  2. get_global_transform() 在树外返回单位变换,于是算出来的只是各网格的局部 AABB。包围盒用于①重力/风 Area3D 的碰撞盒尺寸与位置 ②兜底地面的尺寸与位置 —— 对于把网格摆在非原点位置的场景(导出 GLB 的常见情况),区域会偏小或偏位, 风力/重力可能作用不到物体上。

修复

改为从根节点往下累乘 Node3D.transform:

static func _compute_aabb(node: Node, parent_transform: Transform3D = Transform3D.IDENTITY) -> AABB:
    var local := Transform3D.IDENTITY
    if node is Node3D:
        local = (node as Node3D).transform
    var here := parent_transform * local
    ...

不依赖是否入树;运行时被挂在单位变换的父节点下时,结果与旧的"全局变换"语义一致。

回归测试(tests/test_scene_config.gd)

在代码里手写一个合法 GLB(三角形:POSITION 3×VEC3 + indices 3×u16 + BIN chunk) 落盘后真实加载,覆盖:

  • 真实 GLB 加载 warnings 为空、不使用兜底地面;
  • colliders 三种取值:mesh → 按网格生成;auto 命中 *_col → 生成; auto 未命中 → 兜底地面并标记;none → 不生成也不兜底;
  • 重力 Area3D 的 gravity / gravity_direction 与服务端配置一致, 且用 SPACE_OVERRIDE_REPLACE(不改全局 ProjectSettings);
  • 风力按基础风向量生效(wind_force_at() 与 WIND_FORCE_SCALE 对齐);
  • 带位移网格(节点 translation [10,0,0])时,重力区域碰撞盒与兜底地面 都跟着位移(x≈10)—— 这一条正是上述缺陷的回归测试。

顺带补充:保活的时间维度验证(tests/test_pose_server.gd)

用真实的 15 秒静默超时跑一次对照:

  • 只发应用层 ping、不发任何关键点帧的客户端,17 秒后仍保持连接 (「画面里没人就掉线、然后疯狂重连」的根因对照);
  • 连上后什么都不发的客户端被 close(1001, "timeout") 断开(超时机制仍有效, 且按契约 §7 对端不会重连)。

验收

项 结果
无头测试 751 项断言全部通过(原 720)
build/verify.sh 全通过(新增第 0 步版本一致性)
日志 真实 GLB 加载路径不再出现 !is_inside_tree() ERROR
版本号 project.godot = project.yaml = 发布版本 = 0.2.1(build/check-version.sh 强制)