Group 条件字段、构造默认值与装载接受判定

后续:Godot节点后端与TL2定制Ogre可见性已落地实际节点层;下文保留本批历史状态。

本批结果

补全 NativeGroupConditionState、NativeGroupConditionProperties 与 F2NativeGroupConditions,连接“原始属性 → 原属性派发器 → 实例条件状态 → 接受判定”。

124 组原版机器码参考的构造结果、字段存储、getter 字节数/内容、难度枚举解析和格式化均与核心一致。核心累计 1,357 条断言通过,Godot C# 构建零警告、零错误,coresimlint 通过。194 项相关 Python 回归全部通过。

本批推进的是完整世界装载所需的条件接线,不提高武器可构造覆盖,也不宣称正式 Godot 世界宿主已经接完。

原版字段绑定

IDA 的 CRandomGroupDescriptor 构造函数 4B73B0 注册下列字段。注册顺序为 102–109,flags 均为 0x40008;DIFFICULTY MODE 的 Kind=4,其余 Kind=5。

属性号原名称实例偏移核心条件
102DIFFICULTY MODE+596Difficulty
103DUNGEON+568LevelIdentity
104CLASS+512PlayerClass
105ACTIVE OR COMPLETE+456StartedQuest
106COMPLETE+372CompletedQuest
107NOT COMPLETE+484NotCompletedQuest
108ACTIVE+400ActiveQuest
109NOT ACTIVE OR COMPLETE+428NotStartedQuest

这些属性号属于 Group 描述符,不是全局字段编号。例如 104 在 Room Piece 中是 SCALEX,在 Group 中是 CLASS。新适配器先确认描述符,不能用一个按编号全局分派的空间属性表误处理字符串。

Kind=5 的字段虽然注册了 converter 指针,但原通用派发器不会把它们当数值枚举转换。难度字段则必须先做文本枚举转换,再向实例写入 DWORD。

只实现条件字段的处理器对其他字段返回 false。调用方必须继续路由剩余字段,不能把 false 当作“已经成功处理整条对象记录”。

构造默认值与全局输入

620A20:

  • 七个条件字符串初始化为空。
  • 难度初值是 DWORD 0xFFFFFFFF。
  • byte+601 最终从 405B90 返回的全局设置对象 byte+356 复制。
  • 该复制发生在构造器的可见性调用之后。

因此,新条件状态要求明确提供 globalIgnoreConditions 字节;没有写一个默认 false 来掩盖缺失的全局上下文。保留 raw byte,判定时按非零处理。

两个空字符串全局分别在 1A33840、1673930 由 UTF-16 空字面量 2131C4C 初始化,原始机器码及字面量字节纳入参考来源检查。没有根据 IDA 对未初始化数据区的显示值猜测字符串。

本批仅接构造器的条件数据切片。基础对象构造、场景可见性、实际节点等仍是另行实现的部分。

难度转换不可“统一默认值”

原 4B6CD0:

text
EASY      → 0
NORMAL    → 1
HARD      → 2
VERY HARD → 3
空或其他  → 14

精确比较,不 trim、不转大写。normal、Very Hard、NORMAL 后接空格都不是对应枚举名称。

原 4B6D50:

  • 0–3 格式化成上述名称。
  • 其他 DWORD 格式化为空字符串。

于是构造初值 FFFFFFFF 经 getter/格式化得到空,再经文本 setter 会变成 14。两者都属于“不限制难度”的判定分支,但存储位值不同;不能为了 round-trip 看起来漂亮而擅自改成同一个值。

原难度 setter 直接从有效指针读一个 DWORD,不使用 word-count 参数;参考包括 word-count=0。核心保留这种读写语义,并对无效缓冲区长度明确诊断。

字符串写入、顺序与默认快照

原 setter 只复制字符串:

  • 大小写和前后空格保留。
  • UTF-16 零字符结束字符串,后续字符不属于存储值。
  • 相同属性重复出现时,以实际调用顺序覆盖,不去重排序。
  • 空任务字符串不自动解释为某个任务名称。

二进制适配器读取 ushort UTF-16 长度、核对 payload 总长度,再交给已有 NativePropertyDispatch。属性记录不会越过明确边界读取;直接传入未终止字符串的无效指针形状会报诊断错误。

