原版对象工厂、运行时 ID 与初始化阶段
本批落地
- core_sim/NativeSceneFactory.cs:移植 4D7B30 的创建控制顺序,以及编辑器对象运行时 ID 表、描述符后处理顺序。
- core_sim/NativeEditorGuid.cs:原版 UUID 混合、完整 64 位哈希、一次同步 MWC 抽取。
- 组合测试串起工厂 → 默认属性采集 → 空间和资源属性覆盖 → 延迟 Room 初始化 → 真实别名/碰撞缓存。
- 原版工厂参考覆盖 1024 组:896 组正常返回、128 组异常循环的受控截断前缀;没有将后者当成正常完成。
- 完整 ID 生成 2048 组、哈希块/尾部 130 组与发行代码一致。
- 1246 条核心断言、154 项相关 Python 回归通过;Godot C# 构建零警告、零错误,coresimlint 通过。
武器构造覆盖仍是 1053/1419。本批是核心工厂和注册基础设施,不是正式 Godot 地图后端或全武器战斗完成。
4D7B30 的实际顺序
原函数经汇编核对为四个栈参数:descriptor、可选模板对象、可选父对象、立即初始化标记;this 为场景。反编译给出的额外寄存器/参数标注不能直接当接口定义。
- descriptor 为空则返回空。
- 将 descriptor 加到场景使用的描述符管理器。
- 经 descriptor 虚表 +64 构造对象;失败则返回空。
- 检查运行时 ID 冲突;冲突则生成新 ID,调用对象 ID setter,直到不冲突。
- 再检查一次;仍冲突则返回空。
- 设置对象 descriptor 字段 +44。
- 经对象虚表 +20 设置所属场景。
- 如果可选父对象能转为 CPositionableObject,经对象虚表 +8 设置变换父对象。
- 再检查 ID 冲突:记录错误,再生成和设置 ID,循环检查。
- 写入场景运行时 ID 表。
- 调用 4CBBF0 采集 descriptor 默认属性快照。
- 有模板则调用 4CA380;否则有父对象时,经对象虚表 +12 设置父运行时 ID。
- 调用 4CCD20 注册到该 descriptor 的该场景对象集合。
- 请求立即初始化时,调用 descriptor 虚表 +76。
- 调用场景虚表 +324 的新对象通知,然后返回对象。
默认属性采集在运行时 ID 注册之后、模板复制/显式父 ID 设置之前。所属场景和变换父对象的虚调用也必须在第二次冲突检查之前。
NativeSceneFactory 不将这些虚调用折叠成字段赋值;对象适配器必须保留 Room/Group 等实际覆盖方法。模板复制、构造细节、默认快照采集和对象注册各自仍是明确适配器操作,不宣称对应全部原函数已经接入正式游戏。
二进制 50B680 对场景工厂调用时,可选模板/父对象/立即初始化参数都是零;它在工厂返回后再设置父运行时 ID、源 ID 与属性。因此不能在构造成功时就自动加载 Room 资源或等待 Godot 的 _ready 代替原初始化阶段。
运行时 ID 的更换并非普通字典赋值
4D5760
- 保存旧 RuntimeId,然后写入新 RuntimeId。
- 有 Owner 时,经 4D7AE0 更新 Owner 的 ID 表。
- 仅 SourceId 的两个 DWORD 都为 FFFFFFFF 时,才把新 RuntimeId 同步到 SourceId。
- 已有 SourceId 不随 RuntimeId 变化。
CEditorBaseObject 构造时会生成 RuntimeId,并把它同时存到初始 SourceId;二进制装载稍后又将源文件 ID 写入 SourceId。不要据初始构造状态假设两种 ID 永远相同。
4D7AE0
- 旧 ID 在表内不存在:不操作。
- 旧 ID 存在:先移除旧条目。
- 新 ID 已存在:不覆盖新条目,也不恢复旧条目。
- 新 ID 不存在:插入新 ID 到当前对象的关联。
原函数只测试旧键存在,不比较旧键的值是否就是本对象;本批保留其表操作语义,没有改成通用安全字典迁移的另一种结果。
NativeFactoryRegistry 实现此表行为。652830 的比较为高 DWORD 有符号比较、同高位时低 DWORD 无符号比较,等价于 signed Int64 顺序。
运行时对象表的顺序不是 BINLAYOUT 源文件预序,也不是插入顺序。4D6D10 查询入口另外对 ID=-1 返回空,即使底层表含这个键;工厂使用的直接表检查没有这个额外入口门。
异常冲突循环的证据与保护
工厂参考夹具在 SetOwner 回调时主动注册了一个与当前对象同 ID 的其他条目,以覆盖第二次 ID 冲突分支。
原版随后执行:
- 冲突 → 生成新 ID;
- 4D5760/4D7AE0 移除旧键并将本对象插入新键;
- 工厂再查新键,查到本对象,又视为冲突;
- 重复上述过程。
这是夹具触发的原版异常路径,不证明当前原游戏或正式 Godot 场景正在出现该故障。
128 组此类参考在第九次生成 ID 前由辅助进程的 watchdog 停止;结果 state[0]=-1。其字段和调用轨迹只作为被截断的执行前缀验证,不能算成功返回。
NativeSceneFactory 提供明确的 ID 重试限额,默认 1024;测试使用 8 与参考前缀对照。超过限额抛缺输入/诊断错误,不假造对象成功创建。这是移植端的防挂死措施,不是原游戏有同样上限的声明。
EditorMakeGuid 也消耗同步随机流
原调用链:
6774D0 EditorMakeGuid
→ UuidCreateSequential(16-byte UUID)
→ 677120 unsigned range [0, FFFFFFFE]
→ 676B30(g_seed_synced at 361A8A0)
→ 676330(UUID bytes, length=16, returned seed)
→ EDX:EAX 64-bit result关键事实:
- 系统 UUID 是外部输入,不是由地图 seed 单独决定。
- 在 UUID 取得之后,同步 MWC 流推进一次,不是独立的 ID 随机流。
- 返回哈希种子是新 MWC 状态低 32 位对 FFFFFFFF 取模;低位为 FFFFFFFF 时种子为 0。
- 即使 MWC 的零状态推进后仍为零,调用仍发生一次。
- 676330 在 EDX 返回高 32 位、EAX 返回低 32 位。反编译显示 unsigned int 返回类型不完整,不能截断为 32 位。
- 生成 RuntimeId 的调用发生在对象基类构造期;发生冲突时还会增加重试调用。不能只补 Room 的视觉变体随机抽取而漏掉这些推进。
NativeEditorGuid.Create 要求精确 16 字节 native UUID 内存顺序,并接收明确的同步 Mwc 对象。没有输入时不自行用递增编号、源 ID、Guid.NewGuid 或仅地图 seed 替代。正式宿主还需提供系统 UUID/记录重放输入,并将构造期、重试期及后续随机消费者接到正确的同一流。
哈希实现保留 32 位乘法溢出、输入小端 DWORD、两个累加器和最终 64 位拼接;0 至 64 字节的所有块/尾部长度均有原代码参考。用于实际 EditorMakeGuid 的固定 16 字节入口则另外验证完整原生调用链。
Group 和 Room 的初始化阶段不同
重新读取描述符主虚表:
| descriptor | +64 构造 | +76 对象初始化 | +80 场景后处理 |
|---|---|---|---|
| CRandomGroupDescriptor,2165184 | 4B7D70 | 6FEB50 空函数 | 4B6EB0 |
| CRoomPieceDescriptor,2166A7C | 4BA350 | 4B9A00 → 6234B0 | 6FEB50 空函数 |
50BA10 先处理记录的扩展数据,然后依接受对象列表调用 +76;之后通过 4D88C0 依描述符注册顺序调用 +80。
NativeFactoryRegistry.AfterSceneLoad 保留描述符首次注册顺序、引用去重和动态列表长度:某个后处理回调新增的描述符也会在这一轮被访问。没有将遍历改成排序、集合无序遍历或进入循环前的数组快照。
4B6EB0 对该场景的 Group 调用 61F1E0(1),按 byte+606 决定是否调用 620630(0),再清 byte+607。完整 Group 行为仍待适配器接线。
进一步追踪到 6201E0/620630:
- 子对象缓存的候选来自 Owner 的运行时 ID 有序表。
- 筛选 ParentRuntimeId 等于当前 Group RuntimeId 的对象。
- 对可定位对象保存引用和 SourceId,随后调用其虚表 +8 将变换父对象设为 Group。
- 这发生在 Group 后续处理,不是在 BINLAYOUT 读取每个子记录时直接完成。
- 6201E0 还会按实际类型处理子 Group、EditorScene、Room Piece/单位,并清理已失效的引用。
Owner byte+312 还参与旧/新布局相关的 Group 偏移分支;本批只按原字段记录,不把它误认成 BINLAYOUT 第一字节的文件版本。
上述 Group 子缓存、引用释放、隐藏传播、偏移修正以及选择/接受逻辑没有在本批宣称全部实现。它们是接下来构造真实场景状态的必要环节。
组合测试与未接入边界
组合测试使用现有 NativePropertyDispatch、NativeSpatialProperties、NativeRoomResourceLifecycle、F2NativeRoomResourceHost:
- 工厂创建并完成运行时注册,采集空间默认属性;不请求视觉资源。
- 应用位置 (7,8,9),设置真实原版 GUID 和 VISUAL=0。
- 显式调用 descriptor 的延迟初始化。
- 经过 CATACOMB_RESKIN_01 别名,得到目标 GUID -1971246668922108607 并加载已核查碰撞数据。
这验证了各阶段的数据连接。视觉后端和对象虚调用由明确夹具承担,不能据此声称实际模型创建、Ogre 节点、Godot 地图或完整 BINLAYOUT 装载宿主已完成。
NativeFactoryRegistry 目前为分阶段装载的临时数据。正式跨帧/存档集成前必须落实所有权及序列化或重建策略;快照 v29 本批不变。
取证与验证产物
原 EXE: 186472c3057b38f4cdff4696959a943c396ae6166995f7418997b5ea853e8a5e。
- 工厂辅助进程原样执行 4D7B30、4D5760、4D7AE0、652830;构造/描述符/虚调用、GUID 生成、树插入删除分配、RTTI 和日志为明示 spy。
- 工厂正常返回 896 组,其中 128 组创建成功、768 组 descriptor 为空或构造失败;另外 128 组为 watchdog 截断前缀。
- GUID 对照原样执行 6774D0 → 677120 → 676B30 → 676330,仅替换 UUID 外部输入并关闭可选随机日志;2048 组零差异。
- 哈希 130 组零差异。
- 工厂及 GUID 参考均在隔离的 32 位辅助进程中运行,没有 attach 原游戏。
- build/native_scene_factory_oracle/results.json:源文件和工具哈希、范围标签、全部参考状态/轨迹。
- build/native_scene_factory_core.log:1246 断言。
- build/native_scene_factory_python.log:154 检查。
- build/native_scene_factory_godot.log:零警告/零错误。
按 godot-master 的分层方式,本批只改数据和装载控制,不改正式画面,不将构建通过当作视觉验收。main 原地工作,未提交,未修改默认存档;其 SHA256 保持: 9840fe858b6e311c73f8c2d4ed49912d59d71365f6278392811730e012d32a7f。
下一批优先补 Group 子对象缓存/后处理与完整装载宿主,将构造期 ID 的同步 RNG 消耗纳入场景实例化,再接实际节点/视觉后端、世界分区和攻击型触发技能。289 个场景候选仍缺完整随机上下文,不能按此批参考测试自动计为已解决。