Room 实例碰撞资源替换与共享所有权

本批交付

将原共享碰撞缓存接到 Room Piece 实例的数据宿主,不再由每个 Room 独立保存一份没有引用生命周期的几何对象。

  • 原 622360 setter 与 622F40 碰撞释放段,连接原共享缓存代码,1,512 次操作对照一致。
  • 核心累计 1,445 条断言、270 项相关 Python 回归通过。
  • Godot C# 构建零警告、零错误,coresimlint 通过。
  • Godot 实际资源探针扩展为 26 项检查,验证两份 Room 资源实例的共享/替换/最终释放。
  • F2 未就绪输入继续在 staging 预检时报错,不把提取缺口当作原包不存在。

完整 CCollisionModel 构造/析构、装备实例、导航和正式世界宿主仍未据此标成完成。

622360 正常分支

两个直接 6A4650 调用均是 constant false,不应根据旧名称当成可覆盖的虚函数。代码中的另一条 collision override 路径在此 EXE 不可达。

正常路径顺序:

  1. 比较 request 与 cached request,完全相等直接返回。
  2. 调用共享缓存 release(oldModel),即使 oldModel=null 也照常调用。
  3. 把当前模型槽清零。
  4. request 为空则返回,不清空 cached request
  5. 保存新 request。
  6. collision-enabled=false 时将 cached request 设为空。
  7. cached request 为空则返回。
  8. 查原资源是否存在。
  9. 存在则 acquire 原 request 并写入模型槽;不存在则记录缺失请求。

这里没有额外的动态导航刷新调用;不能为了“接线方便”把 622C20 塞进这个 setter。

必须保留的状态差异

操作模型槽cached request
同名请求,即使 enabled 改为 false原样保留原样保留
不同空请求清空旧模型清空并 release保留旧名字
不同非空请求,enabled=false清空并 release
不同非空请求,资源不存在清空并 release保留尝试的名字
资源存在并成功获取新共享模型新名字

因此:

  • 清空后再次设置相同旧名字,不会重新 acquire。
  • 某次缺文件后,重复相同名字不会自动重试,即使外部后来让资源可用了。
  • 不能用“启用状态变化就重载”或“空模型就重载”的合理化规则替换原行为。

实例结束时的释放

622F40 和 622E80 中都有 release 当前碰撞模型、随后清空模型槽的步骤。原 622F40 还可能销毁 definition holder;本批只实现/验证碰撞所有权部分,没有将共享不可变 F2 catalog 当作该原 holder 的替身。

NativeRoomCollisionSlot.ReleaseForTeardown 保留 release(null) 的调用语义。F2NativeRoomResourceHost 的 IDisposable 包装是幂等的,防止 C# 调用者在结束同一个宿主时重复释放;它不声称原版重复析构合法。

实例释放只减引用,不立即 GC。由共享资源上下文决定何时调用 CollectZeroReferences。

核心与 IO 接线

NativeRoomCollisionSlot 拥有单实例:

  • CachedRequest。
  • 当前 NativeCollisionModelHandle。
  • setter/teardown 的原顺序。

INativeRoomCollisionFiles 提供明确的文件存在判断和缺失记录边界,不能把未知可用性补成存在或不存在。

F2CollisionModelLibrary 是共享上下文:

  • 持有一个 NativeCollisionModelCache。
  • 使用 F2CollisionModelCacheHost 创建原几何输入。
  • 按模型身份保存对应 NativeCollisionAsset,供原顶点 bounds/导航接口使用。
  • 创建、回收时维护 asset 与 handle 的一致性。
  • 允许已验证输入的预检和一次性预读,避免相同共享模型被重复解析。

F2NativeRoomResourceHost 构造器现在必须接收共享 library,不能悄悄为每个 Room 创建孤立缓存。已有工厂、LAYOUT 装载组合测试及 Godot 资源探针均已更新。

视觉对象依然由原视觉 backend 拥有;碰撞宿主 Dispose 不代替视觉节点 Dispose。

staging 预检与原运行时的边界