条件绑定没有擅自添加依赖回调。原 flags 不含默认回放筛选位 0x20000,故默认快照可捕获这些属性,但不凭空将其加入 Replay 队列。

绑定使用装载局部的 int handle;不是把原版 64 位 SourceId/RuntimeId 截短成 int。

与装载器组合验证

新测试沿 NativeLayoutLoader 的真实控制顺序运行:

  1. 工厂构造对应的条件状态。
  2. 按二进制记录顺序应用属性。
  3. 调用 NativeGroupConditions。
  4. 通过后才进入接受、源 ID 映射、extra 和初始化队列。
  5. 拒绝时销毁该 Group,并跳过后代。

组合案例使用明确的 EMBERMAGE 玩家类型及已完成 INTRO 任务输入。匹配时六个夹具对象被接受;CLASS=OUTLANDER 时根组拒绝,接受/extra/源映射均为空,仅销毁已创建的根组。

这是构造、属性和条件门的组合测试,不是接入真实运行中任务管理器的证明。工厂场景对象、额外程序执行等仍使用明示测试宿主。

原版冻结文件实际上使用了什么

重新执行完整 8,987 文件 C# 审计,读取、回写和类型指纹仍全部通过。实际 Group 属性出现次数:

编号名称次数
42POSITION3,291
97VISIBLE374
98DYNAMIC1
101CHOICE2,016
116NO ROOMPIECE COLLISION1
118GAME MODE31

八个条件字段出现次数为 0。先前观察型 schema 列出七类 Group 属性,其中 LEVEL UNIQUE=115 在本次冻结集也没有实际序列化记录;不能把字段表中的“有定义”当作资产中的“有使用”。

首次测试把 schema 的七个键当作实际出现集合,因此失败;已依据完整审计改为校验上述六类的精确次数。没有改输入或忽略失败来伪造覆盖。

这不证明所有原版条件永远为空:构造全局设置、其他数据入口和运行时写入仍需各自验证。本批也不修改冻结集的观察型 schema,避免把没有出现过的字段说成已经做过全量文件验证。新的条件适配器可处理明确构造的原始 Group 记录。

参考执行范围

原函数:

  • 620A20 条件初始化所在构造函数。
  • 4B6290/4B62B0 及七组字符串 setter/getter。
  • 字符串 getter 的 475560。
  • 难度枚举 4B6CD0/4B6D50。

基础对象、可见性调用、全局设置对象、CRT 字符串与 memcpy、Vector3 构造是明示边界。没有执行完整原游戏/CRT 堆。参考工具只重定位代码、IAT 和跳转表,不改变难度分支或字段写入指令。

原 EXE SHA256: 186472c3057b38f4cdff4696959a943c396ae6166995f7418997b5ea853e8a5e

产物:

  • tools/build_native_group_fields_oracle.py、tools/native_group_fields_oracle.c。
  • build/native_group_fields_oracle/results.json。
  • core_sim/NativeGroupConditionProperties.cs。
  • core_sim.io/F2NativeGroupConditions.cs。
  • core_sim.tests/NativeGroupFieldChecks.cs、NativeLayoutLoaderChecks.cs。
  • tests/test_native_group_fields.py。
  • build/native_layout_csharp_audit.json 的 group_condition_fields。
  • build/native_group_fields_core.log、native_group_fields_python.log、native_group_fields_godot.log。

下一步和边界

继续实际世界宿主:将已有工厂、Group 属性/条件、节点、Room 资源、碰撞与注册接口连接到同一个装载实例,并提供真实关卡/任务/全局设置。随后继续世界分区/遮挡、攻击型 proc 和全武器验收。

当前字段状态仍是单次装载中间量,不伪装成已持久化的世界状态。快照 v29 未改。武器可构造覆盖仍是 1,053/1,419;完整战斗和视觉验收不能从本批测试推导。

遵循 godot-master 的数据/表现分层,本批未改正式画面,也未声明视觉核验。main 原地工作、未提交、未 attach 原游戏;默认存档 SHA256 保持: 9840fe858b6e311c73f8c2d4ed49912d59d71365f6278392811730e012d32a7f