完整 BINLAYOUT 数据入口与独立 C# 核验

本批结果

此前 F2BinaryRoomInputs 只提供有序 Room Piece 属性投影;它不足以为统一装载器提供 Group、控制器、扩展数据和完整父子记录。

本批新增完整的冻结数据入口:

  • tools/build_native_layout_programs.py:冻结原始 BINLAYOUT 文件及类型解码指纹。
  • core_sim.io/F2NativeLayouts.cs:独立 C# 文档读取、回写、Group 尾部读取、属性解码和目录加载。
  • core_sim.cli/NativeLayoutAudit.cs:全量独立核验命令。
  • 原有 Room 投影接口和解析报告保留,不覆盖原版文件或重新生成正式地图。

全量结果:

内容数量
原版 BINLAYOUT 文件8,987
对象记录1,492,024
属性记录7,401,033
Group 尾部记录47,224
对象扩展数据1,673,518 字节
Group 尾部数据1,668,136 字节
冻结文件总大小210,661,477 字节

全部文件由 C# 独立读取并重新编码,与源字节一致。全部属性的 C# 类型解码指纹与 Python 一致。源文件集合及 SHA256 同时与前批已完成文本/二进制对照的 8,987 文件清单一致。

读取完整不等于执行完整。 冻结目录和核验报告均明确 native_initialization_complete=false;未因此宣称所有对象已实例化、选择已完成或所有武器可战斗。

完整文档模型

NativeLayoutDocument 保留:

  • 文件版本 byte、第二个 flags byte、Group 尾部偏移。
  • 所有根槽位,包含空记录。
  • 所有 descriptor,不再只导出 Room Piece。
  • 每个对象的 SourceId、原始名称、起止字节偏移和 ParentOffset。
  • 属性原顺序、重复属性及每个原始 payload。
  • 每个对象的 extra 数据。
  • 子槽位顺序及空子记录。
  • 完整的 Group 尾部树。

同一个 SourceId 可以出现在多个不同记录中,不能据此合并对象。文档中的 Offset/ParentOffset 描述当前源文件内的记录位置,不是运行时对象 ID。

名称保留 UTF-16 内容、空格、嵌入 NUL 和代理对;解析阶段不会把名称当成执行指令,也不会自动调用任何名称 setter。

未知 descriptor 或未知属性的原始数据可在文档层保留和回写。类型解码返回未知而不猜值;冻结的原版全量集合没有未知属性。后续执行器必须区分“原版允许跳过的缺 descriptor/属性”与“移植适配器尚未实现”,不能默默把缺实现当原版空处理。

Group 尾部字段含义核对

依据现有 Rust 编译/反编译实现,并结合原版 50AD70 的读取位置,使用以下字段:

尾部顺序本批字段原数据
int64SourceId对应 Group 源 ID,合成根为 -1
byteChoiceCHOICE 编码
uint32RandomizationRANDOMIZATION 整数字段,不按 float 解释
byteNumberNUMBER
uint32ObjectBlockOffsetGroup 对象块的文件偏移,不是标志位
int32FeatureTagTAG ID
byteNoTagRawNO TAG FOUND 的原始字节
byteLevelUniqueRawLEVEL UNIQUE 原始字节,不是普通“已保存组”标记
byteGameModeRawGAME MODE 编码
uint32 count + int64[]ActiveThemesACTIVE THEMES
uint32 count + int64[]DeactiveThemesDEACTIVE THEMES
uint16 count + childrenChildrenGroup 子树

布尔类字段保留原始字节,不在冻结和回写时将所有非零值改为 1。此前独立 Rust 文本回编发现的非规范 NO TAG FOUND 字节仍能原样保留。

这里保留的是原数据,不执行 TAG、LEVEL UNIQUE、游戏模式或主题筛选。50AD70 的运行时派生标记与筛选动作属于后续装载上下文。

对象块引用的全量交叉核验

额外核对 Group 尾部的 ObjectBlockOffset:

  • 44,696 条实际 Group 记录:偏移均对应一个真实 Group 对象块,且对象 SourceId 与尾部 SourceId 相同。
  • 2,528 条合成根:仅将“尾部树根且 SourceId=-1”的记录识别为合成根,不对任意 -1 记录一概豁免。
  • 未解析/悬空引用:0

这为后续装载器提供精确的记录对应关系,不必假设 SourceId 在整个文件中唯一。

证据位置:

  • 原版 50AD70:SourceId 写 +8,Choice 写 +16,Randomization 写 +20,Number 写 +24,对象块偏移写 +28,TAG 写 +92,三个字节写 +25/+26/+27。
  • D:/WizardProject/TL2/tl2-mikuro-mod-packer-rs/src/binlayout.rs 的 emit_dg_node。
  • 同工程 binlayout_decompile.rs 的 read_dg_node 与 ACTIVE THEMES/DEACTIVE THEMES 输出。
  • build/native_layout_csharp_audit.json 的 group_links。

