这份文档描述 DIY overlay 系统的验证工作流:将普通对局中的每个玩家转换为 DIY 格式,然后验证 DIY 版本的初始状态(八围、技能等级、name_factor)是否与原始玩家一致。
原始玩家: PlayerA @Team --build--> attrs=[47,60,48,...], skills={fire:5,...}
↓ to-diy
DIY 玩家: PlayerA+diy[83,96,84,...]{...} @Team --build--> attrs=[47,60,48,...], skills={fire:5,...}
↓ 对比
预期结果: 初始八围、技能等级、name_factor 完全一致
↓ 对战
对战日志: fight --out-raw (orig vs diy)
↓ 对比
预期结果: 对战过程一致
现在会额外对比对战过程:
- 通过
tswn-cli fight --out-raw输出标准化日志 - 比较原始对战与 DIY 对战日志是否一致
- 脚本默认使用
ol:格式,技能按顺序排列(顺序即行动顺序),不再使用独立的skill_order字段 - to-diy 输出会省略 0 熟练度技能,保留非零技能行动顺序,同时编码 boost 类型(
"2*base"/"base+boost")
如果你只关心 build 后的初始状态,或预期存在已知差异(如武器禁用),可使用 --skip-fight 跳过对战过程比对。
- 目标是初始状态一致:原始玩家和 DIY 玩家 build 后的八围、技能等级、name_factor 应当匹配。
- 默认对战过程一致:对战日志应与原始对局一致;如需跳过,用
--skip-fight。 - 先小样本验证:默认用少量 case(64~200)快速验证,确认无问题后再扩大规模。
- 差异分级处理:
- 八围不匹配 →
parse_diy或to_diy_compact的 ±36 逻辑有误 - 技能等级不匹配 →
apply_diy_skill_levels或 boost 逻辑有误 - name_factor 不匹配 →
name_factor_enabled处理有误 - 对战过程不一致 → 可能是技能槽顺序、武器禁用或其它逻辑差异
- 崩溃/无输出 → DIY 名字解析失败
- 八围不匹配 →
cargo build --bin tswn-cli# 从号库生成 64 个 case 并验证
python track_diy_roundtrip.py --library tests/sqp6000.txt --max-cases 64
# 带偏移量
python track_diy_roundtrip.py --library tests/sqp6000.txt --max-cases 64 --case-offset 64
# 安静模式
python track_diy_roundtrip.py -q --max-cases 64
# 只比初始状态,跳过对战过程
python track_diy_roundtrip.py -q --max-cases 64 --skip-fighttarget/diy_roundtrip/
├── summary.json # 总体统计
└── <case-id>/
├── input_orig.txt # 原始输入
├── input_diy.txt # DIY 输入
├── status_orig.json # 原始玩家初始状态
├── status_diy.json # DIY 玩家初始状态
├── fight_orig.txt # 原始对战日志
├── fight_diy.txt # DIY 对战日志
├── fight_diff.txt # 对战差异摘要(失败时)
└── diff.txt # 状态差异(失败时)
| 阶段 | case 数 | 目标 |
|---|---|---|
| 冒烟测试 | 16 | 快速验证基本功能 |
| 小样本 | 200 | 发现常见差异模式 |
| 中样本 | 2000 | 确认修复覆盖主流场景 |
| 大样本 | 12000 | 最终验证 |
现象:[WARN] 无法转换: ...,或 DIY 玩家 build 失败。
原因:tswn-cli to-diy 未能解析玩家名字(如包含特殊字符)。
现象:DIY 玩家初始八围与原始玩家不同。
原因:parse_diy / parse ol 或导出逻辑的前七围 ±36 逻辑有误,或 HP 被错误处理。
排查:对比 diff.txt 中的 attr 字段,检查是否恰好差 36 或差某个固定偏移。
ol.attrs 和紧凑 diy[...] 一样使用前七围 +36 编码,解析时前七围会 -36,HP 原样保留。手写 ol:{...} 时不要把内部八围原样写入 attrs。
现象:原始玩家有武器,DIY 版本 weapon_state=None。
原因:当前 DIY 模式禁用了武器。to_diy_compact 不导出武器信息。
这是已知限制:暂不支持武器还原。
技能 boost 类型现已通过 Skill.diy_boost 在 build 阶段自动记录(boost_if_not() → LastBoost,boost_level() → SlotBoost)。to-diy 导出时正确编码为 "2*base" / "base+boost" 格式。
现象:DIY 玩家的 name_factor 与原始不同。
原因:to_ol_json 输出 name_factor_enabled:true,但 DIY 名字计算的 name_factor 与原始不同。
排查:对比两个玩家的 系数 值。
| 情况 | 判定 |
|---|---|
| 初始八围完全一致 | ✅ 通过 |
| 初始技能等级完全一致 | ✅ 通过 |
| 所有 case 通过 | ✅ 全部通过 |
| 八围/技能出现差异 | ❌ 需修复 |
| 出现崩溃 | ❌ 需修复 |
| 项目 | 状态 |
|---|---|
| 初始八围、技能等级、name_factor 一致 | ✅ 全部通过 |
| sqp6000 对战过程一致(64 case) | ✅ 64/64 通过 |
| sqp5900 对战过程一致(500 case) | ✅ 498/500 通过 (99.6%) |
| 完整 action order 往返(40 槽位) | ✅ 已实现 |
| Boost 类型编码(LastBoost/SlotBoost) | ✅ 已实现 |
| skill_order 移除,skills 有序列表 | ✅ 已实现 |
| boost 流程分离(普通 step A / DIY step B) | ✅ 已实现 |
| OL 召唤物模板与技能分类通道 | ✅ 已实现 |
- 武器不支持:DIY 模式下武器不计入(weapon_state = None)。
- 双 boost 技能:同一技能同时被 LastBoost + SlotBoost 命中时(约占 0.4% case),diy_boost 元数据会丢失一个加成类型,导致往返不一致。
- 召唤物模板导出:
to-diy --minions只导出当前可生成或已显式配置的 shadow/summon/zombie 模板,且省略 0 熟练度技能。
- 初始状态一致(八围、技能等级、name_factor)。
- 对战过程一致(sqp5900 500/500 通过)。
| 维度 | sby_test.md | diy_validation.md |
|---|---|---|
| 比对对象 | TS(JS) vs Rust | 原始 vs DIY(均为 Rust) |
| 比对内容 | 对局 round-by-round | build 后初始状态 |
| 工具 | bun + out_md5.ts | tswn-cli to-diy |
| 目标 | Rust 对齐 JS 行为 | DIY 还原初始状态 |
| RNG 是否一致 | 是(同 seed) | 否(不同 name_base) |