Skip to content

Latest commit

 

History

History
217 lines (167 loc) · 14.8 KB

zero-life-and-failure-analysis.md

File metadata and controls

217 lines (167 loc) · 14.8 KB

零血与失败:原版证据及当前差异

维护状态(2026-10-06):现行 EX 契约及原版历史对照;完整记录/锁血/曲终 F 已实现,不给未反馈场景补记验收。 文档职责与最新状态入口见索引。

更新:2026-10-05,功能检查 F14。用户在结束随机讨论后要求继续核对零血与失败。 最初仅作静态追踪与基线检查;随后用户明确选择新的 EX 零血规则并要求实现。 原版证据保留,当前规则以行为规范为准。

用户选定的 EX 规则与实现

EX 无 NF 时归零后继续记录分数、判定次数、准确率、Combo/最高 Combo、Jam/最高 Jam、 药丸进度与消耗;唯一锁定的数值是血量。药丸照常把 BAD 转为 COOL,但不恢复生命。 整曲仍按原生生命周期完成,输出 Rank=F、Passed=false,F 不由最终准确率决定。 NX/HX 仍在归零后停计分并走原生失败流程;NF 仍可恢复;MS 仍走原生 Mania。

Core 用 LifeLockedAtZero 表达零血锁定,用 HasFailed 表达是否需要结束游玩;没有 将 lazer 的 F 或 ScoreInfo 带入内核。HealthProcessor 对已解析结果读取 Core 失败意图, 避免再次独立决定 EX/NX/HX;非领域结果的原生兼容回退仍保留。

Host 仅在 PopulateScore() 输出原生 F,未调用会永久锁住实时评级的 FailScore()。 回放倒退到归零前、重试/Reset 都能恢复正常血量及评级;完整成绩/回放仍由原生导入。 原生 Passed 是不持久化的属性,当前 Realm 与 v5 envelope 都保留 Rank;回放加载和 独立文件导入据 F 恢复 Passed=false。未来个人纪录页/服务器聚合按 F 过滤,成绩与 回放原始记录继续保留;当前未建立额外存档模型或个人纪录页面。

本轮过滤验证:Core 状态 33、宿主计分/回滚/挑战/NF/回放 67、临时 Realm 正式回放 23、MS 回归 22,共 145 项通过。源码模块、计分、存储边界及语义架构检查均通过, 没有新增例外或补丁。测试覆盖归零后的长连击、Jam 晋级、药丸救回、持续锁血、倒退、 重置、F 成绩完整数据写入及临时库关闭重开后加载。实际游玩与结算表现仍待客户端验收。

测试 DLL 已于 2026-10-05 19:23:50(JST)停机备份安装,客户端真实库未改动; 记录见 .artifacts/install-ex-zero-life.json,旧 DLL 保存在独立的客户端备份目录。

范围与证据等级

主参考仍为用户提供的两个韩版客户端,均在本轮重新校验 SHA-256:

客户端 OTwo.exe SHA-256
O2JamO2 2c8ac19bc26a7e1cfde527f5871c22b14fcc5473137101f4be849435d99b3102
O2Jam Classic 83a045418b6219ba340681766b33de2bfbb5ff51b813ef4e2e9dd1def72c823b

Classic 的主程序只在内存中从安装包展开,没有运行或写出可执行文件;O2JamO2 使用 此前的本地客户端只读副本。地址为 image base 0x400000 下的虚拟地址。原始指令清单 位于忽略的 .artifacts/o2jamo2-OTwo.exe.asm 与 Classic 对应文件。没有将替代本地 服务端的规则当作原官方服务器规则。

本报告分别记录:可确认的客户端分支、当前实现、尚未验证的运行表现。计分函数内的 规则只适用于回调实际到达该函数时;不能直接推广到教程、Couple、Live Battle、任务、 所有房间模式或最终服务器持久化。完整零血客户端验收仍未关闭。

修改前实现与逐字段对照

下表的原版列描述普通判定回调继续执行、判定前生命已为 0 的情况。药丸相关的 state 6 条件另列;O2JamO2 计分入口中的特殊房间提前返回不在表内。