属性解码与指纹

支持当前冻结 schema 的 9 种编码:

  • string:长度前缀 UTF-16,保留内容。
  • u32bool:保留原始 DWORD。
  • u32int、int64:保留有符号整数值。
  • float、g2、g3:保留原始 IEEE 32 位字。
  • curve、u32list:保留前缀数量及每个原始 DWORD。

浮点 NaN/无穷的位模式在数据层不被改写;具体数学消费者继续验证自己的有限值、可逆性等适用条件。不能在读取阶段擅自钳位或补默认数值。

Python 与 C# 使用相同的明确定义指纹格式,但各自独立读取/解码:

  1. 按对象预序写 descriptor、SourceId、原始名称、属性数。
  2. 按属性原顺序写 index、字段键、编码名称。
  3. 字符串写精确 UTF-16 内容;整数写有符号值;布尔同时写原 DWORD 和逻辑真假;浮点/列表写数量与原始字。
  4. 对该内容取 SHA256。

它不是仅比较总数或源文件 hash;重复属性顺序、字符串空格、整数符号和 float bits 的解码差异都会影响指纹。

完整对象 extra 和 Group 树由独立结构回写的逐字节比较覆盖。extra 仍是原始 payload,没有因此被宣布为已编译、已绑定或已执行的逻辑程序。

严格读取和缓存边界

  • 仅支持本次已验证的 v11 framing,不猜旧版本。
  • 验证对象边界、属性长度、额外数据长度、子对象数、Group 尾部边界和主题列表数量。
  • 保留原版空记录槽位。
  • 对异常深度/数量、越界、截断和未消费字节报错。
  • 长度为 0 的特殊属性仍要求原版类型相关解释;当前原版全量集合没有此类条目,不假装支持未来所有 mod 编码。
  • 读取时复制输入缓冲区,不因调用方随后修改原 byte[] 而改变已解析内容。
  • 文件入口校验冻结 schema hash、源文件 hash、记录/属性/Group/extra 统计。
  • 默认还校验独立回写和类型指纹。
  • 目录路径必须解析在当前冻结目录内,不接受目录穿越。

缓存位于 build/f2/native_layout_programs/files,wire_schema.json 和 catalog.json 与之配套。读取正式缓存不需要回到原游戏目录临时查文件。

文档是冻结资源数据,不是场景会话状态;不得把它的数组/记录原地改成某个实例的运行时属性。实例仍应由工厂和状态对象承载。

实际数据到属性派发的组合验证

测试读取 PASS_JD_A 的完整原始文档:

  • 该源文件有 2,158 个对象、68 条 Group 尾部记录
  • 文档同时保留 Room Piece、Group、控制器/触发器类型和非空 extra。
  • 从 Room Piece 的原属性顺序解码 IEEE 字,直接送入 NativePropertyDispatch/NativeSpatialProperties。
  • 位置结果与原始属性位值对应。

这里的 2,158 个对象是一个原始文件,不是递归展开 Layout Link 之后的整张 Echo Pass 世界。测试没有完成整图实例化或实际节点/视觉验收。

命令与验证产物

text
python -m tools.build_native_layout_programs --all
dotnet run --project core_sim.cli -- native-layout-audit
  • build/native_layout_freeze.log:Python 冻结与解码。
  • build/native_layout_csharp_audit.log:全量 C# 核验及 Group 引用结果。
  • build/native_layout_csharp_audit.json:源 hash、工具 hash、指纹、类型数量与引用统计。
  • build/native_layout_program_core.log:1269 条核心断言。
  • build/native_layout_program_python.log:163 项相关 Python 回归。
  • build/native_layout_program_godot.log:Godot C# 构建零警告、零错误。
  • coresimlint 通过。

本批不需要 attach 原游戏,也未写入原版 MEDIA/PAKS。按 godot-master 的数据/表现分层,正式地图和画面未替换;main 原地工作、未提交、快照 v29 未变。默认存档 SHA256 保持: 9840fe858b6e311c73f8c2d4ed49912d59d71365f6278392811730e012d32a7f

下一步把完整数据入口接入执行装载器:原 Group 预选/上下文 → 工厂创建 → 属性派发 → 扩展数据处理 → 统一初始化 → Group 后处理 → 实际节点/碰撞注册。世界分区、完整攻击型 proc 和全武器验收仍属于未完成工作;构造覆盖没有因读取完成而从 1053/1419 改算。