# 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`**: ```gdscript 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` 强制) |