项目 修改前非 MS、无 NF O2JamO2 客户端 Classic 客户端
EX/NX/HX 血量表 EX +3/+2/-10/-50;NX +2/+1/-7/-40;HX +1/0/-5/-30 相同 相同
扣至 0 的那次判定 完成该次计分、Combo、Jam、生命更新,再关闭计分 捕获判定前生命,仍完成本次正常分支 同左
后续原始分数、生命、Jam 冻结,生命不再恢复 计分入口跳过这些更新 同左
后续普通 Combo EX COOL/GOOD 继续;BAD/MISS 重置 回调内 COOL/GOOD 继续,BAD/MISS 重置;剩余药丸可使 BAD 根本不进入该回调 同左
后续最高 Combo 冻结 COOL/GOOD 仍可提高 冻结
后续 COOL/GOOD/BAD 次数 原生计分器继续记录 回调内继续记录;有剩余药丸的 state 6 BAD 跳过回调 冻结
后续 MISS 次数 原生计分器继续记录 冻结 冻结
state 6 的剩余药丸 不增减、不救回 BAD;BAD 仍断连 不增减;有药丸的 BAD 跳过普通计分/断连分支 同左
state 6 的连 COOL 药丸进度 完全冻结 药丸不足 5 时,COOL 仍加进度;不发新药丸;GOOD/BAD 仍清零进度 同左
EX 零血后完成整曲 默认失败条件豁免 EX,可进入原生成功完成流程 进入零血 state 6 的路径已确认;最终“通过”标签/奖励语义未闭合 不能仅用 state 6 推定最终“通过”或退场语义

原生判定次数会影响项目的准确率、评级和结果统计,不能把“内核分数冻结”表述为 “所有统计冻结”。修改前也不是选择其中某个原版的完整零血统计规则:最高 Combo 接近 Classic,判定次数沿用 lazer 的完整记录,药丸进度又比两客户端冻结得更多。 上述表格保留作设计选择的依据,不代表本轮新契约。

O2JamO2:普通游玩场景及停止链路

本轮通过 MSVC RTTI 的 CompleteObjectLocator、类型描述符与实际 vtable 指针确认:

原零血分支 所属场景 / 更新入口 vtable
0x4974c8 CStatePlaying,0x497040 0x5a761c,slot 5
0x47869f CStateCouple,0x4784c0 0x5a614c,slot 5
0x4af2b3 CStateTutorial,0x4af190 0x5a87c4,slot 5

因此此前三段零血分支不能拼成一套普通 EX/NX/HX 规则。

普通场景在谱面控制器状态为 2 时检查零血(0x4974c8–0x4976e8)。玩家状态字段是 0x6cdcdb,房间模式字段是 0x6c9d04,难度字段是 0x6c9d05。难度字段在 0x5285f5–0x528616 写入控制器 +0x34,再进入生命表初始化 0x5292d0, 可对应 0=EX / 1=NX / 2=HX。以下保留房间字段的数值,未擅自命名其全部模式。

  • 房间字段为 0:EX 零血将玩家由 state 5 改为 state 6,保存一次统计并报告 0xfae;该分支没有直接停止全部声音的调用。
  • 同为房间字段 0:NX/HX 零血走 0x4975d4–0x49760e,调用 0x461b20, 将玩家置为 state 7 并发送 0xfb5。
  • 房间字段为 1:也可能进入 state 6,经过定时条件及其他参与者状态检查后再置为 state 7;不能沿用房间字段 0 的“NX/HX 立即退场”概括。
  • 房间字段为 4:此处跳过该零血检查。其他房间分支亦未纳入普通规则结论。

声音停止链为 0x461b20 → 0x50d440。后者清空 34 个当前声音引用,并遍历 最多 2000 个样本缓冲,对正在播放者调用 0x5082a0。该函数停止缓冲并将播放位置 重设为 0。此处直接停止声音的证据不等于已经测量失败画面或整套退场的持续时间。

结果快照并非一律停留在零血瞬间

