原版字符注册顺序、v29 与 proc 遮挡策略
本批继续在 main,没有创建 worktree、提交或 attach 原版游戏。使用 godot-master 的状态归属规则:注册顺序由核心拥有并保存,不从画面排序反推。
已实现
NativeLevelList按原版双向链表头插、摘除首个匹配节点,保留其他节点顺序。SimState保存按场景分开的字符注册表;Godot 场景开始、静态战斗单位绑定、动态刷怪与离场均接到该表。- v29 保存精确顺序,旧版缺失时保留未知,不按数字 ID、距离或 Dictionary 遍历猜顺序。
NativeLosPolicy保存已确认的遮挡分支:根结构门、block-only、单面穿越和严格最近距离比较。- 原始 EXE 字节直接进入 Python 检查,避免由错误的反编译参数名生成“金值”。
**仍有明确边界:**当前实时表只包含已绑定的战斗字符,UniverseComplete=false。它不是原版完整场景单位全集;友方 NPC、装备/道具列表以及原版场景生成器的全部初始注册次序尚未一并证明。不得拿这张部分表放行需要完整查询的攻击型 proc。
构造统计不变:1053/1419(14 成长),319 个 host/NG+ 待补、47 个基础等级缺口。全武器目标未完成。
注册顺序的原版证据
只读原版 E:/Torchlight 2/Torchlight2.exe.i64,EXE SHA256: 186472c3057b38f4cdff4696959a943c396ae6166995f7418997b5ea853e8a5e。 C/ASM 保存在 build/research_weapon_families/。
| 原版入口 | 规则 |
|---|---|
| 5E7A90 | 将字符注册到 level+148 与 level+140;PLAYER另进入+136 |
| 5E8190 | 装备进入 level+152 与+144;不是字符表 |
| 66EC50 | 分配12字节节点:value、next、prev;新节点插到头部 |
| 5E8010 | 字符移除时找到首个对应节点并摘除 |
| 5E8560 | 装备从其各个列表摘除 |
| 5E14C0 | 修复前后链接,不交换末尾节点、不重排幸存者 |
66EC99..66ECA2 的直接字节为 89 48 04 8B 16 89 42 08 89 06: new.next=oldHead,oldHead.prev=new,head=new。
例如按 2、7、3 注册,查询遍历顺序是 3、7、2。删除7后为3、2;再次注册7变成7、3、2。 原版插入 helper 本身不去重,所以核心也没有偷偷去重;正常场景调用者仍应正确管理注册生命周期。
v29 的场景表按场景名排序仅用于稳定序列化,场景内字符顺序不排序。返回的数组是拷贝,外部不能修改权威顺序。恢复拒绝指向不存在 CoreSim 单位的记录。
遮挡调用并不是“碰到任何物体就阻挡”
目标查询调用 5F3060。它首先要求场景碰撞数据、三角数据及分区结构存在;这个外层门覆盖所有后续阶段。
随后依次处理:
- 5E0930 的单位/装备碰撞阶段;
- 4672E0 的静态碰撞网格;
- 622950 的附加 Room Piece。
最近命中只在距离严格更小时替换。后续已核实:比较对象是x87当前距离和前一轮写回的float,不能比较两个float或把几何同距离一律当作保留更早候选;见多面射线的sqrt(5)反例。不自行加epsilon或按对象ID决胜。
同一个 block-only 参数
5F312C 压入 literal 1,传给5E0930最后一个参数。该参数同时控制:
- 角色:block-only=true 时跳过字符碰撞。
- 装备:block-only=true 时要求 COLLIDEABLE(+393)、当前碰撞活动(+411)及 BLOCK(+394)。
BLOCK 是515F90读取的原版 DAT 键,缺省 false;不能把“物件有碰撞”自动当作“阻挡这条proc视线”。
IDA 将两个比较显示为不同 arg 名,是 Ogre 调用栈分析漂移造成的。直接读取 EXE:
5E0998: 80 7C 24 60 00
5E0A5C: 80 7C 24 60 00两处都是 cmp byte [esp+0x60],0。不能依赖那份反编译把它们当作两个不同参数,更不能把其中一个误读成输出向量的低字节。
静态三角形与 Room Piece
4672E0 先做包围盒与候选过滤,再比较起终点的平面分类: 分类不同且起点分类不是1,才进入线段平面交点与三角形内部判断。它是单向穿越,不是任意双面相交。
683800 使用约1e-5的两侧容差,产生0/1/2三类;2表示平面附近。两个端点同属2时不会被简单的跨面检测命中。 6838C0 的 x87 数据流还混用保留寄存器精度与写回float的临时量:X坐标路径和Y/Z路径并非完全相同。完整数值移植与碰撞数据生产仍待验证,当前没有以普通 Godot raycast 或未经证明的通用三角算法替换它。
622950 还检查碰撞数据、Room Piece的实例碰撞/可见条件、若干实例/网格标志,并对端点与物件中心的 X/Z 偏差做50单位门;不能只用一个全局 mesh 名称白名单。
实际场景接线与验证
- 场景重建开始时明确开启一个新的当前注册序列,先注册现有玩家,再按绑定事件注册字符。
- 动态刷怪走同一头插入口;离场清理同步摘除,未混用数字 ID 排序。
- 读档已有序列按保存数据恢复;没有记录的旧版仍是未知。新场景重新构建所观察到的注册事件与“恢复旧序列”是两种操作。
build/proc_registry_core.log:1144 条核心断言通过,含重复插入、首个删除、跨场景、存档不别名、旧档未知和LOS分支。build/proc_registry_python.log:69 项相关检查通过,含原始PE字节锚点。- Godot C# 零 warning/error;coresimlint通过。
scene_query主场景探针验证实际绑定顺序、v29恢复、离场后其他位置不变、重新进入到头部。该表仍明确报告非完整原版全集。- 原有冠军武器的
growth主场景10杀/升级/读档重放回归也通过。 - 无新视觉资源,不使用旧截图声称攻击型proc已经运行。
- 默认存档 SHA256 保持
9840fe858b6e311c73f8c2d4ed49912d59d71365f6278392811730e012d32a7f。
剩余:完整场景单位与道具成员生产、实际激活条件、原版碰撞数据/数值、Line Emitter伤害与技能随机流。继续这些,不以“顺序已存档”代替“所有武器完成”。