F2 数据宿主面对的是已导出并审查的输入,不是原 PAK 管理器:

  • 当新请求需要装载时,先确认现有 F2 几何可读取/校验。
  • 未解析、未导出或未知路径在这一步抛出输入缺失,保留旧 staging 实例及其引用。
  • 预检通过后,才进入原 release-before-acquire setter 顺序。
  • 原缺文件分支由核心接口表达,原生对照也覆盖;F2 层不能把“没有导出”简单返回成原文件不存在。

这是明确保留的输入准备边界,并非声称原版所有装载失败都会回滚旧模型。缓存创建中途的异常仍需处理不完整上下文,不能被预检规则掩盖。

两份实例的实际验证

Godot 探针使用真实 rock_03 碰撞资源:

  1. 第一份 Room 完成原定义/视觉/碰撞输入装载。
  2. 第二份 Room 资源宿主只接碰撞链,使用同一个共享 library。
  3. 两者持有相同碰撞 model handle 和 asset,引用数为 2。
  4. 第一份实例切换到 foliage,旧 rock 引用只减为 1;回收不能销毁 peer 持有的 rock。
  5. peer 结束,旧 rock 归零;回收后旧 handle 退休。
  6. 剩余 Room 结束后,缓存没有残留模型引用。

第二份宿主没有另外创建一套视觉节点;此验证针对碰撞共享所有权,不夸大为两套完整正式场景实例验收。

资源探针仍验证真实纹理、镂空材质、原 bounds/四叉树等既有链路。画面配置没有修改,本批新增检查不是新的像素一致性认证。

核心组合测试还检查:

  • 未知 F2 请求失败前保留旧实例及 peer 引用。
  • 空请求的 sticky name。
  • sticky name 相同不 reacquire。
  • 最后 owner Dispose 两次只释放一次。

原生执行范围

tools/native_room_slot_oracle.c 复用已验证的缓存参考基础,实际执行:

  • 622360 正常 setter。
  • 622F40(definition holder 为空,只观察碰撞释放段)。
  • 6A4650、6488C0、68DD70 原 singleton/常量叶函数。
  • 原 acquire/release/零引用收集和实际 MSVCP90 字符串操作。

明确边界:

  • PackageMgr 文件存在结果。
  • 缺失日志入口;不构造整个原 Ogre 日志/UI。
  • 碰撞模型创建/销毁和受控分配器。

输出逐步记录两个实例的 cached name/model 身份、缓存引用和模型创建数、文件检查/缺失/重入回调。没有只比较最后 count,而漏掉中间替换顺序。

代码只在独立参考进程的映射中修补边界调用,不修改原 EXE 或 attach 游戏。

产物

  • core_sim/NativeRoomCollisionSlot.cs。
  • core_sim.io/F2RoomMetadata.cs。
  • core_sim.io/F2CollisionModelCacheHost.cs。
  • core_sim.tests/NativeRoomCollisionSlotChecks.cs。
  • tools/build_native_room_slot_oracle.py、native_room_slot_oracle.c。
  • tests/test_native_room_slot.py。
  • torchlight-2-poc/NativeVisualBackendProbe.cs。
  • build/native_room_slot_oracle/results.json。
  • build/native_room_slot_mismatches.json(空)。
  • build/native_room_slot_core.log、native_room_slot_python.log、native_room_slot_godot.log、native_room_slot_visual.log。

EXE SHA256: 186472c3057b38f4cdff4696959a943c396ae6166995f7418997b5ea853e8a5e

继续推进

后续进展:CCollisionModel构造、装载门与失败状态已补构造和装载/失败控制并接 F2 后端;完整 mesh 资源租约与单位生命周期仍需继续。

Room 碰撞槽与共享缓存的替换/释放接线不再是未知项。下一步继续原 CCollisionModel 的完整初始化、装备与角色的实例资源生命周期,再接正式世界对象集合和攻击遮挡。

武器可构造覆盖仍为 1,053/1,419;动画、攻击型 proc、缺导出资产和全武器实机验收未完成。UniverseComplete=false 未放宽。

按 godot-master 的资源归属要求,共享模型归共享 library,实例只持有引用,视觉与碰撞分别释放。main 原地工作,未提交、未改正式地图/UI,快照 v29 未变。默认存档 SHA256 保持: 9840fe858b6e311c73f8c2d4ed49912d59d71365f6278392811730e012d32a7f