零血 state 6 分支会调用 0x4ff7f0 将本地玩家统计复制到玩家数组。但普通场景在 曲终、控制器状态为 4 且玩家状态小于 7 时,房间字段为 0 允许再次复制,即使玩家 已是 state 6(0x497752–0x4977f2)。复制包括判定次数、生命 +0x212c、 最高 Combo +0x212e、分数 +0x2134;函数范围 0x4ff7f0–0x4ff948。

CStateResult 初始化在 0x498cec–0x498cfd 又将这份玩家数组复制到本场景。 因此不能通过“零血保存过一次”解释为本地最终最高 Combo 必然冻结;曲终可再次 写入已更新的数据。网络回复及官方服务器最终保存策略没有通过这一链路完全证明。 结果中的另一个计算入口 0x49afc0 在生命为 0 时返回值 1,但该返回值进入结果 资源显示;尚未确认它是失败标识还是最低等级,不能据此断言 EX 不算通关。

Classic:普通场景存在网络边界

RTTI 确认 CStateNormalPlaying 的 vtable 为 0x64b39c,slot 5 指向 0x4efe70;此更新函数在控制器状态为 2 时调用 0x4f0550。 CStateLivePlaying 的更新入口为 0x4ea8b0,其零血 helper 0x4eaec0 属于另一场景,不能混入普通玩法。

普通零血 helper 检查房间字段 0x7789e4、生命 0x77ee50 及玩家状态 0x77cd33。在该检查路径上,没有 O2JamO2 普通场景的 EX/NX/HX 二分: 生命为 0 且玩家 state 5 时复制统计、调用事件 0xfae,然后将全局玩家及控制器 内的玩家副本置为 state 6(0x4f0550–0x4f061b)。房间字段为 3 时跳过。

虚调用 +0x84 落到 0x4ec6e0 → 0x4dd460,其 0xfae 分支在 0x4dd91e–0x4dda86 组装并发送网络消息,包含生命及相关本地状态,而不是直接 启动一段已知的本地失败动画。后续定时路径会检查参与者状态,再报告 0xfb0 并置为 state 7;曲终也有独立路径。不能在缺少官方服务端与运行证据时,宣布 Classic 与 O2JamO2 的 NX/HX 退场契约完全相同或相反。

计分与药丸的关键分支

普通计分入口为 O2JamO2 0x5288f0、Classic 0x570510;两者捕获判定前生命。

  • O2JamO2 的普通 Combo 和最高 Combo 更新位于 0x528a78–0x528acd,早于 后续生命检查;最高 Combo setter 0x4fed00 没有额外生命检查。
  • Classic 的普通 Combo 位于 0x570638–0x570660;生命检查在 0x570667–0x57066d,最高 Combo 更新在 0x570673–0x5706a7,被零血跳过。
  • O2JamO2 COOL/GOOD/BAD 计数在生命检查之前,入口分别见 0x5289b6、 0x528a1e、0x528bb7;MISS 在 0x528bf6 先查生命。Classic 各计数均 在生命检查后,对应 0x570551、0x5705cb、0x5707eb、0x570836。
  • 连 COOL 的药丸进度发生在计分入口之前:O2JamO2 0x5271dd–0x52723e、 Classic 0x56e4a3–0x56e51e。已有药丸不足 5 时仍增加进度,但 state 6 阻止满 15 后发药丸及本次清零。这与冻结整个进度字段不同。
  • BAD 在 O2JamO2 0x5274b0–0x527542、Classic 0x56e7fd–0x56e8ab 先清零连 COOL,再检查剩余药丸。若有药丸且玩家已是 state 6,跳过消耗药丸、 COOL 救回及 BAD 普通计分;若没有药丸,则仍进入 BAD 普通计分回调。
  • GOOD 在药丸/普通分支先清零进度,例如 O2JamO2 0x52736b–0x527380。 不将该结论扩大为零血后 MISS 必定清零:O2JamO2 的 MISS 清零位于生命检查后。

这些可能包含旧客户端边缘行为,确认“有差异”不表示应无条件复刻其统计遗漏或 剩余药丸导致的特殊断连语义。应先决定目标规则,再写入独立内核。

lazer 接入政策与分层问题

