动态行走网格贡献与旧位置恢复

后续:原版包围盒与静态四叉树候选查询已实现包围盒数学与静态候选内核;完整查询宿主仍在接入。下文保留本批历史状态。

语义更正

上一批把 622C20 及 5F4730/5F4AE0 泛称为“世界碰撞注册/移除”,不准确。

本批沿实际代码确认:这条链更新的是 CLevel 行走网格的单字节贡献计数。攻击射线的世界对象集合、空间候选收集和精确查询是另外的环节,不能据此宣称已接入世界遮挡。

同时确认 622C20 调用的 GetLevel 是 4D5930 → 4D5EB0,经 owner 链找 CLevel;这里的 CLevel +76/+80 是网格宽/深的整数计数,不是两个指针。

本批实现与证据

新增:

  • NativeRoomNavigation:622C20 的开关、旧位置保存与恢复顺序。
  • NativeNavigationContribution:5F4730/5F4AE0 的网格范围、查询选择、比较与字节计数运算。
  • x87 中间值的 binary64 存储,用于 floor 前的实际舍入边界。
  • 对应原版执行参考、核心组合测试和 Python 回归。

1,968 组刷新控制与 576 组网格运算对照一致,网格案例覆盖 PC24/PC53/PC64。核心累计 1,374 条断言、208 项相关 Python 回归通过;Godot 构建零警告、零错误,coresimlint 通过。

世界几何查询在参考和当前组合测试中仍为明确输入边界,不是完整场景查询完成。完整回归结果见 build/native_navigation_python.log:208 项通过。

622C20 的入口门

依次检查:

  1. 能找到 CLevel。
  2. 碰撞接口返回模型。
  3. Room +364 的包围几何存在。

任何一步失败直接返回。即使 +453 已经非零,也不会因为模型已经被清掉而自动撤销旧贡献。

接着检查 CollisionEnabled(+388)、对象可见(+105)与 SceneNode::isVisible。这一处用节点本地/派生位的合取,不应误换成仅取 propagated 位的其他查询入口。

加入与移除顺序

显示/启用

  • +453 非零:不重复加入,也不因为位置已经变化就自动重算。
  • 再次查 Level 并检查宽非零,再查 Level 并检查深非零。
  • 先将 +453 写为 1。
  • 读取节点派生方向。
  • 用 61DCE0(useDerived=false) 的位置覆盖 Room +124。
  • 将当前 AABB +360 与 Room 传给 5F4730。

隐藏/禁用

只有 +453 非零才处理:

  1. 先将 +453 清零。
  2. 读取派生方向。
  3. 保存当前位置。
  4. 临时把存储位置改为 +124 的上次贡献位置。
  5. 节点仍存在时同步其位置。
  6. 调用 5F4AE0。
  7. 恢复原存储位置。
  8. 节点仍存在时恢复其位置。

参考包含移除回调之后节点消失的情况;不会继续对空节点调用位置 setter。

原函数还构造未被后续调用消费的临时旋转矩阵。核心保留 getter 顺序,不物化这些死临时量;不宣称 x87 状态标志完全一致。

移植端缺输入异常会用 finally 恢复临时移动的位置,避免留下测试污染。它不是整个网格操作的事务回滚,失败的 staging level 仍需丢弃;不把这种异常路径算成原游戏正常返回。

网格坐标与舍入

设步长为原 FLOAT 常量 0.4000000059604645,中心偏移为 0.20000000298023224。

四个边界分别计算:

text
index = floor(store_f64(round_pc((bound - origin) * 2.5f)))
index = min(dimension, max(0, index))

减法和乘法均遵循当前 x87 精度;floor 之前实际存储为 binary64,不是先截成 float32。

  • 原点只在 CLevel +92 门非零时使用,X/Z 来自 +688/+692。
  • X 为外循环,Z 为内循环。
  • 遍历半开区间 [minX,maxX) × [minZ,maxZ),不是包含上边界。
  • Y 不参与网格下标。

当前核心对非有限或超出 Int32 坐标域的输入明确诊断;没有猜测所有原 CRT 溢出/异常转换结果。

扩边与格心不能混淆

5F2C70 收到的候选区域在 X/Z 上各扩 0.4,Y 保持不变。

这个扩边发生在四个网格下标已算好之后,只扩大查询候选准备区域,不扩大格子循环范围。即使循环为空,仍会执行候选准备。

每格从 y=1000 向 y=-1000 查询,X/Z 位置在格心。另一个容易遗漏的差异:

  • X 的 x×0.4 先存为 float32,才加原点和 0.2。
  • Z 的 z×0.4 不经过这次中间 float32 存储,直接在 x87 中加原点和 0.2。
  • 最终两个坐标都存为 float32。

