Group 候选池构建、解析期唯一性剔除与全量审计

后续:Group条件字段与构造默认值已接通实例字段及属性后的接受判定;下文保留本批历史状态。

本批结果

已实现原版 50AD70 的 v11 Group 池构建:按读取顺序分配节点、候选入队、读取子组、二次唯一性查询、递归销毁,再将存活节点挂到父组。

16 组原版机器码参考与核心实现对照一致,比较的不只是最终子树,还包括分配顺序、父指针、候选队列、约束标记、查询及销毁顺序。

全量审计覆盖 8,987 份已冻结的原版 BINLAYOUT:

项目数量
包含 Group 池的文件2,528
Group 记录47,224
feature 候选8,780
解析期 LEVEL UNIQUE 非零记录0
空唯一历史下的失效候选0
解析/回写/类型指纹失败0

因此,下面的失效引用问题是构造输入下已证实的行为,不是原版地图运行时已经发生的崩溃。此结论仅针对冻结文件中的 Group 尾部字节,不扩大到 Mod、编辑器生成的新文件或其他动态改写路径。

累计 1,345 条核心断言、189 项相关 Python 检查通过。Godot C# 构建零警告、零错误;coresimlint 通过。

两次唯一性查询不能合并

50AB80 在候选入队前:

  1. 若 LEVEL UNIQUE 非零,先查关卡唯一性列表。
  2. 已使用则不入候选队列。
  3. 未使用且存在 feature manager,只有 TAG != -1 或 NO TAG FOUND 非零的节点入队。

这个第一次查询即使没有 feature manager 也会发生。只有主题约束的组不入 feature 队列,但主题列表随后会令 HasFeatureConstraint 为真。

50AD70 在读完所有子组后再次查询当前唯一组。已使用时,递归销毁子树并返回空;未使用才把它挂到父组。不能在读子组之前直接跳过整个组,也不能只查询一次。

本批 NativeGroupPoolConstruction.Build 将解析定义与可变的单次加载树分开,复制主题数组,不修改冻结的 NativeLayoutGroup。F2NativeGroupChoices.BuildForLoad 是对应的数据适配入口;旧 ProjectRaw 仍只做未筛选投影。

失效候选的证据与移植边界

构造输入:

text
root
 ├─ 10:TAG=1,唯一
 │   ├─ 11:TAG=2
 │   └─ 12:唯一,无 TAG
 │        └─ 13:TAG=3
 ├─ 20:TAG=2,唯一
 ├─ 30:仅主题约束
 └─ 40:NO TAG FOUND=2

当唯一列表预先含 10:

  • 10 自身不进入候选队列。
  • 后代 11、13 在读取时仍入队。
  • 子树随后按 11、13、12、10 的顺序释放。
  • 候选队列仍保留 11、13 的指针,随后是存活的 20、40。

原参考确实执行原函数及析构路径。测试分配器保留已释放的存储,仅用于诊断追踪;不进行内存复用,不在这些指针上继续执行原生 feature 预选。因此没有声称复现任意原版堆状态下的后续结果。

核心保留 Released 诊断和候选顺序,不悄悄删除失效候选并宣称算法等价。若存在 feature ledger,预选在任何随机消耗、队列修改或 feature 标记写入之前抛出:

text
native_group_pool_released_feature_candidate

没有 ledger 时保留原预选函数的立即返回。对已释放节点的查找及普通 CHOICE 入口也明确拒绝。

这是针对原生无效引用的安全诊断边界,不是已确定的原版正常行为替代方案。若未来 Mod 或新输入触发它,需要先取得具体输入和堆/生命周期证据。

与装载控制器的串接

组合测试已经将真实解析器、池构建、feature 预选、随机恢复、普通 CHOICE 与 NativeLayoutLoader 的对象读取控制串在一起:

  1. 构建本次加载池。
  2. 装载器保存同步随机状态。
  3. 执行 feature 预选。
  4. 装载器恢复同步随机状态。
  5. 执行普通 CHOICE。
  6. 按 Group 的 Allowed 过滤对象。
  7. 将真实池上下文传给 Layout Link,并传播 HasFeatureConstraint 到 Group 标记。
  8. 继续接受队列、extra 与延迟初始化。

组合夹具验证了最终接受对象 ID、预选确实消耗随机数而状态在普通选择前恢复、被拒绝组不创建、上下文传播,以及失效候选在工厂调用前被拦截。

这些组合测试的场景对象、实例条件接受和 extra 执行仍使用明示测试宿主。 正式 Godot 世界宿主没有因此完成;不可把控制器 Finished 当作整张地图/武器攻击已验收。

全量数据审计的含义

native-layout-audit 在原有无损回写、类型指纹和 Group→对象块引用校验之外,现在还构建每份文件的候选池:

  • 空唯一列表时,分配节点数必须与解析记录数一致。
  • 所有节点存活、候选可用。
  • 若有唯一组,会对每个唯一 ID 单独预置“已使用”,记录导致失效候选的反事实场景。
  • 当前原版冻结集没有非零唯一标记,因此此项反事实场景数为 0,而不是跑过若干唯一场景却没发现问题。
  • 实际 Group 块匹配数为 44,696,另有 2,528 个合成根,未解析引用为 0。

有符号 short 的子组计数边界保留为显式错误:预解析树若拥有超过 32,767 个子组,原读取路径不会按 unsigned 完整消费该树,核心不擅自重解释。此边界没有在原版冻结集中触发。

文件与验证

实现:

  • core_sim/NativeGroupPoolConstruction.cs。
  • core_sim/NativeGroupChoice.cs、NativeFeaturePreselection.cs 的失效引用防护。
  • core_sim.io/F2NativeLayouts.cs、F2NativeGroupChoices.cs 的定义适配。
  • core_sim.cli/NativeLayoutAudit.cs 的全量池审计。
  • core_sim.tests/NativeGroupPoolChecks.cs 及 NativeLayoutLoaderChecks.cs。
  • tools/build_native_group_pool_oracle.py、tools/native_group_pool_oracle.c。
  • tests/test_native_group_pool.py。

证据:

  • build/native_group_pool_oracle/results.json:16 组原版参考。
  • build/native_layout_csharp_audit.json:8,987 文件完整审计。
  • build/native_group_pool_core.log:1,345 断言。
  • build/native_group_pool_python.log:189 检查。
  • build/native_group_pool_godot.log:构建通过。

原 EXE SHA256: 186472c3057b38f4cdff4696959a943c396ae6166995f7418997b5ea853e8a5e

原代码执行范围含 50AD70、50AB80、509C20、509D50、50A230、712F70、405A10。读取器、分配器、唯一性查询和相关引擎入口是明示边界。

剩余

继续做条件字段到运行时实例的绑定、真实任务/关卡上下文、实际世界节点及碰撞注册/分区,然后接攻击型 proc 与全武器逐类验收。武器可构造覆盖仍为 1,053/1,419,不能以本批布局基础设施工作提高武器完成率。

按 godot-master 的数据与表现分层,本批只修改数据和控制路径,没有改正式地图画面,也没有声称做过视觉对拍。候选池为单次装载中间状态;世界持久化和快照接入仍待实现,快照 v29 不变。

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