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 在候选入队前:
- 若 LEVEL UNIQUE 非零,先查关卡唯一性列表。
- 已使用则不入候选队列。
- 未使用且存在 feature manager,只有 TAG != -1 或 NO TAG FOUND 非零的节点入队。
这个第一次查询即使没有 feature manager 也会发生。只有主题约束的组不入 feature 队列,但主题列表随后会令 HasFeatureConstraint 为真。
50AD70 在读完所有子组后再次查询当前唯一组。已使用时,递归销毁子树并返回空;未使用才把它挂到父组。不能在读子组之前直接跳过整个组,也不能只查询一次。
本批 NativeGroupPoolConstruction.Build 将解析定义与可变的单次加载树分开,复制主题数组,不修改冻结的 NativeLayoutGroup。F2NativeGroupChoices.BuildForLoad 是对应的数据适配入口;旧 ProjectRaw 仍只做未筛选投影。
失效候选的证据与移植边界
构造输入:
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 标记写入之前抛出:
native_group_pool_released_feature_candidate没有 ledger 时保留原预选函数的立即返回。对已释放节点的查找及普通 CHOICE 入口也明确拒绝。
这是针对原生无效引用的安全诊断边界,不是已确定的原版正常行为替代方案。若未来 Mod 或新输入触发它,需要先取得具体输入和堆/生命周期证据。
与装载控制器的串接
组合测试已经将真实解析器、池构建、feature 预选、随机恢复、普通 CHOICE 与 NativeLayoutLoader 的对象读取控制串在一起:
- 构建本次加载池。
- 装载器保存同步随机状态。
- 执行 feature 预选。
- 装载器恢复同步随机状态。
- 执行普通 CHOICE。
- 按 Group 的 Allowed 过滤对象。
- 将真实池上下文传给 Layout Link,并传播 HasFeatureConstraint 到 Group 标记。
- 继续接受队列、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。