因此不能把两轴统一成一个普通 float 表达式,或者认为它们必然逐位相同。

加入与移除用不同几何查询

阶段本物件查询可见性/半径
加入 5F4730622950 普通射线尊重可见性,不传扫掠半径
移除 5F4AE0622670 扫掠半径 0.1,ignoreVisibility=true

本物件没有命中就跳过当前格。命中后保存它的高度,再执行 5E2150 世界扫掠,两个半径参数都是 0.1,末参数为 1。

这两个查询不能因接口看起来相似而共用一个普通射线实现。当前接口分别要求 TraceOwnRay、SweepOwn 和 SweepWorld;缺完整查询输入时不能返回“空世界/没有碰撞”来伪装成功。

哪些格子更新计数

符合任一条件时更新:

  • 世界查询未命中。
  • 世界查询的特殊结果标志非零。
  • 本物件命中高度大于或等于世界命中高度。
  • 高度比较 unordered(NaN):原 x87 parity 分支也走更新路径。

本物件的 Surface 是 DWORD,必须与 100 精确比较,不能先转成 byte。参考专门覆盖 356(低字节同为 100),它不属于 NOPATH=100 分支。

Surface加入移除
恰好 100+1-1
其他-1+1

结果写回 byte,保留回绕:255+1→0,0-1→255。这里确认的是原字节更新,不把它直接替换成布尔“可走/不可走”,也没有在此批实现全部寻路消费规则。

包围盒生产方与共享查询依赖

继续追踪到:

  • 623840:碰撞几何来源的包围盒刷新。
  • 623AF0:视觉/动画模型相关包围盒路径。
  • Room +364 为变换包围几何,+360 为对外 AABB;不是简单的“是否注册”布尔字段。
  • 包围盒计算包含 X/Z 0.2、Y 0.1 的扩边,再按物件缩放和姿态转换;后续仍需完整接入。
  • 5F2C70 准备静态与动态碰撞候选,而非仅设置一个脏区域标记。
  • 5E2150 消费这些候选做世界查询。

这些生产与消费链仍是实际世界宿主的下一部分。本批没有凭 Godot Mesh.GetAabb 直接代替原包围盒,也没有用 Godot 物理扫掠替代原 622670。

组合与异常验证

核心组合测试已把 Refresh → Level.ApplyNavigationContribution → 字节网格算法接在一起,验证一次加入会改变格子,再禁用并移除会恢复原字节,同时分别走普通射线与扫掠接口。

另有检查:

  • 缺碰撞数据时旧贡献标记保留,不能宣称已经清理。
  • 已应用且可见的物件不因位置变化重复贡献。
  • 未实现查询抛错时临时位置仍恢复。
  • 回调中节点被移除后不继续写空节点。
  • 空格子范围仍准备候选。
  • 字节回绕、完整 Surface DWORD、NaN 比较及三精度坐标。

实际静态模型 GPU 回归的 19 项检查也重新运行通过。该探针中的缺失回调现在准确命名为 navigation contribution context;native_world_collision_registered=false 仍保留。

原版参考范围

执行原 EXE 中的:

  • 622C20。
  • 5F4730、5F4AE0。
  • 5DB5D0、5DB5E0 原点 getter。

Level/节点访问、候选准备、三种几何查询及无用途矩阵临时量是明示边界。CRT floor/整数转换使用有界参考辅助实现;本批网格坐标测试为有限且可表示域,不宣称覆盖所有 CRT 未定义/溢出输入。

原 EXE SHA256: 186472c3057b38f4cdff4696959a943c396ae6166995f7418997b5ea853e8a5e

产物:

  • tools/build_native_navigation_oracle.py、native_navigation_oracle.c。
  • build/native_navigation_oracle/results.json。
  • core_sim/NativeRoomNavigation.cs、NativeNavigationContribution.cs。
  • core_sim/NativeRayMath.cs 的 binary64 存储。
  • core_sim.tests/NativeNavigationChecks.cs。
  • tests/test_native_navigation.py。
  • build/native_navigation_core.log、native_navigation_python.log、native_navigation_godot.log。
  • build/native_navigation_visual.log:静态资源回归。

剩余与目标

继续包围盒生产、实际空间候选与扫掠查询,再接动态行走贡献到正式关卡;攻击遮挡的世界查询也沿其真实入口独立完成。之后继续攻击型 proc 与全武器验收。

本批没有给正式关卡换一个近似寻路系统,也没有将这项导航工作当成攻击射线注册完成。按 godot-master 的分层要求,控制和数值运算留在核心,世界数据和查询由明确宿主提供。

新状态仍处于 staging 层,尚未纳入持久世界/快照,v29 不变。武器可构造覆盖仍为 1,053/1,419,全武器目标未完成。

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