当前文件责任:

  • O2Jam.Core/O2JamGameplayState.cs:血量锁定、EX 持续计分、NX/HX 的内核失败 意图、Combo/Jam/药丸;不依赖宿主。判定次数继续由原生计分器记录。
  • Integration/Scoring/O2JamJudgementHistory.cs:历史与回滚;不是另一份玩法规则。
  • Integration/Scoring/O2JamHealthProcessor.cs:把已解析生命增减交给原生 HealthProcessor;解析结果读取 Core 失败意图,只有兼容回退按原生条件处理。
  • Host/Gameplay/Scoring/O2JamScoreProcessor.cs:同步内核 Combo/分数,原生 sealed 计分入口仍累计判定次数、准确率;非 MS 设置 ApplyNewJudgementsWhenFailed=true。
  • 原生 Player:失败流程、失败 Mod 覆盖、成绩失败标记、动画、失败菜单与退出。

本轮按以下边界整理:

  1. Core 是已解析判定的失败意图来源;HealthProcessor 翻译该意图,再由宿主结合 NF、SD/PF 等原生 Mod 处理退出,不另建正常 EX/NX/HX 规则表。
  2. 血量锁定、计分开关、结束游玩的意图与最终成绩通过状态分别表达。EX 可以完整 游玩而记录 F;不会因为结果需要 F 而提前触发原生失败动画。

以下是已有、应与原版无 Mod 对照分开的接入政策:

  • NoFail 使 Core 不进入零血冻结,允许继续计分和恢复生命;原生失败覆盖阻止退出, 原生 Mania NF 分数倍率 0.5 仍由宿主处理。这是项目已测试的 Mod 政策。
  • Mania Score 使用原生 Mania 路线,不以原版 O2Jam 零血规则覆盖它。
  • 原生默认失败动画时长 2500 ms;Player.PerformFail() 先启动动画并调度失败成绩 收尾,onFailComplete() 再停止 GameplayClock。O2JamO2 的上述特定分支直接 停止样本;二者的音频/退场时序不同。保持原生动画是接入取舍,是否收紧失败后的 判定/统计接受范围则是另外一个问题。

宿主参考为本地 osu 正式 2026.1005.0-lazer 对应源码: osu.Game/Rulesets/Scoring/{HealthProcessor,ScoreProcessor}.cs 与 osu.Game/Screens/Play/{Player,FailAnimationContainer}.cs。本轮没有为这些差异添加补丁。

静态检查阶段的历史基线与原版证据验收

实现新规则之前使用已有 Release 构建、--no-build --no-restore 运行过滤测试:

过滤对象 结果
Core O2JamGameplayStateTest 31 项通过
NoFailKeepsScoringAndLifeRecoveryWithNativeMultiplier 与 NativeRollbackRestoresSnapshotsAcrossLifeDepletion 5 项通过

TRX 位于 .artifacts/zero-life-analysis/tests/。没有运行 benchmark,也没有修改测试来 把原版待确认行为写成既定规则;36 项仅证明当前实现满足已有契约。

原版实测应尽量缩小到三组场景,而不是全库游玩:

  1. EX 无药丸归零后,完成一段长于归零前最高 Combo 的连击,再各产生 GOOD、BAD、 MISS,核对显示与最终最高 Combo/计数,另确认完成整曲是否算通过。
  2. EX 先取得并保留一颗药丸,用 MISS 耗尽生命;归零后连击、触发 BAD,再命中, 核对药丸是否仍在、Combo 是否被 BAD 打断、统计如何变化。
  3. 同曲 EX/NX/HX 对照零血后的输入、KS/BGM 与退场。Classic 若需要替代服务端运行, 必须标注结果可能含服务端规则,不得当作原官方退场验证。

这些步骤保留用于继续核对原版证据;用户已明确选择新规则,不再把复刻上述原版 冻结行为作为实现前提。F14 的新契约已实现并通过自动验证,客户端验收待完成。 验收时重点确认:EX 漏判归零后继续游玩/计分,命中不回血,药丸/Jam 正常,曲终 F 且统计完整;回放倒退归零前及 NF/MS 行为正常。无需为验收清空曲库。