plan-satiety-hydration-v1 PR-1:落地饱食水分 P0 底盘 - #1259
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughSummary by CodeRabbit
Walkthrough本次变更新增营养双轴模型、活动衰减和开发命令,并将其接入持久化、hydration、复活、转世及新角色流程;同时加强生命周期事务、角色身份校验、事件契约和系统调度约束。 Changes营养系统与生命周期持久化
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant Player
participant NourishCommand
participant Nourishment
participant Chat
Player->>NourishCommand: nourish set/show
NourishCommand->>Nourishment: 更新或读取营养轴
Nourishment->>Chat: 返回数值和分档
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
/review |
🔭 Review · PR #1259❌ 未通过:0/4,存在 32 项真实 finding,要求修改
📋 Plan 原意确认
🧑⚖️ 复投结果
🔎 Findings
首轮与复投原始结构化摘要[
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 96,
"plan_intent": {
"status": "misaligned",
"reason": "PR 覆盖了 Nourishment、五段边界、持久化、生命周期和 dev 命令等 P0 接入面,但周期消耗的两个核心契约发生了结构性漂移:消耗由 Update 执行次数而非 CombatClock 的 200-tick 节拍驱动,活动倍率也从“窗口级 1.0/1.5/3.0、dash 优先”改成了未在 plan 决议中的逐 tick 加权平均与 20-tick lease。因此不能确认其符合关联 plan 的原意和数值验收边界。",
"missing": [
"按 CombatClock 每 200 tick 结算且同一 clock tick 不重复累计的生产契约",
"按 plan 对整个 sweep 窗口选择单一活动倍率,而不是逐 tick 加权平均",
"正式 Revive 分支重置并立即持久化 80/80 的专门状态转换测试"
]
},
"summary": "P0 大部分接线已经落地,但周期消耗的时钟来源和活动倍率语义均偏离 plan 锁定公式,会直接改变实际消耗速度。另有正式复活这一明确验收分支未被新增测试锁定,需 Request changes。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "198-219, 540-586",
"title": "周期消耗按 Update 次数累计,而不是按 CombatClock 的 200 tick 节拍",
"evidence": "tick_nourishment 每次系统执行都无条件调用 activity_window.record,是否结算只检查 total_ticks >= 200;CombatClock 仅用于移动 lease 的 now_tick。production_tick_waits_for_full_window_then_applies_loss_and_resets 测试把 CombatClock 固定为默认 tick,连续调用 200 次 app.update 仍期望发生扣减,明确证明当前消耗与 CombatClock 是否前进无关。这不符合 plan §1/§4 要求的 CombatClock 同源、每 200 tick sweep。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "76-85, 104-119, 174-187, 503-518",
"title": "活动倍率被改成逐 tick 加权平均,偏离 plan 的窗口级倍率",
"evidence": "NourishmentActivityWindow 分别累计 idle/move/dash tick,weighted_activity_ticks 将三者按 1.0/1.5/3.0 求和;mixed_window_uses_tick_weighted_average_not_peak_activity 进一步明确锁定了“不是 peak activity”的新语义。plan 则规定“该 sweep 窗口内有水平位移 ×1.5,Dashing ×3.0”,并锁定每轴公式为 base_loss × activity_multiplier × realm_multiplier,即每个 sweep 使用一个活动倍率且 dash 优先。新增 20-tick movement lease 也未进入 plan §8.1 决议。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1569, 3363-3448, 4525-4548, 5061-5074",
"title": "缺少 plan 明确要求的正式 Revive 重置持久化测试",
"evidence": "实现于 revive_lifecycle 中重置 Nourishment 和活动窗口并排队持久化,但本 PR 新增生命周期断言只覆盖 NearDeath 自救保留、Reincarnate 重置和 CreateNewCharacter 重置;没有测试从非默认食水值驱动正式 Revive,断言 ECS 变为 80/80、窗口清零且 SQLite 同步更新。plan §4 和 §8.1 #3 明确要求“正式复活重置”专门测试,PR Body 也声称已补齐生命周期饱和测试。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "REQUEST_CHANGES",
"confidence": 96,
"plan_intent": {
"status": "misaligned",
"reason": "模块注册、命令 registry、生命周期重置和主要保存路径已接入,但 P0 的核心运行契约未完全按 active plan 落地:消耗由 Update 次数而非 CombatClock tick 驱动,活动倍率被实现为逐 tick 加权平均而非窗口级倍率;同时 plan 指定的 canonical cultivation bundle 保存入口仍会写入 null,存在非默认食水状态被后续保存链抹除的路径。",
"missing": [
"以 CombatClock 为唯一节拍源的 200-tick 消耗门控",
"按 plan 约定选择窗口级 idle/move/dash 倍率,dash 优先",
"保证所有 cultivation bundle 保存入口都不会丢失 Nourishment",
"真正聚合同 tick 多个 MovementEvent 后再做水平位移阈值判断"
]
},
"summary": "P0 的主要组件和注册路径不是孤立 stub,但持久化兼容入口具有破坏性,且消耗节拍与活动倍率偏离 plan 已锁定语义。MovementEvent 的所谓同 tick 聚合也只实现了“任一单事件命中”,分包后的有效位移仍会漏记,因此不能通过。",
"findings": [
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6167-6236",
"title": "旧 cultivation bundle 保存入口会把已有食水状态覆盖为 null",
"evidence": "`persist_player_cultivation_bundle` 仍是公开的 canonical 入口,但现在固定向新函数传入 `None, None`;下方 `json!` 又无条件写出 `\"nourishment\": nourishment` 和 `\"nourishment_activity_window\": nourishment_activity_window`。因此经该入口保存会把整行中的现有值改成 null,重连时自定义反序列化会回退到 80/80 和空窗口。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "198-220",
"title": "周期消耗按 Update 调用次数推进,没有真正接到 CombatClock 的 200 tick 节拍",
"evidence": "`tick_nourishment` 将 CombatClock 声明为可选资源,缺失时用 tick 0;无论 clock.tick 是否变化,每次系统运行都会执行一次 `activity_window.record(activity)`。对应测试也在 CombatClock 始终不递增时调用 200 次 `app.update()` 并期望发生扣减。这与 plan 指定的 CombatClock 同源节拍及 200 tick 门控不一致。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "72-84, 174-187",
"title": "活动倍率被改成逐 tick 加权平均,偏离 plan 的窗口级倍率契约",
"evidence": "`weighted_activity_ticks` 将 idle/move/dash tick 分别乘 1.0/1.5/3.0 后求和,`sweep_losses` 再按加权分钟扣减;测试还明确锁定 `mixed_window_uses_tick_weighted_average_not_peak_activity`。但 active plan 写的是“该 sweep 窗口内有水平位移 ×1.5,Dashing ×3.0”,并规定一次扣减使用单个 `activity_multiplier`,dash 优先。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "128-146",
"title": "同 tick MovementEvent 没有聚合,分包位移会被漏判为静止",
"evidence": "`record_movement_events` 对每个事件单独调用 `has_qualifying_horizontal_movement`,只有某个单事件 delta 大于 0.05 才刷新 tracker,没有按 entity/tick 汇总。比如同 tick 的 0→0.03 和 0.03→0.06 两个事件总水平位移为 0.06,但两个事件都会被拒绝;现有测试只覆盖“大事件后零位移事件不会擦除”,不能证明 PR Body 声明的“同 tick 多事件聚合”。"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 94,
"plan_intent": {
"status": "misaligned",
"reason": "组件、六境界倍率、生命周期重置和主 schedule 接线基本覆盖 P0,且未触碰真元或绕开 qi_physics;但周期消耗没有以 CombatClock 的真实 200 tick 为准,活动倍率被改成了 plan 未定义的逐 tick 加权平均,同时持久化兼容入口会写入 null 并可能造成重连免费重置,因此尚未符合 plan 锁定的消耗与持久化语义。",
"missing": [
"按 CombatClock 实际推进且同一 tick 至多结算一次的 200-tick 周期契约",
"按 sweep 窗口最高活动等级应用 idle/move/dash 倍率,而非未决议的逐 tick 加权平均",
"同 tick 多个 MovementEvent 的真实位移聚合",
"任何 cultivation bundle 写入都不得把既有 nourishment 状态覆盖为 null"
]
},
"summary": "P0 的主要结构已经接通,六境界常数与世界观、灵气守恒也无冲突。但消耗时钟、活动倍率和持久化写入存在可复现的契约偏差,会导致速率错误或重连恢复到 80/80,需修改后再审。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "190-210",
"title": "周期消耗按 Update 执行次数推进,而不是按 CombatClock 的真实 tick 推进",
"evidence": "tick_nourishment 每次被调度都无条件 record 一次,clock 只用于 movement lease,甚至是 Option<Res<CombatClock>>;测试 production_tick_waits_for_full_window_then_applies_loss_and_resets 在 CombatClock 始终为默认值、tick 完全未推进时调用 200 次 app.update 仍期望扣减。这证明重复执行同一 clock tick 会多计,clock 跳 tick 又会少计,不满足 plan 的“CombatClock 节拍、每 200 tick”契约。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "58-81,167-178",
"title": "活动倍率被改成逐 tick 加权平均,违反 plan 锁定的 sweep 窗口倍率",
"evidence": "weighted_activity_ticks 将 idle/move/dash tick 分别乘 1/1.5/3 后求和;例如 199 tick idle 加 1 tick dash 的实际窗口倍率只有 202/200=1.01,而 plan 明确规定“该 sweep 窗口内有水平位移 ×1.5,Dashing ×3.0”,并要求 dash 优先。一次移动事件经 20-tick lease 后也只得到约 1.05 倍,而不是窗口的 1.5 倍。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "125-142",
"title": "同 tick 多 MovementEvent 没有聚合位移,分片移动会被误判为空闲",
"evidence": "record_movement_events 对每个事件独立执行 hypot(delta_x, delta_z) > 0.05,只实现了“任一单事件超过阈值”。同一 tick 连续两个同向 0.03 格事件累计移动 0.06 格时,两次都不合格,tracker 不刷新;这与 PR Body 声明的“同 tick 多事件聚合”不符,现有测试也只覆盖“大位移事件后跟零位移事件”,没有覆盖分片位移。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6167-6236",
"title": "旧 cultivation bundle 写入口会把 nourishment 确定性覆盖为 null",
"evidence": "persist_player_cultivation_bundle 仍是公开的完整行写入入口,但它委托新函数时固定传入 None/None;新函数又始终序列化 \"nourishment\": nourishment 和 \"nourishment_activity_window\": nourishment_activity_window,因此调用旧入口会覆盖为 JSON null。下次 join 对 null 解码失败或回退默认值,玩家会免费恢复到 80/80。player/mod.rs:80-104 的生产查询也把两个组件声明为 Option,并在 393-425、536-566、686-728 原样传入,同样保留了缺组件时写 null 的路径。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 96,
"plan_intent": {
"status": "misaligned",
"reason": "组件、五段边界、六境界倍率、命令和主要生命周期接线大体符合 P0,但周期消耗的时钟门控和活动倍率算法偏离 plan 锁定公式;同时保留的旧持久化入口会把新字段覆盖为 null,无法保证普通持久化与重连保留状态。",
"missing": [
"按 CombatClock 的 200-tick 边界驱动且同一 tick 不重复计数",
"按整个 sweep 窗口选择 idle/move/dash 单一倍率,并保证 dash 优先",
"所有 cultivation bundle 写入口均保留 Nourishment,不能通过旧入口写 null",
"正式复活分支的独立状态转换与即时持久化测试"
]
},
"summary": "P0 接线覆盖面较广,但核心消耗算法被实现成按 Update 次数推进的逐 tick 加权平均,与 plan 的 CombatClock 周期门和窗口级活动倍率不一致。旧持久化 API 还会清空新切片,存在重连免费恢复风险,因此不能通过。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "193-214, 550-598",
"title": "周期消耗按 Update 调用次数推进,而不是按 CombatClock 的 200-tick 门控",
"evidence": "tick_nourishment 每次系统运行都直接 record 一次,并在累计 200 次后扣减;clock.tick 只用于移动 lease,缺少 plan 指定的 is_multiple_of(200) 或同 tick 去重。production_tick_waits_for_full_window_then_applies_loss_and_resets 测试在 CombatClock 始终为默认 tick 的情况下连续 app.update() 200 次仍触发扣减,直接锁定了错误契约。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "60-82, 165-181, 490-515",
"title": "活动倍率被改成逐 tick 加权平均,违反窗口级倍率与 dash 优先契约",
"evidence": "weighted_activity_ticks 将 idle/move/dash 分别按 1.0/1.5/3.0 加权后求总损耗,测试 mixed_window_uses_tick_weighted_average_not_peak_activity 还明确 pin 为“not peak activity”。但 plan 固定的是该 sweep 窗口存在水平移动则整体 ×1.5、存在 dash 则整体 ×3.0,且 PR Body 声明 dash 优先。例如 199 idle + 1 dash 当前仅得到约 ×1.01,而不是 ×3.0。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6167-6236",
"title": "保留的旧持久化入口会把 nourishment 切片覆盖成 null",
"evidence": "persist_player_cultivation_bundle 委托新函数时固定传入 None, None,而新函数始终把 nourishment 和 nourishment_activity_window 写入整份 JSON bundle;None 会序列化为 null。该表按整行覆盖,因此任何旧入口调用都会抹掉先前保存的双轴和活动窗口,加载时 null 又回退到 80/80 与空窗口,造成普通重连免费重置。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1566, 3363-3448, 4424-4550, 4973-5098",
"title": "缺少 plan 要求的普通正式复活状态转换测试",
"evidence": "revive_lifecycle 新增了 80/80 重置、活动窗口清零和即时落盘逻辑,但新增测试只明确覆盖 NearDeath 自救保留、Reincarnate、CreateNewCharacter 以及 join-time reincarnation;没有测试直接执行普通正式复活分支并验证 ECS、PlayerRevived 后状态及 SQLite 一致。PR Body 声称“正式复活”已做饱和测试,diff 无对应契约测试。"
}
]
}
][
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 99,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:复投后维持原结论。PR 已覆盖组件、五段边界、六境界倍率、主 schedule、主要生命周期和 dev 命令,但 P0 核心消耗契约仍发生结构性漂移:结算由 Update 执行次数而非 CombatClock 的 200-tick 节拍驱动,活动倍率也被改成逐 tick 加权平均及未决议的 20-tick lease,而不是整个 sweep 窗口采用单一最高活动倍率。此外,兼容持久化入口会覆盖新字段为 null,同 tick MovementEvent 未真正聚合,正式 Revive 的重置与即时持久化也缺少 plan 明确要求的专门测试。",
"missing": [
"以 CombatClock 为唯一节拍源、同一 clock tick 不重复累计的 200-tick sweep",
"整个 sweep 窗口采用 idle/move/dash 单一倍率并保证 dash 优先",
"同 tick 多个 MovementEvent 的真实水平位移聚合",
"所有 cultivation bundle 保存入口均无损保留 Nourishment 及活动状态",
"普通正式 Revive 重置 80/80、清空窗口并立即持久化的独立状态转换测试"
]
},
"summary": "复核其他意见后没有降低风险判断,反而确认了持久化断链和事件聚合问题。当前实现会因服务器 Update 调度频率改变消耗速度,混合活动窗口的数值结果与 plan 锁定公式不一致,并存在保存后重连免费恢复到 80/80 的路径,不能通过。 Plan 原意未确认:最终确认:复投后维持原结论。PR 已覆盖组件、五段边界、六境界倍率、主 schedule、主要生命周期和 dev 命令,但 P0 核心消耗契约仍发生结构性漂移:结算由 Update 执行次数而非 CombatClock 的 200-tick 节拍驱动,活动倍率也被改成逐 tick 加权平均及未决议的 20-tick lease,而不是整个 sweep 窗口采用单一最高活动倍率。此外,兼容持久化入口会覆盖新字段为 null,同 tick MovementEvent 未真正聚合,正式 Revive 的重置与即时持久化也缺少 plan 明确要求的专门测试。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "198-219, 540-586",
"title": "周期消耗按 Update 执行次数推进,而不是按 CombatClock 的 200-tick 节拍",
"evidence": "tick_nourishment 每次系统执行都无条件调用 activity_window.record;CombatClock 仅用于移动 lease,甚至允许资源缺失时回退 tick 0。production_tick_waits_for_full_window_then_applies_loss_and_resets 将 CombatClock 固定为默认值,连续执行 200 次 app.update() 仍期望扣减,直接证明同一 clock tick 可被重复累计,clock 跳跃时又会漏计。这不符合 plan §1、§4 指定的 CombatClock 同源及每 200 tick sweep。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "58-85, 104-119, 167-187, 490-518",
"title": "逐 tick 加权平均和 20-tick lease 偏离 plan 的窗口级活动倍率",
"evidence": "NourishmentActivityWindow 分别累计 idle/move/dash tick,weighted_activity_ticks 按 1.0、1.5、3.0 加权求和;mixed_window_uses_tick_weighted_average_not_peak_activity 还明确将该语义 pin 为“not peak activity”。plan 锁定的是该 sweep 窗口内出现水平位移则整次 ×1.5,出现 Dashing 则整次 ×3.0,即每次扣减使用单一 activity_multiplier 且 dash 优先。当前 199 tick idle 加 1 tick dash 仅约为 ×1.01,而 plan 要求 ×3.0;20-tick movement lease 也未进入 plan 决议。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6167-6236",
"title": "旧 cultivation bundle 保存入口会把已有食水状态覆盖为 null",
"evidence": "persist_player_cultivation_bundle 委托新函数时固定传入 None、None,而 persist_player_cultivation_bundle_with_nourishment 无条件把 nourishment 和 nourishment_activity_window 写入整份 JSON。Option::None 会序列化为 null,因此调用原有公开保存入口会覆盖数据库中的非默认食水状态;加载 null 时又会回退到 80/80 和空窗口。plan §4 明确要求在 persist_player_cultivation_bundle 中扩展 bundle,而不是保留一个具有破坏性的兼容入口。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "128-146",
"title": "同 tick MovementEvent 未真正聚合,分片水平移动会被漏判为空闲",
"evidence": "record_movement_events 对每个事件分别计算单次 hypot(delta_x, delta_z) > 0.05,未按 entity 和 clock tick 汇总。同 tick 的 0→0.03 与 0.03→0.06 两个连续事件累计水平位移为 0.06,但两个单事件均不超过阈值,tracker 不会刷新。现有测试只证明合格事件不会被后续零位移事件擦除,不能支持 PR Body 声称的“同 tick 多事件聚合”。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1569, 3363-3448, 4525-4548, 5061-5074",
"title": "缺少普通正式 Revive 重置与即时持久化的专门契约测试",
"evidence": "revive_lifecycle 中实现了 Nourishment 重置、活动窗口清零和持久化排队,但新增测试只明确覆盖 NearDeath 自救保留、Reincarnate、CreateNewCharacter 及 join-time reincarnation,没有从非默认食水值执行普通正式 Revive,并同时断言 PlayerRevived、ECS 80/80、窗口清零和 SQLite 更新。plan §4 与 §8.1 #3 明确把正式复活重置列为独立生命周期接线和饱和测试交付物。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:复核其他 reviewer 意见后维持拒绝结论。模块注册、dev 命令、join hydration、主要 autosave/disconnect/shutdown 及生命周期重置路径虽已接线,但 P0 核心运行契约仍发生结构性漂移:消耗由 Update 执行次数而非 CombatClock 的 200-tick 节拍驱动,活动倍率被改成逐 tick 加权平均而不是 sweep 窗口级最高活动倍率;此外旧 cultivation bundle 保存入口会覆盖新字段为 null,同 tick MovementEvent 未真正聚合,正式 Revive 分支也缺少 plan 明确要求的专门契约测试。因此不符合 plan 原意和交付标准。",
"missing": [
"以 CombatClock 为唯一节拍源、同一 clock tick 不重复累计的 200-tick 消耗门控",
"整个 sweep 窗口使用单一 idle/move/dash 倍率并保证 dash 优先",
"所有 cultivation bundle 保存入口均保留 Nourishment,不能把已有状态写成 null",
"按 entity 和 clock tick 真正聚合多个 MovementEvent 后判断累计水平位移",
"正式 Revive 成功后重置并立即持久化 80/80 的独立状态转换测试"
]
},
"summary": "复投后结论不变。当前 PR 的新增组件并非孤立 stub,主注册和多数生命周期路径也确有调用方,但核心消耗时钟与倍率语义均偏离 active plan,持久化兼容入口还存在可复现的数据抹除路径;MovementEvent 聚合声明与实现不符,正式复活契约缺少测试锁定。以上均为 major,不能通过。 Plan 原意未确认:最终确认:复核其他 reviewer 意见后维持拒绝结论。模块注册、dev 命令、join hydration、主要 autosave/disconnect/shutdown 及生命周期重置路径虽已接线,但 P0 核心运行契约仍发生结构性漂移:消耗由 Update 执行次数而非 CombatClock 的 200-tick 节拍驱动,活动倍率被改成逐 tick 加权平均而不是 sweep 窗口级最高活动倍率;此外旧 cultivation bundle 保存入口会覆盖新字段为 null,同 tick MovementEvent 未真正聚合,正式 Revive 分支也缺少 plan 明确要求的专门契约测试。因此不符合 plan 原意和交付标准。",
"findings": [
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6167-6236",
"title": "旧 cultivation bundle 保存入口会把已有食水状态覆盖为 null",
"evidence": "公开入口 `persist_player_cultivation_bundle` 委托新函数时固定传入 `None, None`,而 `persist_player_cultivation_bundle_with_nourishment` 又无条件把 `nourishment` 和 `nourishment_activity_window` 写入整份 JSON。由于该表按 username 整行覆盖,任何仍调用旧入口的保存都会把已有值确定性改成 JSON null;下次 join 解码失败或回退默认值后,玩家会恢复到 80/80 和空窗口。这也不符合 plan 指定应直接扩展 canonical `persist_player_cultivation_bundle` 的交付方式。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "198-220, 540-586",
"title": "周期消耗按 Update 调用次数推进,没有接到 CombatClock 的真实 200-tick 节拍",
"evidence": "`tick_nourishment` 将 CombatClock 声明为可选资源,缺失时退化到 tick 0;无论 `clock.tick` 是否变化,每次系统执行都会调用一次 `activity_window.record(activity)`,累计 200 次 Update 后即扣减。测试 `production_tick_waits_for_full_window_then_applies_loss_and_resets` 在 CombatClock 始终为默认 tick、完全未推进时连续调用 200 次 `app.update()` 仍期望扣减,直接锁定了错误契约。重复执行同一 clock tick 会多计,clock 跳 tick则会少计。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "58-85, 167-187, 503-518",
"title": "活动倍率被实现为逐 tick 加权平均,违反窗口级倍率与 dash 优先契约",
"evidence": "`NourishmentActivityWindow` 分别累计 idle/move/dash tick,`weighted_activity_ticks` 按 1.0/1.5/3.0 加权求和,`sweep_losses` 再按加权分钟扣减;测试名称 `mixed_window_uses_tick_weighted_average_not_peak_activity` 更明确 pin 住了该新语义。active plan 则规定一次 sweep 使用单个 `activity_multiplier`:窗口内发生水平位移则整体乘 1.5,发生 Dashing 则整体乘 3.0 且 dash 优先。例如 199 tick idle 加 1 tick dash,当前约为 1.01 倍,plan 要求为 3.0 倍。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "128-146, 369-405",
"title": "同 tick MovementEvent 没有真正聚合,分片位移会被误判为空闲",
"evidence": "`record_movement_events` 对每个事件独立计算 `hypot(position-old_position) > 0.05`,只有某个单事件超过阈值才刷新 tracker,并未按 entity 和 clock tick 汇总。若同 tick 收到 0→0.03、0.03→0.06 两个同向事件,累计水平位移为 0.06,但两个单事件都不合格。现有 `same_tick_zero_delta_event_cannot_erase_qualifying_movement` 只证明“大位移事件后面的零位移事件不会擦除结果”,不能证明 PR Body 声称的“同 tick 多事件聚合”。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1569, 3363-3448, 4424-4550, 4973-5098",
"title": "缺少 plan 明确要求的正式 Revive 重置与即时持久化测试",
"evidence": "`revive_lifecycle` 中确实加入了 Nourishment 重置、活动窗口清零和排队持久化,但新增测试只明确覆盖 NearDeath 自救保留、Reincarnate、CreateNewCharacter 及 join-time reincarnation,没有测试执行普通正式 Revive 分支并验证 ECS、`PlayerRevived` 后状态以及 SQLite 三者一致。plan §4 和 §8.1 #3 将正式复活列为独立生命周期接线,并在饱和测试清单中明确要求专门测试。"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:不符合 plan 原意。P0 的组件、六境界倍率、生命周期主接线和 dev 命令已落地,且未触碰真元流动或绕开 qi_physics;但核心消耗被实现为按 Update 次数推进的逐 tick 加权模型,而 plan 锁定的是 CombatClock 的 200-tick sweep 与窗口级单一活动倍率。此外,持久化兼容入口会覆盖既有食水状态为 null,同 tick MovementEvent 也未真正聚合。",
"missing": [
"按 CombatClock 实际推进、同一 tick 不重复计数的 200-tick sweep 契约",
"按整个 sweep 窗口选择单一 idle/move/dash 倍率并保证 dash 优先",
"按 entity 和 CombatClock tick 聚合同 tick 多个 MovementEvent 的水平位移",
"所有 cultivation bundle 保存入口均保留 Nourishment,禁止以 null 覆盖既有状态",
"正式 Revive 分支重置 80/80、清空窗口并立即持久化的专门状态转换测试"
]
},
"summary": "复投后结论不变。其他 reviewer 的证据进一步确认了首轮发现,并补强了正式复活测试缺口;当前实现会产生消耗速率错误、分片移动漏判以及重连免费恢复到 80/80 的可复现风险,不能通过。 Plan 原意未确认:最终确认:不符合 plan 原意。P0 的组件、六境界倍率、生命周期主接线和 dev 命令已落地,且未触碰真元流动或绕开 qi_physics;但核心消耗被实现为按 Update 次数推进的逐 tick 加权模型,而 plan 锁定的是 CombatClock 的 200-tick sweep 与窗口级单一活动倍率。此外,持久化兼容入口会覆盖既有食水状态为 null,同 tick MovementEvent 也未真正聚合。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "198-219, 540-586",
"title": "周期消耗按 Update 执行次数推进,而不是按 CombatClock 的真实 tick 推进",
"evidence": "tick_nourishment 每次系统执行都无条件调用 activity_window.record;CombatClock 仅用于 movement lease,甚至允许资源缺失时回退 tick 0。production_tick_waits_for_full_window_then_applies_loss_and_resets 在 CombatClock 始终不递增时连续执行 200 次 app.update() 仍期望扣减,证明同一 clock tick 可被重复累计,clock 跳变又不会补足经过的 tick。这不满足 plan 指定的 CombatClock 同源、每 200 tick sweep 契约。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "76-85, 174-187, 503-518",
"title": "活动倍率采用逐 tick 加权平均,违反 plan 锁定的窗口级倍率",
"evidence": "NourishmentActivityWindow 分别累计 idle/move/dash tick,weighted_activity_ticks 按 1.0、1.5、3.0 加权求和,mixed_window_uses_tick_weighted_average_not_peak_activity 还明确 pin 了该语义。plan 要求一次 sweep 使用单个 activity_multiplier:窗口内存在水平移动则整体乘 1.5,存在 dash 则整体乘 3.0 且 dash 优先。例如 199 tick idle 加 1 tick dash,当前约为 1.01 倍,而 plan 要求 3.0 倍。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "128-146, 380-415",
"title": "同 tick 多个 MovementEvent 未聚合,分片水平位移会被误判为空闲",
"evidence": "record_movement_events 对每个事件独立计算 hypot(position-old_position) > 0.05,只要没有单个事件超过阈值就不会刷新 tracker。同 tick 两个连续同向事件 0→0.03 与 0.03→0.06 的总位移为 0.06,但两次均不合格。same_tick_zero_delta_event_cannot_erase_qualifying_movement 只证明后续零位移不会擦除已有命中,不能证明 PR Body 声明的“同 tick 多事件聚合”。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6167-6236",
"title": "旧 cultivation bundle 保存入口会把既有食水状态覆盖为 null",
"evidence": "persist_player_cultivation_bundle 委托新函数时固定传入 None、None,而新函数无条件把 nourishment 和 nourishment_activity_window 写入整份 JSON bundle,因此该入口会确定性写出 null。加载端对 null 回退为 Nourishment::spawn_default() 和空窗口,导致原有非默认食水状态在后续重连时免费恢复到 80/80。player/mod.rs:80-104、393-425、536-566、686-728 还把生产保存查询中的两个组件声明为 Option,并将缺失值原样写入,保留了同类覆盖路径。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1569, 3363-3448, 4525-4548, 5061-5098",
"title": "缺少 plan 明确要求的正式 Revive 重置与即时持久化测试",
"evidence": "revive_lifecycle 中实现了重置 Nourishment、清空活动窗口并排队持久化,但新增测试只明确覆盖 NearDeath 自救保留、Reincarnate、CreateNewCharacter 和 join-time reincarnation。没有测试从非默认食水状态执行普通正式 Revive,断言 ECS 在 PlayerRevived 后为 80/80、窗口归零且 SQLite 同步更新。plan §4 与 §8.1 #3 明确将该分支列为独立接线和饱和测试交付物。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认不符合 plan 原意。P0 的组件、边界、六境界倍率、命令、主 schedule 和主要生命周期接线已经落地,也未发现 qi_physics 守恒或世界观冲突;但核心消耗被实现为按 Update 次数累计的逐 tick 加权模型,而非 CombatClock 每 200 tick 的窗口级倍率结算。持久化兼容入口还会清空新字段,MovementEvent 聚合声明与实现不符,正式复活契约也缺少 plan 明确要求的专门测试。",
"missing": [
"以 CombatClock 为唯一节拍源、同一 clock tick 不重复累计的 200-tick sweep",
"整个 sweep 选择单一 idle/move/dash 倍率并保证 dash 优先",
"所有 cultivation bundle 写入口均保留 Nourishment,不能写入 null 抹除既有状态",
"按 entity 和 clock tick 真正聚合多个 MovementEvent 后再判定水平位移",
"普通正式 Revive 的 ECS 重置、PlayerRevived 时序和 SQLite 即时持久化测试"
]
},
"summary": "维持 REQUEST_CHANGES。其他 reviewer 的意见与 diff 可相互验证,且补充确认了同 tick MovementEvent 并未真正聚合。当前问题会改变实际食水消耗速率,或让玩家经特定保存与重连路径免费恢复到 80/80,均属于 P0 核心契约的 major 缺陷;测试数量通过不能抵消测试正在 pin 错误语义及关键状态转换缺测。 Plan 原意未确认:最终确认不符合 plan 原意。P0 的组件、边界、六境界倍率、命令、主 schedule 和主要生命周期接线已经落地,也未发现 qi_physics 守恒或世界观冲突;但核心消耗被实现为按 Update 次数累计的逐 tick 加权模型,而非 CombatClock 每 200 tick 的窗口级倍率结算。持久化兼容入口还会清空新字段,MovementEvent 聚合声明与实现不符,正式复活契约也缺少 plan 明确要求的专门测试。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "190-220, 540-586",
"title": "周期消耗按 Update 调用次数推进,而不是按 CombatClock 的 200-tick 节拍",
"evidence": "tick_nourishment 每次执行都会无条件调用 activity_window.record(activity),clock.tick 仅参与 movement lease,甚至允许 CombatClock 资源缺失并回退到 tick 0。production_tick_waits_for_full_window_then_applies_loss_and_resets 在 CombatClock 始终不推进时连续执行 200 次 app.update() 仍期望扣减,明确锁定了错误契约。因此同一 clock tick 重复调度会多扣,clock 跳变或系统调度频率变化又会少计,与 plan §1、§4 指定的 CombatClock 同源 200-tick sweep 不符。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "58-85, 167-187, 490-518",
"title": "活动消耗被改成逐 tick 加权平均,违反窗口级倍率和 dash 优先契约",
"evidence": "NourishmentActivityWindow 分别累计 idle/move/dash tick,weighted_activity_ticks 再按 1.0、1.5、3.0 加权;mixed_window_uses_tick_weighted_average_not_peak_activity 还明确把该行为 pin 为“not peak activity”。plan 锁定的是一次 sweep 使用单一 activity_multiplier:窗口内存在移动则整体乘 1.5,存在 dash 则整体乘 3.0 且 dash 优先。当前 199 tick idle 加 1 tick dash 仅约为 1.01 倍,而不是 3.0 倍;额外引入的 20-tick lease 也因此直接改变数值,未经过 plan 决议。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6167-6236",
"title": "旧 cultivation bundle 保存入口会把已有食水状态覆盖为 null",
"evidence": "公开的 persist_player_cultivation_bundle 仍可作为完整 bundle 保存入口调用,但它委托新函数时固定传入 None, None;新函数又无条件把 nourishment 和 nourishment_activity_window 写入 JSON。Option::None 会序列化为 null,而该存储按 username 覆盖整份 cultivation_json。加载端对 null 回退到 80/80 和空窗口,因此任何遗留调用方都可能在保存其他 cultivation 数据时抹除玩家食水状态,造成重连免费恢复。player/mod.rs:80-104 的生产查询也把两个新组件保留为 Option,并在缺组件时原样传入,存在相同写 null 风险。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "125-146, 354-390",
"title": "同 tick 多个 MovementEvent 没有真正聚合,分片位移会被误判为空闲",
"evidence": "record_movement_events 对每个事件独立执行 has_qualifying_horizontal_movement,只有某个单事件的水平 delta 大于 0.05 才刷新 tracker。比如同一 tick 的 0→0.03 和 0.03→0.06,总水平位移已超过阈值,但两个事件都会被拒绝。same_tick_zero_delta_event_cannot_erase_qualifying_movement 只证明“大事件之后的零位移事件不会清除 tracker”,并不能证明 PR Body 声明的“同 tick 多事件聚合”。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1569, 3363-3448, 4424-4550, 4973-5098",
"title": "缺少 plan 明确要求的普通正式 Revive 状态转换与持久化测试",
"evidence": "revive_lifecycle 中确实新增了 Nourishment 重置、活动窗口清零、即时持久化排队以及随后发送 PlayerRevived 的实现,但新增测试只明确覆盖 NearDeath 自救保留、Reincarnate、CreateNewCharacter 和 join-time reincarnation。没有测试以非默认食水进入普通正式 Revive 分支,并同时验证 ECS 为 80/80、窗口清零、PlayerRevived 时序以及 SQLite bundle 已同步更新。plan §4 和 §8.1 #3 将“正式复活重置”列为独立生命周期交付物和专门测试,PR Body 的测试声明与实际 diff 不一致。"
}
]
}
] |
|
|
/review |
🔭 Review · PR #1259❌ 未通过:0/4,存在 37 项真实 finding,要求修改
📋 Plan 原意确认
🧑⚖️ 复投结果
🔎 Findings
首轮与复投原始结构化摘要[
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 97,
"plan_intent": {
"status": "misaligned",
"reason": "PR 已接入双轴、境界倍率、生命周期、命令和主 schedule,但周期消耗没有按 CombatClock 的 200 tick 契约驱动,活动倍率也从 plan 的窗口级 1.5/3.0 判定改成了未获决议的逐 tick 加权平均;同时旧持久化入口会把新增字段写成 null。这些差异会改变核心数值和重连语义,不能视为符合 P0 原意。",
"missing": [
"按 CombatClock 每 200 tick 结算,而不是按 Update 执行次数累计",
"落实 plan 约定的窗口级活动倍率,或先在 plan 中明确批准加权平均与 20-tick lease",
"保证所有 cultivation bundle 写入路径保留 Nourishment,而不是用 null 覆盖",
"正式 Revive 分支的 80/80 重置与即时持久化契约测试"
]
},
"summary": "P0 接线覆盖面较完整,但消耗节拍、活动倍率和持久化兼容入口均偏离 plan 的核心契约。其中旧持久化入口可导致重连免费恢复 80/80,必须修复后再合并。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "177-198, 519-566",
"title": "消耗周期按 Update 次数而非 CombatClock 的 200 tick 驱动",
"evidence": "tick_nourishment 每次系统执行都直接 activity_window.record,达到 200 次 Update 就扣减;CombatClock 只用于移动 lease。production_tick_waits_for_full_window_then_applies_loss_and_resets 测试保持 CombatClock 不变仍通过 200 次 app.update 触发消耗,明确锁定了与 plan 相反的行为。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "72-84, 161-174, 496-514",
"title": "活动倍率被擅自改成逐 tick 加权平均",
"evidence": "plan 规定“该 sweep 窗口内有水平位移 ×1.5,Dashing ×3.0”,并以单一 activity_multiplier 参与公式;实现却计算 idle×1.0 + move×1.5 + dash×3.0 的加权 ticks,测试还明确 pin 为“not peak activity”。例如 200 tick 中仅 1 tick dash 不会采用 ×3.0,而只得到约 ×1.01。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6167-6236",
"title": "旧持久化入口会用 null 覆盖已保存的饱食和活动窗口",
"evidence": "公开的 persist_player_cultivation_bundle 转调新函数时固定传入 None、None,而 json bundle 无条件写入 nourishment 和 nourishment_activity_window,因此调用该既有入口会把两项序列化为 null。join hydration 遇到 null 会回退默认 80/80 和空窗口,违反普通持久化恢复不得免费重置的决议。"
},
{
"severity": "minor",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1568, 3363-3454, 4525-4548, 5061-5079",
"title": "缺少 plan 明确要求的正式 Revive 重置契约测试",
"evidence": "新增测试覆盖了 NearDeath 自救不重置、Reincarnate 重置和 CreateNewCharacter 重置,但没有通过正式 Revive action 验证 80/80、活动窗口清零及即时落盘。该分支是 §8.1 #3 单独要求 pin 的状态转换。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "REQUEST_CHANGES",
"confidence": 95,
"plan_intent": {
"status": "misaligned",
"reason": "组件、主 schedule、命令和主要生命周期路径已接入,但周期消耗没有以 CombatClock 的 200 个真实 tick 为准,持久化主入口仍会把新增状态写成 null;此外活动倍率实现为逐 tick 加权,和 plan 锁定的整段 sweep 活动倍率不一致。",
"missing": [
"按 200 个不同 CombatClock tick 结算的生产节拍",
"不会清空 nourishment 的统一持久化入口",
"符合 plan 的整段 sweep 活动倍率语义,或先更新 plan 决议",
"正式 Revive 路径的 80/80 重置及立即持久化契约测试"
]
},
"summary": "P0 的主要类型和注册路径不是孤立 stub,但存在两条会改变生产语义的断链:消耗按 Update 调用次数推进,旧持久化入口会覆盖掉食水状态。活动倍率也被测试锁成了 plan 未声明的逐 tick 加权算法,因此当前不能通过。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "151-174, 525-557",
"title": "周期消耗按 Update 次数推进,而不是按 CombatClock 的真实 tick 推进",
"evidence": "tick_nourishment 每次运行都直接执行 activity_window.record;CombatClock 只用于移动 lease,且缺失时还回退到 tick 0。production_tick_waits_for_full_window_then_applies_loss_and_resets 测试在 CombatClock 完全不前进的情况下连续 app.update 200 次并期待扣减,明确锁定了同一 tick 可重复累计的错误语义。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6167-6236",
"title": "保留的主持久化入口会把 nourishment 两个字段覆盖为 null",
"evidence": "persist_player_cultivation_bundle 无条件转调新函数并传入 None、None,而 JSON bundle 又始终写入 nourishment 和 nourishment_activity_window,因此调用该既有公开入口会把已保存的非默认状态改成 null。join hydration 对 null 解码失败后使用 80/80 和空窗口,形成普通重连免费重置,违反 plan 的持久化契约。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "67-76, 130-145, 469-487",
"title": "活动倍率被实现为逐 tick 加权平均,与 plan 的整段 sweep 倍率不一致",
"evidence": "plan 锁定的是“该 sweep 窗口内有水平位移 ×1.5,Dashing ×3.0”,但 weighted_activity_ticks 将 idle/move/dash tick 分别加权后求总损耗,mixed_window_uses_tick_weighted_average_not_peak_activity 还明确 pin 了该算法。例如 180 tick idle 加 20 tick move 实际仅为 1.05 倍,而不是窗口内发生移动后的 1.5 倍。"
},
{
"severity": "minor",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1566, 3363-3448, 4424-4554",
"title": "正式 Revive 的新增重置和立即落盘路径没有专门契约测试",
"evidence": "实现向 revive_lifecycle 加入了 80/80 重置、活动窗口清零和延迟持久化,但本 diff 新增的生命周期测试只覆盖 NearDeath 稳定、自主 Reincarnate 和 CreateNewCharacter;没有正式 Revive 成功后同时断言 ECS 状态、PlayerRevived 路径及 SQLite bundle 的测试,与 plan 和 PR Body 声称的正式复活饱和测试不符。"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 95,
"plan_intent": {
"status": "misaligned",
"reason": "PR 基本覆盖了 P0 的组件、生命周期、持久化和 dev 命令范围,且未引入真元流动;但周期消耗的活动倍率与 CombatClock 节拍均未按 plan 锁定语义实现,并保留了会清空新持久化字段的旧写入口,因此不能确认符合 plan 原意。",
"missing": [
"按整个 200-tick sweep 窗口取 dash > movement > idle 的单一活动倍率",
"以 CombatClock 为唯一节拍并避免同一 tick 被重复计数",
"确保所有 cultivation bundle 写入口无损保存 nourishment 字段",
"将 satiety/hydration 固化为 Brigadier 命令树分支"
]
},
"summary": "存在三项会改变线上数值或丢失玩家状态的 major 问题:活动倍率被改成逐 tick 加权、消耗按 Update 次数而非 CombatClock 推进、旧持久化入口会写入 null。命令树也未锁定 plan 指定的两个 axis 分支。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "72-80, 160-175, 500-514",
"title": "活动倍率被实现为逐 tick 加权平均,违反锁定的 sweep 窗口倍率语义",
"evidence": "plan 规定“该 sweep 窗口内有水平位移 ×1.5,Dashing ×3.0”,即窗口内出现活动后取单一倍率;实现却用 idle/move/dash tick 数分别乘 1.0/1.5/3.0 后求加权分钟,测试还明确锁定了 mixed window 不取 peak。比如 199 tick idle 加 1 tick dash 只会得到约 1.01 倍,而不是 plan 要求的 3.0 倍。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "180-208, 544-581",
"title": "消耗按 Update 调用次数推进,而非按 CombatClock tick 推进",
"evidence": "tick_nourishment 在每次系统运行时无条件 record 一次,CombatClock 缺失时甚至回退到 tick 0;生产测试保持 CombatClock 不变,仅调用 app.update 200 次仍会完成一次消耗。这意味着同一 CombatClock tick 的重复 Update 会重复累计,时钟暂停或非一比一调度时也会偏离“每 200 tick = 10s”的契约。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6167-6236",
"title": "保留的旧持久化入口会把已有 nourishment 状态覆盖为 null",
"evidence": "persist_player_cultivation_bundle 无条件向新函数传入 None、None,而新函数仍把两个键写入整包 JSON,因此任何遗留调用都会写出 \"nourishment\": null 和 \"nourishment_activity_window\": null。加载端将这些值回退为 80/80 和空窗口,造成玩家状态免费重置。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "19-36",
"title": "命令树未把 satiety 和 hydration 固化为 Brigadier 分支",
"evidence": "实现注册的是通用 axis:string,registry 也固定为 nourish set <axis:string> <value:float>;因此任意字符串都能通过语法解析,只在执行期拒绝。这不符合 plan 中 /nourish set satiety|hydration <value> 的命令树契约,也无法提供两个合法值的 Brigadier 补全。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 94,
"plan_intent": {
"status": "misaligned",
"reason": "双轴、境界倍率、持久化和生命周期接线总体属于 PR-1 范围,但周期消耗没有按 plan 锁定的 CombatClock 200-tick 节拍执行,活动倍率也被改成逐 tick 加权平均;此外正式复活重置缺少 plan 明确要求的契约测试。",
"missing": [
"按 CombatClock 每 200 tick 门控消费,而不是按 Update 系统执行次数累计",
"落实 plan 的 sweep 窗口活动倍率契约,或先同步修改 plan 决议",
"正式复活成功重置并立即持久化、失败不重置的生产路径测试"
]
},
"summary": "存在三个需阻断的问题:消费节拍依赖 Update 次数而非 CombatClock、活动倍率实现偏离 plan、旧持久化入口会用 null 覆盖新字段。生命周期测试还未覆盖正式复活这一关键状态转换。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "193-221, 550-590",
"title": "BLOCKING: 周期消耗按系统执行次数推进,而非按 CombatClock 的 200-tick 节拍",
"evidence": "tick_nourishment 每次运行都直接 record 一次,窗口累计到 200 就扣减;CombatClock 仅用于移动 lease,缺失时甚至回退到 tick 0。对应测试固定 CombatClock 不前进,连续 app.update 200 次仍断言发生消耗,明确锁定了与 plan“CombatClock 节拍 + is_multiple_of(200) 门控”相反的行为。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "60-84, 178-190, 425-439",
"title": "BLOCKING: 活动倍率被实现为逐 tick 加权平均,偏离 plan 的 sweep 窗口倍率",
"evidence": "weighted_activity_ticks 将 idle/move/dash tick 分别乘 1.0/1.5/3.0 后求和,测试还明确要求 mixed window 使用 weighted average 而不是 peak activity。plan 则规定“该 sweep 窗口内有水平位移 ×1.5,Dashing ×3.0”,每轴公式为 base_loss × activity_multiplier × realm_multiplier;例如窗口内仅一次移动时,当前实现只提高约 20 个 lease tick,而不是对该 sweep 使用移动倍率。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6167-6210, 6234-6235",
"title": "BLOCKING: 旧持久化入口会把已有 nourishment 数据覆盖为 null",
"evidence": "保留的 persist_player_cultivation_bundle 始终向新入口传入 None, None,而新入口无条件把 nourishment 和 nourishment_activity_window 写入整行 JSON。因此任何仍通过旧公开入口保存 cultivation bundle 的路径都会覆盖玩家已有食水值,下一次登录再回退为默认值。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1568; tests 3363-3448, 4525-4554, 5061-5079",
"title": "BLOCKING: 正式复活重置接线没有对应状态转换测试",
"evidence": "revive_lifecycle 新增了 80/80 重置、活动窗口清零和立即持久化,但新增测试只明确覆盖 NearDeath 自救保留、Reincarnate 重置和 CreateNewCharacter 重置,没有测试驱动正式复活分支并核验 ECS 与 SQLite。该测试是 plan §4 明确列出的饱和测试交付物。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "20-31",
"title": "命令树未将 satiety 和 hydration 固定为字面量分支",
"evidence": "命令树注册的是通用 <axis:string>,所以 food 等无效轴会先通过 Brigadier 解析,再到 handler 中拒绝;registry pin 也固定成了 nourish set <axis:string> <value:float>,未锁定 plan 声明的 satiety|hydration 命令树契约。"
}
]
}
][
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:维持首轮结论。PR 虽已接入双轴组件、六境界倍率、主 schedule、生命周期和主要持久化路径,但核心周期消耗仍由 Update 执行次数而非 CombatClock 的 200 tick 契约驱动,活动倍率也被改成 plan 未批准的逐 tick 加权平均;同时保留的旧持久化入口会把新增状态写成 null。这些问题会直接改变线上数值及普通重连语义,不能认定符合 P0 原意。",
"missing": [
"按不同 CombatClock tick 采样并在每 200 tick 边界结算,禁止同一 tick 因重复 Update 被多次累计",
"按整个 sweep 窗口采用 dash 优先、movement 次之、idle 最后的单一活动倍率,或先正式修改 plan 决议",
"保证所有 cultivation bundle 写入入口无损保留 Nourishment 与活动窗口",
"正式 Revive 成功后的 80/80 重置、窗口清零、事件和即时落盘契约测试",
"将 satiety 与 hydration 固化为 Brigadier 字面量分支"
]
},
"summary": "复投后仍应阻止合并。四位 reviewer 的意见没有提供足以推翻首轮判断的新证据;重新核对 diff 后,消费节拍、活动倍率和旧持久化入口三项 major 均可直接由代码与测试验证。正式复活和命令树还缺少 plan 明确要求的契约锁定。 Plan 原意未确认:最终确认:维持首轮结论。PR 虽已接入双轴组件、六境界倍率、主 schedule、生命周期和主要持久化路径,但核心周期消耗仍由 Update 执行次数而非 CombatClock 的 200 tick 契约驱动,活动倍率也被改成 plan 未批准的逐 tick 加权平均;同时保留的旧持久化入口会把新增状态写成 null。这些问题会直接改变线上数值及普通重连语义,不能认定符合 P0 原意。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "151-174, 519-566",
"title": "BLOCKING: 消耗窗口按 Update 执行次数推进,而非 CombatClock 的 200 tick 节拍",
"evidence": "tick_nourishment 每次系统执行都会无条件调用 activity_window.record;CombatClock 只用于移动 lease,缺失时还回退到 tick 0,没有检查 tick 是否变化或 is_multiple_of(200)。production_tick_waits_for_full_window_then_applies_loss_and_resets 保持 CombatClock 不变,连续执行 200 次 app.update 仍期待发生扣减,明确锁定了同一真实 tick 可被重复累计的行为。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "72-84, 161-174, 496-514",
"title": "BLOCKING: 活动倍率被改成逐 tick 加权平均,违反 sweep 窗口级倍率契约",
"evidence": "weighted_activity_ticks 将 idle、move、dash tick 分别乘 1.0、1.5、3.0 后求和,mixed_window_uses_tick_weighted_average_not_peak_activity 又明确 pin 了该算法。plan 锁定的是该 sweep 窗口内发生 Dashing 时整段使用 3.0,否则发生水平移动时整段使用 1.5,否则使用 1.0;例如 199 tick idle 加 1 tick dash,当前实现约为 1.01 倍,而不是 3.0 倍。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6167-6236",
"title": "BLOCKING: 旧持久化入口会把已保存的食水状态覆盖为 null",
"evidence": "persist_player_cultivation_bundle 保留为公开入口,但转调新函数时固定传入 None、None;新函数又无条件向整包 JSON 写入 nourishment 和 nourishment_activity_window,因此任何遗留调用都会写出两个 null。加载端对 null 解码失败后保留默认 80/80 和空窗口,造成普通持久化恢复或重连免费重置。"
},
{
"severity": "minor",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1568, 3363-3454, 4525-4548, 5061-5079",
"title": "正式 Revive 分支缺少 plan 明确要求的状态转换契约测试",
"evidence": "revive_lifecycle 已加入 80/80 重置、活动窗口清零和即时持久化,但新增测试只覆盖 NearDeath 自救保留、Reincarnate 重置及 CreateNewCharacter 重置,没有通过正式 Revive action 驱动生产分支并同时核验 ECS、PlayerRevived 和 SQLite bundle。plan §4 明确把正式复活重置列为 P0 饱和测试交付物。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "19-36",
"title": "命令树没有把 satiety 和 hydration 固化为字面量分支",
"evidence": "当前注册的是通用 axis:string,任意字符串都会先通过 Brigadier 语法解析,再由 handler 在执行期拒绝。对应 registry 也锁成 nourish set <axis:string> <value:float>,没有实现 plan 声明的 /nourish set satiety|hydration <value> 命令树契约及合法值补全。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:主要组件、主 schedule、移动事件、生命周期和常用持久化路径已经接线,并非孤立 stub;但生产消耗仍按 Update 执行次数而非 CombatClock 的 200 个真实 tick 推进,活动倍率被改成 plan 未批准的逐 tick 加权平均,保留的旧持久化入口还会把已有食水状态写成 null。这些问题会直接改变线上数值与重连状态,当前不符合 P0 原意。",
"missing": [
"以 CombatClock 为唯一节拍、同一 clock tick 不得重复累计的 200-tick sweep",
"按整个 sweep 取 dash > movement > idle 的单一活动倍率,或先正式修改 plan 决议",
"所有 cultivation bundle 写入路径无损保留 Nourishment 与活动窗口",
"正式 Revive 成功及失败分支的 ECS、事件和即时持久化契约测试",
"将 satiety 和 hydration 固化为 Brigadier 命令树分支"
]
},
"summary": "维持 REQUEST_CHANGES。四方意见中的三个核心 major 均可由 diff 直接复核:时钟节拍断链、活动倍率偏离 plan、旧持久化 API 破坏新增状态。正式复活和命令树测试也未完整锁定 PR Body 声称的契约。 Plan 原意未确认:最终确认:主要组件、主 schedule、移动事件、生命周期和常用持久化路径已经接线,并非孤立 stub;但生产消耗仍按 Update 执行次数而非 CombatClock 的 200 个真实 tick 推进,活动倍率被改成 plan 未批准的逐 tick 加权平均,保留的旧持久化入口还会把已有食水状态写成 null。这些问题会直接改变线上数值与重连状态,当前不符合 P0 原意。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "177-198, 519-566",
"title": "BLOCKING: 消耗窗口按 Update 次数而非 CombatClock tick 推进",
"evidence": "tick_nourishment 每次系统执行都无条件调用 activity_window.record,CombatClock 仅用于判断移动 lease,缺失时还回退为 tick 0。production_tick_waits_for_full_window_then_applies_loss_and_resets 保持 CombatClock 不变,仅连续 app.update 200 次就期待扣减,明确允许同一个真实 tick 被重复计数,违反 plan 的“CombatClock 每 200 tick = 10s”契约。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "67-84, 161-174, 496-514",
"title": "BLOCKING: 活动倍率被改成逐 tick 加权,偏离 sweep 窗口级倍率",
"evidence": "weighted_activity_ticks 将 idle、move、dash tick 分别乘 1.0、1.5、3.0 后求和,mixed_window_uses_tick_weighted_average_not_peak_activity 还明确 pin 了该算法。plan 锁定的是该 sweep 窗口内出现水平位移即使用 1.5,出现 Dashing 则使用 3.0 的单一 activity_multiplier;例如 199 tick idle 加 1 tick dash,当前实现约为 1.01 倍而非 3.0 倍。20-tick lease 还可能把上一 sweep 末尾的移动证据带入下一 sweep。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6167-6236",
"title": "BLOCKING: 旧 cultivation bundle 写入口会把食水状态覆盖为 null",
"evidence": "persist_player_cultivation_bundle 转调新函数时固定传入 None、None,而新函数无条件序列化 nourishment 和 nourishment_activity_window 两个键,因此调用旧公开入口会写出 null。加载端对 null 解码失败后使用 80/80 和空窗口,非默认食水状态会在后续重连中免费重置,违反普通持久化恢复不得重置的决议。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1568;tests 3363-3454, 4525-4548, 5061-5079",
"title": "BLOCKING: 正式 Revive 的重置与即时落盘没有状态转换契约测试",
"evidence": "revive_lifecycle 新增了 80/80 重置、活动窗口清零和延迟执行的 cultivation bundle 持久化,但新增测试只明确覆盖 NearDeath 自救保留、Reincarnate 重置和 CreateNewCharacter 重置。没有通过正式 Revive action 验证成功时 PlayerRevived、ECS 与 SQLite 同步重置,也没有验证失败的 revive 不得重置。该项是 plan §4 明列的饱和测试交付物。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "19-36",
"title": "命令树未将 satiety 和 hydration 固化为合法分支",
"evidence": "实现注册的是通用 axis:string,任何字符串都能通过 Brigadier 语法解析,直到 handler 才被 NourishmentAxis::parse 拒绝;server/src/cmd/registry_pin.rs:93-96 也将契约 pin 为 nourish set <axis:string> <value:float>,没有体现 plan 声明的 satiety|hydration 两个合法分支及补全能力。"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:PR 虽已接入双轴组件、六境界倍率、生命周期、主 schedule 和主要持久化路径,且未引入绕过 qi_physics 的真元流动,但核心周期消耗并未按 plan 锁定的 CombatClock 200-tick sweep 语义实现,活动倍率也被改为未获决议的逐 tick 加权平均;同时保留的旧持久化入口会将新增状态写成 null。以上问题会改变线上数值和重连状态,不能视为符合 plan 原意。",
"missing": [
"以 CombatClock 为唯一消费节拍,并保证同一 tick 重复 Update 不会重复计数",
"按整个 sweep 窗口采用 dash 优先于 movement、movement 优先于 idle 的单一活动倍率",
"统一所有 cultivation bundle 写入口,无损保留 Nourishment 与活动窗口",
"正式 Revive 成功重置、立即持久化以及失败不重置的生产路径契约测试",
"将 satiety 和 hydration 固化为 Brigadier 字面量分支"
]
},
"summary": "维持 REQUEST_CHANGES。其他 reviewer 的意见没有削弱首轮结论,反而确认了三个独立的线上正确性问题:消费节拍依赖系统执行次数、活动倍率偏离 plan、兼容持久化入口会丢失玩家状态。此外,plan 明确要求的正式复活状态转换仍未被测试锁定。修复这些问题前不应合并。 Plan 原意未确认:最终确认:PR 虽已接入双轴组件、六境界倍率、生命周期、主 schedule 和主要持久化路径,且未引入绕过 qi_physics 的真元流动,但核心周期消耗并未按 plan 锁定的 CombatClock 200-tick sweep 语义实现,活动倍率也被改为未获决议的逐 tick 加权平均;同时保留的旧持久化入口会将新增状态写成 null。以上问题会改变线上数值和重连状态,不能视为符合 plan 原意。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "177-198, 519-566",
"title": "BLOCKING: 消耗窗口按 Update 执行次数推进,而不是按 CombatClock tick 推进",
"evidence": "tick_nourishment 每次系统执行都会无条件调用 activity_window.record;CombatClock 仅用于移动 lease,缺失时还回退到 tick 0。production_tick_waits_for_full_window_then_applies_loss_and_resets 在 CombatClock 完全不前进的情况下连续 app.update 200 次仍触发扣减,明确锁定了同一真实 tick 可被重复累计的行为。这会在时钟暂停、重复调度或 Update 与 CombatClock 非一比一时破坏“每 200 tick = 10 秒”的数值契约。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "67-84, 161-174, 469-487",
"title": "BLOCKING: 活动倍率被实现为逐 tick 加权平均,违反窗口级倍率契约",
"evidence": "weighted_activity_ticks 将 idle、move、dash tick 数分别乘以 1.0、1.5、3.0 后求和,mixed_window_uses_tick_weighted_average_not_peak_activity 还明确将该算法固定为测试契约。plan 规定的是“该 sweep 窗口内有水平位移 ×1.5,Dashing ×3.0”,因此窗口内存在 dash 时应使用 3.0,否则存在移动时使用 1.5,否则使用 1.0。例如 199 tick idle 加 1 tick dash,当前实现仅约为 1.01 倍,而不是 3.0 倍。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6167-6236",
"title": "BLOCKING: 旧持久化入口会把已有 nourishment 状态覆盖为 null",
"evidence": "persist_player_cultivation_bundle 转调新入口时固定传入 None、None,而新入口无条件将 nourishment 和 nourishment_activity_window 写入整包 JSON,因此该保留入口会产出 \"nourishment\": null 和 \"nourishment_activity_window\": null。加载端对 null 回退为 80/80 和空窗口,任何遗留或未来调用都会造成普通持久化恢复免费重置,直接违反 §8.1 #3。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1568; tests 3363-3454, 4525-4548, 5061-5079",
"title": "BLOCKING: 正式 Revive 的重置和即时持久化缺少计划要求的状态转换测试",
"evidence": "revive_lifecycle 新增了 80/80 重置、活动窗口清零和 cultivation bundle 持久化,但新增测试仅明确覆盖 NearDeath 自救保留、Reincarnate 重置及 CreateNewCharacter 重置,没有通过正式 Revive action 的生产入口验证成功后的 ECS 状态、PlayerRevived 事件和 SQLite 内容。该测试是 plan §4 明列的 P0 饱和测试交付物,也是 §8.1 #3 单独锁定的生命周期契约。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "19-36",
"title": "命令树未将 satiety 和 hydration 固化为 Brigadier 字面量分支",
"evidence": "当前命令图注册为通用 axis:string,任意字符串都会先通过语法解析,再由 handler 运行期拒绝;server/src/cmd/registry_pin.rs:93-95 也固定为 nourish set <axis:string> <value:float>。这与 plan 声明的 /nourish set satiety|hydration <value> 命令树不一致,且失去合法轴的 Brigadier 补全与语法级约束。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:当前实现未真正符合 PR-1 的 plan 原意。核心周期不是由 CombatClock 的 200 个真实 tick 驱动,活动损耗也从 plan 锁定的窗口级单一倍率改成了逐 tick 加权平均;此外保留的旧 cultivation bundle 写入口会把新增状态覆盖为 null。上述问题会直接改变线上消耗数值和重连状态,不能仅凭现有测试通过而接受。",
"missing": [
"按不同 CombatClock tick 累计并在 200-tick 边界结算,禁止同一时钟 tick 因重复 Update 被多次计数",
"按 sweep 窗口采用 dash 优先、其次 movement、否则 idle 的单一活动倍率,或先正式修改 plan 决议",
"统一所有 cultivation bundle 写入路径,保证旧入口不会把 nourishment 字段写成 null",
"正式 Revive 成功和失败分支的 ECS、事件及即时 SQLite 持久化契约测试",
"将 satiety 和 hydration 固化为 Brigadier 字面量分支"
]
},
"summary": "维持 REQUEST_CHANGES。三个 major 会改变生产语义或丢失玩家状态:Update 次数替代 CombatClock 节拍、逐 tick 加权替代窗口倍率、兼容持久化入口写入 null。正式复活这一关键状态转换也缺少 plan 明确要求的生产路径测试。 Plan 原意未确认:最终确认:当前实现未真正符合 PR-1 的 plan 原意。核心周期不是由 CombatClock 的 200 个真实 tick 驱动,活动损耗也从 plan 锁定的窗口级单一倍率改成了逐 tick 加权平均;此外保留的旧 cultivation bundle 写入口会把新增状态覆盖为 null。上述问题会直接改变线上消耗数值和重连状态,不能仅凭现有测试通过而接受。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "177-198, 519-566",
"title": "BLOCKING: 消耗窗口按 Update 执行次数推进,而不是按 CombatClock 的真实 tick 推进",
"evidence": "tick_nourishment 每次系统执行都无条件调用 activity_window.record,CombatClock 只参与移动 lease,缺失时还回退为 tick 0。production_tick_waits_for_full_window_then_applies_loss_and_resets 保持 CombatClock 不变,仅连续调用 app.update 200 次便期待发生扣减,明确锁定了同一时钟 tick 可重复累计的错误契约。时钟暂停、重复调度或 Update 与 CombatClock 非一比一时,实际消耗速率都会偏离 plan 的“每 200 tick = 10 秒”。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "60-84, 151-174, 469-514",
"title": "BLOCKING: 活动损耗被实现并测试锁定为逐 tick 加权平均",
"evidence": "weighted_activity_ticks 将 idle、move、dash tick 数分别乘 1.0、1.5、3.0 后求和;mixed_window_uses_tick_weighted_average_not_peak_activity 明确要求 mixed window 不使用窗口峰值。plan 则规定“该 sweep 窗口内有水平位移 ×1.5,Dashing ×3.0”,并以一个 activity_multiplier 参与每轴公式。当前实现中 199 tick idle 加 1 tick dash 约为 1.01 倍,而锁定语义应因窗口内发生 dash 使用 3.0 倍。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6167-6236",
"title": "BLOCKING: 旧持久化入口会将已有 nourishment 状态覆盖为 null",
"evidence": "persist_player_cultivation_bundle 转调新入口时固定传入 None、None,而 persist_player_cultivation_bundle_with_nourishment 无条件把 nourishment 和 nourishment_activity_window 写进整包 JSON。因此任何遗留调用方使用旧公开入口保存时,都会产生 \"nourishment\": null 和 \"nourishment_activity_window\": null。加载端对 null 回退到 80/80 和空窗口,导致普通重连免费恢复并丢失未结算活动状态。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1568; tests 3363-3454, 4424-4554, 4973-5098",
"title": "BLOCKING: 正式 Revive 的重置和即时持久化没有状态转换契约测试",
"evidence": "revive_lifecycle 新增了双轴重置、活动窗口清零及延迟命令持久化,但新增测试覆盖的是 NearDeath 自救、Reincarnate 和 CreateNewCharacter,没有通过正式 Revive action 驱动该分支并同时核验 Nourishment、NourishmentActivityWindow、PlayerRevived 和 SQLite bundle。queue_nourishment_cultivation_bundle_persist 还会在任一 cultivation slice 缺失时仅告警并跳过即时持久化,现有测试无法证明正式复活生产实体一定满足该前置条件。plan §4 明确把正式复活重置列为饱和测试交付物。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "19-36",
"title": "命令树没有将 satiety 和 hydration 固化为字面量分支",
"evidence": "当前命令注册为 nourish set <axis:string> <value:float>,任意字符串都会先通过 Brigadier 语法解析,再由 handler 运行期拒绝。server/src/cmd/registry_pin.rs:93-96 也把通用 string 形状固定为契约,与 plan 声明的 /nourish set satiety|hydration <value> 不一致,并失去合法轴的命令补全与解析期约束。"
}
]
}
] |
|
/review |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
server/src/player/mod.rs (1)
396-425: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win三处 18 参数的持久化调用完全重复,建议抽一个 helper。
断线 flush、关机 flush、60s autosave 三处的解构 +
severed_owned+persist_player_cultivation_bundle_with_nourishment(...)调用逐字相同;本次每处又同步新增两个参数,后续任何新 slice 都要改三处,很容易漏改其中一处(这正是本 PR 需要为 legacy writer 补回退逻辑的成因)。建议提取如fn persist_cultivation_snapshot(settings, username, snapshot: CultivationSnapshotQueryItem<'_>) -> io::Result<()>,三处只保留错误日志差异。Also applies to: 539-568, 689-730
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/player/mod.rs` around lines 396 - 425, Extract the duplicated cultivation persistence flow used by the disconnect flush, shutdown flush, and 60-second autosave paths into a shared helper, such as one accepting the existing settings, username, and cultivation snapshot query data. Move the tuple destructuring, severed fallback, and persist_player_cultivation_bundle_with_nourishment call into that helper, while preserving each caller’s distinct error logging behavior and passing all current parameters through one centralized path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/plan-satiety-hydration-v1.md`:
- Around line 63-65: 在文档 §1/§3 的活动判定说明中补充 20-tick session lease、同 tick
事件聚合与结算时序,并明确 Dash 优先覆盖普通水平位移倍率。统一 sweep 窗口位移描述与逐 tick Position/OldPosition
判定,确保文字契约与 movement/mod.rs 的实现一致。
- Around line 91-95: 将 P0 服务端基础设施范围与前文 P1 过饱呕吐定义统一:从 Line 91 的 P0 文件列表移除
server/src/nourishment/vomit.rs,并确保 §3、PR-1 目标及 /consume-plan 实施范围继续将呕吐功能归入
P1,避免重复或冲突的范围定义。
- Around line 119-121: 统一 water_skin_filled 的类别契约:将 inventory/mod.rs 中
test_template(...) fixture 的 ItemCategory::Liquid 改为 ItemCategory::Misc,并确保生产
TOML 注册同样使用 category = "misc";不要扩展或继续使用不受支持的 Liquid 类别,保持相关 pin tests 与物品注册一致。
- Around line 82-87: 在呕吐 A/V
规格中补充环境表现及其生命周期与清理规则,明确呕吐触发时产生的环境效果、持续范围/时间、叠加行为,以及结束或中断时如何移除;若该机制不改变方块、天气、光照或其他环境状态,则明确声明“无环境状态变化”。保持现有粒子、音效、动画、HUD
与 narration 规格不变。
In `@server/src/cmd/dev/nourish.rs`:
- Around line 20-30: 将 nourish set 命令图中 `axis` 的 `String` parser 替换为
`NourishmentAxis` parser,并让 `NourishCmd::Set` 直接接收该枚举值;同步移除 `handle_nourish`
中针对非法轴的运行时处理分支。更新 `registry_pin.rs` 中对应的 nourish set pin,将 `<axis:string>`
改为枚举约束的声明。
- Around line 39-57: 为 dev::handle_nourish 增加统一的 dev 权限白名单或本地测试开关门控,并在未满足条件时拒绝执行
`/nourish`;在 dev::register 中接入现有命令权限机制(如
SeasonCommandPermissions)或仓库已有的游戏模式运行时限制,确保普通在线玩家无法修改
Nourishment,现有组件检查和成功处理流程保持不变。
In `@server/src/combat/lifecycle.rs`:
- Around line 1608-1613: 在 revive_lifecycle 中,将“cultivation bundle is
incomplete”分支的 tracing::warn! 调整为 tracing::debug!,保留现有日志内容、实体上下文及 return
行为不变;不要修改下方真正持久化失败的告警。
In `@server/src/nourishment/tick.rs`:
- Around line 153-179: Remove the #[cfg(test)] restriction from
has_qualifying_horizontal_movement and reuse it in record_movement_events for
the aggregated delta_x and delta_z values, replacing the duplicated hypot
threshold check. Preserve the existing strict-greater-than epsilon behavior and
keep the event-level wrapper only if needed by the tests.
In `@server/src/persistence/mod.rs`:
- Around line 6193-6195: 移除 persist_player_cultivation_bundle_with_nourishment
上的 #[cfg_attr(not(test), allow(dead_code))] 属性,仅保留必要的 clippy::too_many_arguments
配置;不要修改该函数或其现有调用路径。
- Around line 6215-6241: 将包含 existing_bundle 查询、字段合并和最终 upsert 的读改写流程统一包裹在
rusqlite 的 TransactionBehavior::Immediate 事务中,参照 complete_tribulation_ascension
与 release_ascension_quota_slot 的事务模式。把末尾针对 connection 的 execute 改为
transaction.execute,并在返回前提交事务,将事务错误映射为 io::Error::other。
---
Outside diff comments:
In `@server/src/player/mod.rs`:
- Around line 396-425: Extract the duplicated cultivation persistence flow used
by the disconnect flush, shutdown flush, and 60-second autosave paths into a
shared helper, such as one accepting the existing settings, username, and
cultivation snapshot query data. Move the tuple destructuring, severed fallback,
and persist_player_cultivation_bundle_with_nourishment call into that helper,
while preserving each caller’s distinct error logging behavior and passing all
current parameters through one centralized path.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: abb4acd1-78df-4ff6-874a-b03594ead928
📒 Files selected for processing (13)
docs/plan-satiety-hydration-v1.mdserver/src/cmd/dev/mod.rsserver/src/cmd/dev/nourish.rsserver/src/cmd/registry_pin.rsserver/src/combat/lifecycle.rsserver/src/cultivation/mod.rsserver/src/lib.rsserver/src/main.rsserver/src/movement/mod.rsserver/src/nourishment/mod.rsserver/src/nourishment/tick.rsserver/src/persistence/mod.rsserver/src/player/mod.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: e2e
🧰 Additional context used
📓 Path-based instructions (9)
server/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
server/**/*.rs: Server Rust 代码提交前必须运行cargo fmt --check、cargo clippy --all-targets -- -D warnings和cargo test。
Dev harness 命令只能用于本地/dev 测试,显式绕过 worldview 修炼规则和 qi ledger 的入口不得复用于生产 gameplay 路径。
Files:
server/src/lib.rsserver/src/cmd/dev/mod.rsserver/src/main.rsserver/src/cmd/registry_pin.rsserver/src/movement/mod.rsserver/src/cmd/dev/nourish.rsserver/src/persistence/mod.rsserver/src/nourishment/mod.rsserver/src/nourishment/tick.rsserver/src/player/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
server/src/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
server/src/**/*.rs: 所有真元/灵气流动必须通过qi_physics::ledger::QiTransfer { from, to, amount, reason },不得直接增减账户或区域数值。
测试灵气总量时必须引用SPIRIT_QI_TOTAL,不得硬编码100。
离屏战斗死亡必须调用release_dormant_qi_to_zone并通过ledger.transfer(ReleaseToZone)归还真元。
新增衰减、抽取或衰减率常数前必须检查并复用qi_physics;不存在时先扩展其 constants,禁止在业务 plan 中重复定义。
天道时代衰减必须使用qi_physics::tiandao::era_decay_step,并通过WorldQiBudget::apply_era_decay和assert_conservation追踪沉降槽。
不得使用 armor stand 或 invisible mob 作为碰撞箱/交互载体;实体必须使用 Marker+自定义渲染,交互通过 C2S 请求处理。
headless/CI 启服必须设置BONG_SKIP_SKIN_PREFETCH=1,避免缺少MINESKIN_API_KEY导致 panic。
Files:
server/src/lib.rsserver/src/cmd/dev/mod.rsserver/src/main.rsserver/src/cmd/registry_pin.rsserver/src/movement/mod.rsserver/src/cmd/dev/nourish.rsserver/src/persistence/mod.rsserver/src/nourishment/mod.rsserver/src/nourishment/tick.rsserver/src/player/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
server/src/**/*.{rs,json}
📄 CodeRabbit inference engine (CLAUDE.md)
server/src/**/*.{rs,json}: 新增 zone 前必须核对docs/worldview.md区域表和server/zones.json中已有 ID。
ItemCategory只能使用 Pill、Herb、Scroll、Misc、Weapon、Armor、Tool、Treasure、RecipeFragment、RecipeHint、BoneCoin、Container;炼丹材料使用Misc,不得使用Material。
Files:
server/src/lib.rsserver/src/cmd/dev/mod.rsserver/src/main.rsserver/src/cmd/registry_pin.rsserver/src/movement/mod.rsserver/src/cmd/dev/nourish.rsserver/src/persistence/mod.rsserver/src/nourishment/mod.rsserver/src/nourishment/tick.rsserver/src/player/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
**/*.{rs,ts,tsx,java,py}
📄 CodeRabbit inference engine (CLAUDE.md)
新增 skill/cast/主动能力必须同时提供独立 animation、particle/VFX、SFX、HUD 反馈和 hotbar/SkillBar PNG icon;仅实现 server 或 schema 不算完成。
Files:
server/src/lib.rsserver/src/cmd/dev/mod.rsserver/src/main.rsserver/src/cmd/registry_pin.rsserver/src/movement/mod.rsserver/src/cmd/dev/nourish.rsserver/src/persistence/mod.rsserver/src/nourishment/mod.rsserver/src/nourishment/tick.rsserver/src/player/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
**/*.{rs,ts,tsx,java}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{rs,ts,tsx,java}: 六境界必须使用“醒灵→引气→凝脉→固元→通灵→化虚”,不得使用练气、筑基、金丹、元婴等旧称。
命名不得使用末法禁词玄、陨、星、仙、太、古,除明确允许的俗世矿名例外。
Files:
server/src/lib.rsserver/src/cmd/dev/mod.rsserver/src/main.rsserver/src/cmd/registry_pin.rsserver/src/movement/mod.rsserver/src/cmd/dev/nourish.rsserver/src/persistence/mod.rsserver/src/nourishment/mod.rsserver/src/nourishment/tick.rsserver/src/player/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
**/*.{rs,ts,tsx,java,json,md}
📄 CodeRabbit inference engine (CLAUDE.md)
唯一真货币是骨币;矿物是交易筹码,灵石是燃料/衰变物,金银不是货币。
Files:
server/src/lib.rsserver/src/cmd/dev/mod.rsserver/src/main.rsserver/src/cmd/registry_pin.rsserver/src/movement/mod.rsserver/src/cmd/dev/nourish.rsserver/src/persistence/mod.rsserver/src/nourishment/mod.rsserver/src/nourishment/tick.rsserver/src/player/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rsdocs/plan-satiety-hydration-v1.md
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
**/*: 禁止git stash push后不执行对应git stash pop;不得留下孤儿 WIP stash。
每个逻辑单元使用中文 atomic commit;agent 产生的每个 commit 必须包含真实模型 ID 的Model:trailer。
未经明确确认不得执行 force push、hard reset、amend、交互式 rebase、批量删除/移动文件或依赖版本/生产配置修改;严禁--no-verify、--no-gpg-sign及关闭签名。
PR review 只能通过独立评论/review触发;不得等待 Codex,review 修改后必须重新等待 re-review。
Files:
server/src/lib.rsserver/src/cmd/dev/mod.rsserver/src/main.rsserver/src/cmd/registry_pin.rsserver/src/movement/mod.rsserver/src/cmd/dev/nourish.rsserver/src/persistence/mod.rsserver/src/nourishment/mod.rsserver/src/nourishment/tick.rsserver/src/player/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rsdocs/plan-satiety-hydration-v1.md
docs/**/*.md
📄 CodeRabbit inference engine (docs/CLAUDE.md)
docs/**/*.md: 新 plan 头部必须写明接入面:进料、出料、复用的共享类型/event/schema、server/agent/client 跨仓库契约,以及对应的worldview.md锚点。
涉及真元、灵气、衰减、逸散、半衰、距离损耗、排斥或吸力的 plan,必须调用qi_physics;新物理常数和公式必须先扩展qi_physics,不得在功能 plan 中重复实现。
所有真元/灵气流动必须遵守守恒律并通过qi_physics::ledger::QiTransfer;释放使用qi_release_to_zone,吸收使用qi_excretion,不得凭空生成或销毁真元。
涉及玩家可感知行为的 plan,必须在对应机制阶段中内联可直接实现的粒子、音效、HUD、环境、动画和 narration 规格;不得将视听内容笼统推迟到独立阶段。纯 server 逻辑 plan 例外。
每份 plan 必须列出开放问题;实施前必须追加§N.1 决议,逐项给出结论、实施方案、边界条件,并以文件:行号和 plan 章节双锚点落地。
scope 大于或等于 4 个 PR 的 plan,必须在末尾包含§10 实施工作流,并按依赖顺序在一个 plan 内序列化多个 PR,不得拆成多个 plan。
涉及 NBT 建筑、worldgen layout 或复杂视觉资产的 TODO,必须完成三轮提交:(round 1/3)、(round 2/3)、(round 3/3);终轮提交必须包含拼写准确的<PROMISE>担保块。
§10 最末必须包含“单次 consume-plan 全自动到 merge”章节,明确用户提交/consume-plan后即可等待最终归档至docs/finished_plans/。一个 PR 只允许修改一个 plan;
/consume-plan只能追加 Finish Evidence 或执行允许的 plan 归档移动,不得自动修改其他 docs 文件、CLAUDE.md或 worldview 文档。
Files:
docs/plan-satiety-hydration-v1.md
docs/plan-*.md
📄 CodeRabbit inference engine (CLAUDE.md)
docs/plan-*.md: Active plan 每个阶段必须写出可核验的模块路径、类型/函数、测试、schema、Redis key 或跨仓库契约 symbol。
plan 归档前必须所有阶段标记为✅ YYYY-MM-DD并补充严格标题为## Finish Evidence的证据章节。
Files:
docs/plan-satiety-hydration-v1.md
🔇 Additional comments (21)
docs/plan-satiety-hydration-v1.md (1)
3-3: LGTM!Also applies to: 62-62, 66-70, 72-73, 76-81, 107-109, 140-140, 154-154, 156-165, 167-183, 185-219, 221-229
server/src/nourishment/mod.rs (2)
8-23: LGTM!Also applies to: 25-198
202-214: 🎯 Functional Correctness无需修改:
tick_nourishment不会因缺少Nourishment/NourishmentActivityWindow而静默跳过。
tick_nourishment查询要求With<Client>同时包含&mut Nourishment、&mut NourishmentActivityWindow等必需组件;若缺失,实体不会属于该系统查询范围,不会“运行后不扣分”,而是由后续玩家组件注入/持久化流程补全或报错。> Likely an incorrect or invalid review comment.server/src/nourishment/tick.rs (3)
21-88: LGTM!Also applies to: 90-151, 181-216
248-251: 📐 Maintainability & Code Quality无需修改。
server/rust-toolchain.toml使用 stable channel,u64::is_multiple_of可正常编译;editon = "2021"不构成限制。
240-253: 🎯 Functional Correctness无需修改。 当前实现已用活动窗口累计 tick 决定结算时机,不再依赖
CombatClock.tick对 200 取模。server/src/lib.rs (1)
87-87: LGTM!server/src/main.rs (1)
11-12: LGTM!Also applies to: 114-114
server/src/movement/mod.rs (1)
327-327: LGTM!Also applies to: 482-482
server/src/cmd/dev/nourish.rs (1)
67-100: LGTM!Also applies to: 102-279
server/src/cmd/dev/mod.rs (1)
15-15: LGTM!Also applies to: 66-66
server/src/cmd/registry_pin.rs (1)
21-21: LGTM!Also applies to: 96-97, 185-185
server/src/persistence/mod.rs (1)
10362-10451: LGTM!server/src/player/mod.rs (2)
83-107: LGTM!
1152-1210: LGTM!Also applies to: 1292-1474, 1549-1628
server/src/cultivation/mod.rs (2)
200-205: LGTM!Also applies to: 601-627, 669-670, 754-771, 954-986, 1018-1024
1862-1916: LGTM!Also applies to: 1961-1985, 2078-2101, 2876-3084, 3258-3398
server/src/combat/lifecycle.rs (4)
1543-1554: LGTM!Also applies to: 1941-1952
1566-1566: LGTM!Also applies to: 1572-1607, 1614-1646, 2021-2021
121-141: LGTM!Also applies to: 768-768, 946-946, 979-980, 1087-1101, 1815-1829
2398-2420: LGTM!Also applies to: 3366-3451, 4427-4448, 4528-4551, 4976-4984, 5064-5100
| else { | ||
| tracing::warn!( | ||
| "[bong][combat] skipped immediate nourishment persistence for {entity:?}: cultivation bundle is incomplete" | ||
| ); | ||
| return; | ||
| }; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
组件不全时用 warn! 会产生常态噪音,建议降级为 debug!。
revive_lifecycle 会被任何持有 Lifecycle 的实体走到(NPC、以及本文件内大量只挂 Lifecycle/Wounds 的实体形态),它们本就没有 Username/Cultivation 等完整培养 bundle。此时"bundle 不完整"是预期状态而非异常,用 warn! 会在正常复活流程里持续刷告警、淹没真正的持久化失败(下面 1639 那条才是真错误)。
🔧 建议的日志级别调整
- tracing::warn!(
+ tracing::debug!(
"[bong][combat] skipped immediate nourishment persistence for {entity:?}: cultivation bundle is incomplete"
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| else { | |
| tracing::warn!( | |
| "[bong][combat] skipped immediate nourishment persistence for {entity:?}: cultivation bundle is incomplete" | |
| ); | |
| return; | |
| }; | |
| else { | |
| tracing::debug!( | |
| "[bong][combat] skipped immediate nourishment persistence for {entity:?}: cultivation bundle is incomplete" | |
| ); | |
| return; | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/combat/lifecycle.rs` around lines 1608 - 1613, 在 revive_lifecycle
中,将“cultivation bundle is incomplete”分支的 tracing::warn! 调整为
tracing::debug!,保留现有日志内容、实体上下文及 return 行为不变;不要修改下方真正持久化失败的告警。
🔭 Review · PR #1259❌ 未通过:0/4,存在 33 项真实 finding,要求修改
📋 Plan 原意确认
🧑⚖️ 复投结果
🔎 Findings
首轮与复投原始结构化摘要[
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 95,
"plan_intent": {
"status": "misaligned",
"reason": "PR 已落地双轴、五带、境界倍率、持久化入口、生命周期重置和 dev 命令,主体方向符合 P0;但活动窗口被跨会话持久化却没有绑定所属 200-tick sweep,且同 tick 位移按净向量聚合会漏判真实移动,未满足 plan 对“该 sweep 窗口内活动”的契约。正式复活这一独立状态转换也缺少 plan 明确要求的 pin 测试。",
"missing": [
"活动窗口必须仅影响其所属的 200-tick sweep,不能让旧会话或旧 sweep 的 dash/move 污染重连后的结算",
"同 tick 往返或折返水平移动必须被识别为真实活动",
"普通正式复活路径的 80/80 重置、活动窗口清零及持久化测试"
]
},
"summary": "P0 主体接线基本完整,但活动统计的时间边界和位移聚合存在可复现断契约,重连后可能用旧 dash 记录按三倍扣减,往返移动也可能被判为静止。另有正式复活验收分支未被测试锁定,因此不能批准。",
"findings": [
{
"severity": "major",
"file": "server/src/cultivation/mod.rs",
"line": "751-769",
"title": "跨会话恢复的活动窗口没有 sweep 身份,会让旧活动污染未来结算",
"evidence": "join 时无条件恢复 `nourishment_activity_window`;该结构只保存 idle/move/dash 三个计数,没有保存所属 sweep、保存时 CombatClock 或截止 tick(server/src/nourishment/tick.rs:29-54)。之后 `tick_nourishment` 只要遇到任意全局 `clock.tick % 200 == 0` 就使用该窗口结算(server/src/nourishment/tick.rs:245-259)。因此玩家可在一次旧 sweep 中 dash 后断线,跨过若干 sweep 再重连,旧 dash 仍会令下一次扣减使用 ×3,违反 plan“该 sweep 窗口内有水平位移/冲刺”的限定以及 PR 所称 session lease。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "165-176",
"title": "同 tick 位移按净向量相加,往返移动会被错误抵消为静止",
"evidence": "`record_movement_events` 分别累加 delta_x/delta_z,最后只检查合成净位移。例如同 tick 先从 x=0 移到 0.06,再从 0.06 回到 0,两段真实水平位移会合计为 (0,0),不会刷新 lease。现有测试只覆盖同方向的两个 0.03 片段,没有覆盖折返;这与 plan 的“有水平位移”及 PR body 的“同 tick 多事件聚合”不符。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1566",
"title": "正式复活重置分支未按 plan 要求提供独立状态转换测试",
"evidence": "`revive_lifecycle` 新增了双轴重置、活动窗口清零和即时持久化,但本 diff 新增的生命周期断言覆盖的是 NearDeath 自救保留(3363-3448)、Reincarnate(4424-4548)和 CreateNewCharacter(4973-5098),没有测试普通正式复活这一独立入口。plan §4 和 §8.1 #3 明确要求“正式复活重置”并要求专门 pin 生命周期边界,不能由转世/新角色测试代替。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "REQUEST_CHANGES",
"confidence": 96,
"plan_intent": {
"status": "aligned",
"reason": "PR 的模块注册、命令注册、持久化入口、加入/重连/复活/新角色生命周期和六境界倍率总体对应 PR-1/P0 原意;但活动窗口与 200 tick sweep 的接线存在可复现断层,当前不能确认周期消耗和重连语义正确。",
"missing": [
"活动窗口没有记录所属 sweep/时钟相位,重连跨过全局 200 tick 边界后会把旧窗口活动带入新窗口",
"结算只看全局 tick 是否为 200 的倍数,不校验窗口实际累计 tick 数,新加入、复活或重连玩家可能不足 200 tick 就被扣完整周期,或累计超过 200 tick 后只结算一次并丢失余量",
"同 tick 水平位移按有符号向量求和,反向移动会抵消,未满足“窗口内有水平位移”的活动判定"
]
},
"summary": "P0 的主要定义均已接入生产 schedule、持久化和生命周期,但周期窗口与全局时钟未正确绑定,重连及任意时刻加入/复活会产生错误扣减。MovementEvent 聚合还会漏掉同 tick 往返移动,核心活动倍率契约尚未锁住。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "22-61, 94-106, 231-264",
"title": "活动窗口未绑定 sweep 相位,重连和生命周期重置会在错误时长上结算完整 200 tick 消耗",
"evidence": "NourishmentActivityWindow 只保存三类 tick 计数,NourishmentSweepCursor 也只保存本 session 的 last_processed_tick;tick_nourishment 却无条件在 CombatClock.tick.is_multiple_of(200) 时按固定 200 tick 时长调用 sweep_losses,未检查 activity_window.total_ticks()。因此玩家若在 tick 199 加入或正式复活,tick 200 即会以约 2 个活动 tick 扣完整 10 秒;若携带旧 partial window 跨过一个或多个全局边界后重连,旧窗口活动会污染当前 sweep,累计超过 200 的 tick 也会在一次结算后全部 reset 丢失。现有重连测试把时钟直接设为 200,恰好避开了任意重连相位。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "155-177",
"title": "同 tick 位移按有符号向量相加,真实水平移动可被反向包抵消",
"evidence": "record_movement_events 将每个事件的 delta_x/delta_z 直接累加,最后只检查合成向量长度。玩家同 tick 先移动 +0.03 再移动 -0.03 时,虽然发生了 0.06 格水平路径移动,合成结果却为零,不刷新 20-tick lease;更大的往返移动同样可以被完全漏判。这不符合 plan 的“该 sweep 窗口内有水平位移”及 PR 声明的同 tick 多事件聚合。"
},
{
"severity": "minor",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1571, 3363-3454, 4424-4548, 4973-5103",
"title": "新增正式复活重置路径没有对应的独立状态转换与落盘测试",
"evidence": "实现已在 revive_lifecycle 中重置 Nourishment 和 NourishmentActivityWindow 并排队持久化,但本 diff 新增的生命周期断言只覆盖 NearDeath 自救保留、Reincarnate 重置和 CreateNewCharacter 重置;没有测试普通正式 Revive 从非默认值重置为 80/80、清空窗口并立即写入 cultivation bundle。该分支还是 §8.1 #3 明确要求单独锁定的交付物。"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 96,
"plan_intent": {
"status": "misaligned",
"reason": "PR 覆盖了 P0 的组件、境界倍率、生命周期、持久化、命令和生产 schedule,且未发现绕过 qi_physics 的真元/灵气流动;但周期消耗没有按玩家实际经历的 sweep 时长结算,持久化活动窗口也未绑定 CombatClock 相位,导致首次加入和普通重连可能被错误扣费,偏离 plan 锁定的每分钟损耗与重连语义。",
"missing": [
"活动窗口必须绑定 sweep/CombatClock 相位,不能把跨边界、跨 session 的旧 move/dash 峰值带入新窗口",
"周期损耗必须按实际累计 tick 结算,不能在不足 200 tick 时扣完整 10 秒基线",
"正式 Revive 路径需要单独 pin 80/80 重置、活动窗口清零及即时持久化"
]
},
"summary": "P0 主体接线和六境界倍率基本符合方向,也没有灵气守恒违规。但核心周期窗口缺少时钟相位且无条件按 200 tick 扣费,重连会携带失去归属的旧活动峰值;同时 cultivation bundle 的兼容保留采用非原子读改写,可能回滚刚完成的复活重置,因此不能通过。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "190-249",
"title": "周期结算忽略实际累计 tick,首次加入和重连可被扣完整 10 秒损耗",
"evidence": "sweep_losses 固定使用 NOURISH_SWEEP_INTERVAL_TICKS / NOURISH_TICKS_PER_MINUTE,tick_nourishment 只检查全局 clock.tick % 200 == 0,完全不校验 activity_window.total_ticks()。因此玩家在 tick 199 加入,只记录 199、200 两个 tick,仍会在 tick 200 扣完整 200 tick 损耗。持久化窗口也没有保存所属边界或最后时钟;跨过若干边界后重连,旧 dash_ticks 仍会把下一次无关 sweep 提升到 3 倍,窗口还可能在非边界时增长到 200 以上,与反序列化只接受 <200 的契约自相矛盾。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6200-6272",
"title": "兼容 writer 对 nourishment 的保留是非原子 read-modify-write,可回滚生命周期重置",
"evidence": "persist_player_cultivation_bundle_with_nourishment 先用 SELECT 读取整份旧 cultivation_json,随后在独立语句中 UPSERT 整行,中间没有事务、锁或 CAS。旧签名调用方传 None 时会把 SELECT 时看到的 nourishment 填回整份 JSON;若期间复活/转世即时保存或 autosave 写入了 80/80,新旧 writer 交错即可由旧 writer 再覆盖成旧饥渴值,活动窗口同样会回滚。"
},
{
"severity": "minor",
"file": "server/src/nourishment/tick.rs",
"line": "154-175",
"title": "同 tick 的反向水平移动会被向量相消,错误判为静止",
"evidence": "record_movement_events 将同一实体所有事件的 delta_x、delta_z 分别求和,最后只对净位移做 hypot。玩家同 tick 先移动 +0.06、再移动 -0.06 时净位移为 0,尽管已经发生两次超过 0.05 的水平移动,lease 仍不会刷新;这也不满足测试文字声明的“any qualifying event in a tick must win”。"
},
{
"severity": "minor",
"file": "server/src/nourishment/mod.rs",
"line": "18",
"title": "NOURISH_TICKS_PER_MINUTE 硬编码复制了服务器 TPS 真源",
"evidence": "代码直接声明 NOURISH_TICKS_PER_MINUTE = 1_200.0,而关联 plan §8.1 #5 已把 server/src/combat/components.rs::TICKS_PER_SECOND 列为时长公式的真源。当前数值恰好按 20 TPS 正确,但 TPS 一旦调整,200 tick 的真实秒数与每分钟损耗会静默漂移。"
},
{
"severity": "minor",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1566, 3363-5098",
"title": "正式 Revive 的 Nourishment 状态转换没有专门 pin 测试",
"evidence": "实现把 80/80 重置放进 revive_lifecycle,但本 diff 新增的生命周期断言只覆盖 NearDeath 自救保留、Reincarnate 重置和 CreateNewCharacter 重置;没有通过正式 Revive action 验证双轴重置、活动窗口清零及即时 SQLite 写回。该测试是 plan §4 明确列出的状态机交付物。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 96,
"plan_intent": {
"status": "misaligned",
"reason": "组件、持久化、生命周期重置和命令主体基本按 PR-1 接线,但核心的“每 200 tick 周期消耗”没有按玩家实际累计窗口结算,水平移动聚合也会漏掉同 tick 内真实发生的往返位移,因此尚未满足 plan 的周期与活动判定原意。",
"missing": [
"按每名玩家实际累计的 200 tick 窗口结算,避免 tick 0、临近全局边界加入或任意相位重连时整窗多扣/少扣",
"同 tick 移动聚合须保留实际水平移动事实,不能因反向位移向量相消而判定为静止",
"补充 tick 0、边界前加入、任意时钟相位重连和同 tick 往返位移的生产路径测试"
]
},
"summary": "P0 的主要结构和生命周期测试较完整,但周期结算目前绑定全局 CombatClock 取模而非玩家的 200 tick 窗口,会在出生、加入和重连时错误扣减。MovementEvent 聚合使用净位移向量,也与“窗口内有水平位移”的契约不符。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "246-280, 663-726",
"title": "全局 tick 取模会把不足 200 tick 的玩家窗口按完整周期扣减",
"evidence": "tick_nourishment 只要 clock.tick.is_multiple_of(200) 就调用 sweep_losses,而 sweep_losses 固定按 200 tick 计算损失,完全不检查 activity_window.total_ticks()。因此 tick 0 首次更新就会扣完整 10 秒;在 tick 199 加入的玩家也会在 tick 200 仅累计约 2 tick 后被扣完整周期。现有测试还从 tick 1 直接跳到 199、200,仅执行三次 Update,却明确期待完整 200 tick 损失,实际锁定了错误行为。持久化窗口在任意相位重连同样会出现少于或多于 200 个累计 tick 只结算一次的问题。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "166-190",
"title": "同 tick 位移按向量求和会让真实水平移动相消为静止",
"evidence": "record_movement_events 将同一实体的 delta_x/delta_z 分别相加,最后只检查合成净位移。若同 tick 先移动 +0.06 再移动 -0.06,玩家实际发生了两次超过阈值的水平位移,但合成结果为零,lease 不刷新,违反 plan 的“该 sweep 窗口内有水平位移”以及 PR Body 的同 tick 聚合声明。已有测试只覆盖同向碎片和后续零位移,没有覆盖反向相消。"
}
]
}
][
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:PR 已完成双轴、五段带、六境界倍率、生产 schedule、持久化入口、生命周期重置和 dev 命令等 P0 主体接线,也未发现绕过 qi_physics 的灵气流动;但核心周期消耗并未对应玩家实际经历的 200 tick sweep,持久化活动窗口又缺少时钟相位,导致加入、复活和任意相位重连时多扣、漏算或由旧 dash 污染新周期。同 tick 往返移动也会被净向量抵消。正式复活的独立状态转换仍未按 plan 明确要求由测试锁定,因此尚不符合 plan 原意和交付物。",
"missing": [
"为每名玩家记录可核验的 sweep 身份或累计相位,仅在实际累计完整 200 个有效 tick 后结算,并正确保留余量",
"明确并实现跨 session 的窗口语义,禁止旧 sweep 的 move/dash 峰值污染重连后的新周期",
"按实际水平路径或逐事件资格聚合同 tick MovementEvent,避免反向位移相消",
"正式 Revive 的 80/80 重置、活动窗口清零及即时落盘测试",
"兼容 cultivation writer 对 nourishment 字段的原子保留"
]
},
"summary": "维持 REQUEST_CHANGES。其他 reviewer 的意见进一步确认首轮结论,并揭示了更广的周期问题:当前实现不只是旧活动跨会话污染,还会在玩家不足 200 tick 时按完整周期扣减,累计超过 200 tick 时又只结算一次并丢弃余量。另有 cultivation bundle 非原子读改写可能回滚正式复活或转世刚落盘的 80/80 状态。以上均涉及 P0 核心正确性,不能批准。 Plan 原意未确认:最终确认:PR 已完成双轴、五段带、六境界倍率、生产 schedule、持久化入口、生命周期重置和 dev 命令等 P0 主体接线,也未发现绕过 qi_physics 的灵气流动;但核心周期消耗并未对应玩家实际经历的 200 tick sweep,持久化活动窗口又缺少时钟相位,导致加入、复活和任意相位重连时多扣、漏算或由旧 dash 污染新周期。同 tick 往返移动也会被净向量抵消。正式复活的独立状态转换仍未按 plan 明确要求由测试锁定,因此尚不符合 plan 原意和交付物。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "22-61, 94-106, 231-264",
"title": "全局取模结算没有对应玩家实际经历的 200 tick sweep",
"evidence": "NourishmentActivityWindow 只保存三类 tick 数,不保存 window_start_tick、sweep_id 或最后结算时钟;NourishmentSweepCursor 也只阻止同一 session 内重复处理同一个 clock tick。tick_nourishment 却在任意全局 tick % 200 == 0 时调用固定按 200 tick 计算的 sweep_losses,完全不检查 total_ticks()。玩家在 tick 199 加入或正式复活,会在约 2 个活动 tick 后于 tick 200 被扣完整 10 秒;持久化 199 tick 窗口若在任意其他相位重连,则可能累计超过 200 后仍只结算一次并 reset,丢失余量,同时旧窗口中的 dash 会把无关的新周期提升到三倍。现有 server/src/nourishment/tick.rs:663-726 测试从 tick 1 跳到 199、200,仅执行三次 Update 却期待完整周期扣减,实际锁定了该错误行为。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "155-177",
"title": "同 tick 水平移动按有符号净向量聚合,真实往返移动会被判为静止",
"evidence": "record_movement_events 将同一实体所有 MovementEvent 的 delta_x、delta_z 分别相加,最后只检查合成净位移。玩家同 tick 先移动 +0.06 再移动 -0.06 时,已经发生两段超过 0.05 的真实水平位移,但合成结果为零,20-tick lease 不会刷新。现有测试只覆盖同方向的两个 0.03 片段和后续零水平事件,没有覆盖反向相消;这也与测试声明的“any qualifying event in a tick must win”及 plan 的“窗口内有水平位移”不一致。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6200-6272",
"title": "兼容 writer 非原子保留 nourishment,可能回滚生命周期重置",
"evidence": "persist_player_cultivation_bundle_with_nourishment 在 nourishment 参数为 None 时,先 SELECT 旧 cultivation_json,再从旧 JSON 提取 nourishment,最后通过另一个自动提交语句 UPSERT 整行;SELECT 与 UPSERT 之间没有事务、写锁或 CAS。若旧签名 writer 读到复活前的饥渴值后,正式复活、转世或 autosave 先写入新的 80/80,旧 writer 随后仍可把刚读取的旧 nourishment 和 activity window 覆盖回去,破坏 plan 要求的即时生命周期持久化。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1566, 3363-3448, 4424-4548, 4973-5098",
"title": "正式 Revive 的独立状态转换仍未按 plan 提供 pin 测试",
"evidence": "revive_lifecycle 新增了 Nourishment 重置、NourishmentActivityWindow 清零和即时 cultivation bundle 持久化,但本 diff 新增的生命周期断言分别覆盖 NearDeath 自救保留、Reincarnate 和 CreateNewCharacter,没有通过普通正式 Revive action 验证该分支。plan §4 和 §8.1 #3 明确将正式复活与新角色/转世定义为两条独立接线,并在饱和测试清单中单独要求“正式复活重置”。"
},
{
"severity": "minor",
"file": "server/src/nourishment/mod.rs",
"line": "18",
"title": "每分钟 tick 数复制了服务器 TPS 真源",
"evidence": "NOURISH_TICKS_PER_MINUTE 被硬编码为 1_200.0,而 plan §8.1 #5 明确把 server/src/combat/components.rs::TICKS_PER_SECOND 列为时长公式真源。当前仅因 TPS 为 20 而数值一致;服务器 TPS 调整后,周期对应的真实时间和每分钟损耗会静默漂移。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:模块注册、生产 schedule、持久化入口、生命周期和六境界倍率的主体接线符合 P0 方向,但核心周期实现把每玩家活动窗口与全局 CombatClock 取模混用,无法保证按玩家实际经历的 200 tick 结算;活动聚合也会漏掉真实往返位移。此外,兼容持久化 writer 可并发回滚生命周期重置。以上均直接破坏 plan 锁定的周期、重连和正式复活契约,因此当前交付不符合 plan 原意。",
"missing": [
"为每名玩家建立可核验的完整 200-tick 窗口语义,或持久化明确的 sweep 身份与相位,禁止不足 200 tick 扣完整周期及跨窗口活动污染",
"按实际水平路径聚合同 tick MovementEvent,避免反向位移相消",
"将旧签名 writer 对 nourishment 字段的兼容保留改为原子更新,不能回滚复活、转世或 autosave 的新值",
"补充正式 Revive 的 80/80 重置、活动窗口清零及即时 SQLite 落盘测试",
"周期时长公式引用服务器 TPS 真源,避免重复硬编码 1200 tick/min"
]
},
"summary": "维持 REQUEST_CHANGES。其他 reviewer 的意见进一步确认了首轮发现:tick 0、边界前加入、任意相位重连都会在不足或超过 200 个实际玩家 tick 时结算固定完整周期,旧 dash 还可能污染无关窗口;同 tick 往返移动会被净向量抵消。复核后另确认兼容 cultivation writer 存在非原子 read-modify-write,可把刚完成的正式复活或转世重置覆盖回旧饥渴值。核心运行契约仍有多个 major,不能批准。 Plan 原意未确认:最终确认:模块注册、生产 schedule、持久化入口、生命周期和六境界倍率的主体接线符合 P0 方向,但核心周期实现把每玩家活动窗口与全局 CombatClock 取模混用,无法保证按玩家实际经历的 200 tick 结算;活动聚合也会漏掉真实往返位移。此外,兼容持久化 writer 可并发回滚生命周期重置。以上均直接破坏 plan 锁定的周期、重连和正式复活契约,因此当前交付不符合 plan 原意。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "22-61, 94-106, 231-264",
"title": "活动窗口未绑定正确的 200-tick 结算边界,首次加入和重连会错误扣减",
"evidence": "NourishmentActivityWindow 只保存三类累计 tick,NourishmentSweepCursor 只防止同一 CombatClock tick 重复执行;但 tick_nourishment 无条件在 clock.tick % 200 == 0 时调用固定按 200 tick 计算的 sweep_losses,完全不检查 activity_window.total_ticks()。因此 tick 0 首次更新会记录 1 tick 后立即扣完整 10 秒;tick 199 加入或正式复活会在 tick 200 只经历约 2 tick 就被扣完整周期。相反,保存了 199 tick 的玩家若在 tick 201 重连,窗口会继续增长到接近 398 tick,直到 tick 400 才只结算一次并丢弃余量;窗口中的旧 dash 也会把这个跨边界周期整体提升到三倍。现有测试从 tick 1 直接跳到 199、200,仅执行三次 Update 却期待完整周期损失,实际锁定了错误行为。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "155-177",
"title": "同 tick 反向位移会被有符号向量相消,真实移动被判为静止",
"evidence": "record_movement_events 分别累加每个事件的 delta_x 和 delta_z,最后只检查合成净向量。玩家同 tick 从 x=0 移到 0.06,再从 0.06 返回 0,实际水平路径为 0.12,但合成结果为 (0,0),不会刷新 20-tick movement lease。现有测试只覆盖同方向的 0.03+0.03 和后续零水平事件,没有覆盖折返。这不满足 plan 的“窗口内有水平位移”,也与测试文案“any qualifying event in a tick must win”冲突。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6200-6272",
"title": "兼容 writer 的非原子 read-modify-write 可回滚正式复活或转世重置",
"evidence": "persist_player_cultivation_bundle_with_nourishment 在调用方传 None 时先 SELECT 旧 cultivation_json,提取旧 nourishment 和 nourishment_activity_window,随后在独立的自动提交语句中 UPSERT 整行。SELECT 与 UPSERT 之间没有事务、写锁或 CAS。若旧签名 writer 读到复活前的 7/8 与旧活动窗口,期间生命周期 writer 已写入 80/80 和清零窗口,旧 writer 随后的 UPSERT 会再次把旧值覆盖回去。Bevy 系统及不同 SQLite 连接之间存在这种交错可能;当前测试只验证顺序调用,不覆盖并发交错。"
},
{
"severity": "minor",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1571, 3363-3454, 4424-4548, 4973-5103",
"title": "正式 Revive 独立状态转换仍没有 plan 要求的 pin 测试",
"evidence": "revive_lifecycle 已增加双轴重置、活动窗口清零和即时 cultivation bundle 持久化,但新增测试只覆盖 NearDeath 自救保留、Reincarnate 和 CreateNewCharacter。没有通过普通正式 Revive action 从非默认值验证 80/80、窗口清零以及 SQLite 即时写回。plan §4 与 §8.1 #3 明确把正式复活和新角色/转世列为两条独立生命周期接线,不能由后两者代替。"
},
{
"severity": "minor",
"file": "server/src/nourishment/mod.rs",
"line": "18",
"title": "周期时长重复硬编码服务器 TPS,可能与 CombatClock 真源漂移",
"evidence": "NOURISH_TICKS_PER_MINUTE 直接写成 1_200.0,而 plan §8.1 #5 将 server/src/combat/components.rs::TICKS_PER_SECOND 指定为时长公式落点。当前仅因服务器为 20 TPS 才得到 200 tick = 10 秒;TPS 调整后,饱食水分每分钟损耗会静默偏离 plan 数值。"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:PR 已完成双轴、五段状态、六境界倍率、生产 schedule、生命周期、持久化和 dev 命令的主体接线,且未发现绕过 qi_physics 的真元/灵气流动;但核心周期消耗按全局 CombatClock 边界固定扣除完整 200 tick 损耗,而不是按玩家实际累计窗口结算,持久化窗口又没有 sweep 身份,导致加入、复活和跨会话重连的扣减时长与活动倍率错误。此外,兼容持久化 writer 存在可回滚生命周期重置的非原子读改写。因此尚未符合 plan 的周期、重连和状态转换原意。",
"missing": [
"按每名玩家实际累计的 200 tick 窗口结算,并为持久化窗口建立明确的时钟或 sweep 归属",
"同 tick 聚合必须保留实际水平移动路径,不能因反向位移相消而判定为静止",
"cultivation bundle 的 nourishment 兼容保留必须原子化,避免并发 writer 回滚复活或转世重置",
"普通正式 Revive 的 80/80 重置、活动窗口清零和即时持久化专门测试"
]
},
"summary": "维持 REQUEST_CHANGES。其他 reviewer 的意见进一步确认了首轮发现:当前实现会在玩家不足 200 个实际在线 tick 时扣完整周期,并可让旧 sweep 的 dash/move 峰值污染重连后的结算;同 tick 往返移动也会被净向量抵消。主体接线和守恒方向正确,但这些都是核心 P0 契约问题,不能批准。 Plan 原意未确认:最终确认:PR 已完成双轴、五段状态、六境界倍率、生产 schedule、生命周期、持久化和 dev 命令的主体接线,且未发现绕过 qi_physics 的真元/灵气流动;但核心周期消耗按全局 CombatClock 边界固定扣除完整 200 tick 损耗,而不是按玩家实际累计窗口结算,持久化窗口又没有 sweep 身份,导致加入、复活和跨会话重连的扣减时长与活动倍率错误。此外,兼容持久化 writer 存在可回滚生命周期重置的非原子读改写。因此尚未符合 plan 的周期、重连和状态转换原意。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "190-264",
"title": "全局 200-tick 取模与固定周期损耗不匹配玩家实际累计窗口",
"evidence": "`sweep_losses` 始终使用 `NOURISH_SWEEP_INTERVAL_TICKS / NOURISH_TICKS_PER_MINUTE` 计算完整 200 tick 损耗;`tick_nourishment` 只检查 `clock.tick.is_multiple_of(200)`,没有校验 `activity_window.total_ticks()`。玩家在 tick 199 加入或正式复活,只经历约 2 个处理 tick,仍会在 tick 200 被扣完整 10 秒;tick 0 首次处理甚至会立即扣完整周期。另一方面,`server/src/cultivation/mod.rs:751-769` 会恢复只有计数、没有 sweep ID 或时钟相位的窗口,旧会话的 dash 记录可跨过多个全局边界后影响无关结算;若重连相位较晚,窗口还可在运行时累计到 200 以上后只结算一次并丢弃余量。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6200-6272",
"title": "兼容 writer 以非原子 read-modify-write 保留 nourishment,可能回滚生命周期重置",
"evidence": "`persist_player_cultivation_bundle_with_nourishment` 先执行独立 `SELECT` 读取旧 `cultivation_json`,当调用方传入 `None` 时复制旧 nourishment 字段,之后再用另一个 autocommit 语句 UPSERT 整行。该读写序列没有事务、锁或 CAS:旧 writer 可先读到 7/8,正式复活 writer 随后写入 80/80,旧 writer 最后再将包含 7/8 的整份 JSON 覆盖回去;活动窗口同样存在 lost-update。单线程顺序测试无法覆盖这一交错。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "154-176",
"title": "同 tick 反向位移按净向量相消,真实水平活动会被漏判",
"evidence": "`record_movement_events` 将同一实体各事件的 `delta_x`、`delta_z` 分别做有符号求和,最后只检查合成净位移。玩家同 tick 先移动 +0.06,再移动 -0.06,实际发生了两次超过 0.05 的水平位移,但最终向量为零,20-tick lease 不会刷新。现有测试只覆盖同向的两个 0.03 片段及后续零位移,未覆盖折返;这也违背测试文字所称的“any qualifying event in a tick must win”。"
},
{
"severity": "minor",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1566, 3363-5098",
"title": "普通正式 Revive 的新增 Nourishment 状态转换没有独立 pin 测试",
"evidence": "`revive_lifecycle` 已新增双轴重置、活动窗口清零和即时 cultivation bundle 持久化,但本 diff 的生命周期测试分别覆盖 NearDeath 自救保留、Reincarnate 和 CreateNewCharacter,没有通过普通正式 Revive action 验证该独立分支。plan §4 与 §8.1 #3 明确将正式复活和新角色/转世定义为不同接线,并要求正式复活重置测试。"
},
{
"severity": "minor",
"file": "server/src/nourishment/mod.rs",
"line": "18",
"title": "每分钟 tick 数硬编码复制了服务器 TPS 真源",
"evidence": "`NOURISH_TICKS_PER_MINUTE = 1_200.0` 隐式假定 20 TPS,而 plan §8.1 #5 将 `server/src/combat/components.rs::TICKS_PER_SECOND` 指定为时长公式真源。当前数值虽然一致,但 TPS 调整后周期真实时长和每分钟损耗会静默漂移。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:P0 的组件、境界倍率、生产 schedule、持久化入口、生命周期和 dev 命令主体已经接线,也未发现绕过 qi_physics 的真元流动;但周期消耗没有按玩家实际累计的 200 tick 窗口结算,持久化活动窗口也没有 sweep/时钟身份,同 tick 往返移动还会被净向量抵消。这些问题直接破坏 plan 锁定的周期损耗与活动倍率语义。",
"missing": [
"按每名玩家实际累计的 200 tick 窗口结算,或显式保存并校验所属全局 sweep 相位,禁止不足 200 tick 扣整窗及跨 sweep 活动污染",
"同 tick 聚合必须保留实际水平移动路径,不能让反向位移相消",
"正式 Revive 独立生产路径的 80/80 重置、活动窗口清零和即时落盘 pin 测试",
"消除 cultivation bundle 兼容 writer 的非原子 read-modify-write 覆盖窗口",
"从服务器 TICKS_PER_SECOND 真源推导每分钟 tick 数"
]
},
"summary": "复投后维持 REQUEST_CHANGES,并新增确认了持久化 writer 的竞态风险。当前测试不仅遗漏边界,还把仅执行三个 tick 更新便扣完整 200 tick 损耗的错误行为写成了期望值;核心周期、重连和移动活动契约尚不能通过。 Plan 原意未确认:最终确认:P0 的组件、境界倍率、生产 schedule、持久化入口、生命周期和 dev 命令主体已经接线,也未发现绕过 qi_physics 的真元流动;但周期消耗没有按玩家实际累计的 200 tick 窗口结算,持久化活动窗口也没有 sweep/时钟身份,同 tick 往返移动还会被净向量抵消。这些问题直接破坏 plan 锁定的周期损耗与活动倍率语义。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "190-264, 663-726",
"title": "全局取模会把不足 200 tick 的玩家窗口按完整周期扣减并丢失跨边界余量",
"evidence": "tick_nourishment 每次实际执行只向 activity_window 记录一个 tick,却只要 CombatClock.tick 是 200 的倍数就调用 sweep_losses;后者固定使用 200 / 1200 分钟,不检查 activity_window.total_ticks()。因此玩家在 tick 199 加入或正式复活,tick 200 就会在仅累计约两个 tick 后被扣完整 10 秒损耗;tick 0 首次更新也可立即扣费。恢复的 partial window 没有所属 sweep 或时钟相位,任意相位重连后可能把旧 dash 峰值带到未来边界;若累计超过 200,结算后直接 reset 还会丢掉余量。production_tick_uses_combat_clock_boundaries_and_ignores_duplicate_updates 从 tick 1 跳至 199、200,仅执行三次 Update 却期待完整 sweep 损失,实际锁定了错误行为。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "155-177",
"title": "同 tick 水平位移按净向量求和,真实往返移动会相消为静止",
"evidence": "record_movement_events 分别累加每个 MovementEvent 的有符号 delta_x 和 delta_z,最后只检查合成净向量。玩家同 tick 先移动 +0.06,再移动 -0.06 时已经发生两段超过阈值的水平移动,但合成结果为零,NourishmentMovementTracker 不会刷新。现有测试只覆盖同方向的两个 0.03 片段以及合格事件后接零水平事件,没有覆盖反向相消,与测试文字“any qualifying event in a tick must win”及 plan 的“窗口内有水平位移”不符。"
},
{
"severity": "major",
"file": "server/src/persistence/mod.rs",
"line": "6200-6272",
"title": "兼容 writer 以非原子 read-modify-write 保留 nourishment,可回滚并发生命周期重置",
"evidence": "persist_player_cultivation_bundle_with_nourishment 在 nourishment 参数为 None 时先 SELECT 旧 cultivation_json,再从中复制 nourishment 和 activity_window,随后用独立 UPSERT 覆盖整行;两步之间没有事务、写锁或 CAS。若旧签名 writer 读取旧饥渴值后,正式复活、转世或 autosave 先写入新值,旧 writer 随后的整行 UPSERT 会把旧值重新覆盖回来。新增 legacy_cultivation_bundle_writer_preserves_existing_nourishment_slices 只验证顺序调用,无法发现该交错。"
},
{
"severity": "minor",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1566, 3363-5098",
"title": "正式 Revive 的独立状态转换和即时持久化仍未被测试锁定",
"evidence": "revive_lifecycle 新增了双轴重置、活动窗口清零及 queue_nourishment_cultivation_bundle_persist,但本 diff 的新增生命周期测试覆盖 NearDeath 自救保留、Reincarnate 和 CreateNewCharacter,没有通过普通正式 Revive action 验证该分支。该路径与转世/新角色是 plan §8.1 #3 明确区分的独立接线,而且即时持久化闭包会在 cultivation bundle 任一必需组件缺失时直接跳过,更需要生产路径测试。"
},
{
"severity": "minor",
"file": "server/src/nourishment/mod.rs",
"line": "18",
"title": "每分钟 tick 数硬编码复制服务器 TPS 真源",
"evidence": "NOURISH_TICKS_PER_MINUTE 直接写为 1_200.0,而 plan §8.1 #5 明确把 server/src/combat/components.rs::TICKS_PER_SECOND 列为时长公式真源。当前值在 20 TPS 下恰好正确,但服务器 TPS 调整后,200 tick 的实际时长、文档中的每分钟损耗和实现会静默漂移。"
}
]
}
] |
|
/review |
🔭 Review · PR #1259❌ 未通过:0/4,存在 24 项真实 finding,要求修改
📋 Plan 原意确认
🧑⚖️ 复投结果
🔎 Findings
首轮与复投原始结构化摘要[
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 95,
"plan_intent": {
"status": "misaligned",
"reason": "双轴模型、五段边界、六境界倍率、生命周期接线、持久化和 dev 命令主体均落到了 P0,但周期调度被实现成可持久化的玩家个人 200-tick 窗口,明确偏离 plan 要求的 CombatClock 全局 is_multiple_of(200) sweep;同时正式复活这一独立验收分支没有对应契约测试。",
"missing": [
"按 plan 锁定的 CombatClock 全局 200-tick sweep 语义,或先经 plan 决议明确批准个人窗口及其新增持久化 schema",
"正式 Revive 分支重置 80/80 并即时持久化的专门状态转换测试"
]
},
"summary": "P0 大部分交付物已经接通,但核心消耗节拍被改成了 plan 未授权的个人累计窗口,现有测试还刻意锁定了与 plan 相反的行为。正式复活验收分支和命令树轴枚举契约也未完整 pin,当前不应通过。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "242-272",
"title": "周期消耗改成个人累计窗口,偏离 plan 的全局 CombatClock sweep",
"evidence": "plan §1/接入面明确要求仿 shelflife 使用 is_multiple_of(200) 门控;实现没有检查 clock.tick.is_multiple_of(200),而是每个 distinct tick 累加 activity_window,满 200 才扣减。对应测试 658-686 还明确要求在全局 tick 199 加入的玩家延迟到 tick 398 才结算,证明行为并非 plan 指定的下一次全局 tick 200 sweep。额外持久化 nourishment_activity_window 也因此成为 plan 未声明的新存档契约。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1567",
"title": "正式复活重置与即时落盘缺少独立验收测试",
"evidence": "这里为 revive_lifecycle 增加了 80/80 重置、活动窗口清零和延迟持久化,但本 diff 新增的生命周期测试只覆盖 NearDeath 自救保留、Reincarnate 重置和 CreateNewCharacter 重置;没有从非默认存档执行正式 Revive 并同时断言 ECS 与 SQLite。plan §4 和 §8.1 #3 明确把正式复活列为独立接线及专门验收边界。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "19-34",
"title": "命令树将轴声明为任意字符串,未锁定 satiety|hydration 分支",
"evidence": "plan 指定的 Brigadier 契约是 /nourish set satiety|hydration <value>,实现却注册 argument(\"axis\").with_parser::<String>(),registry_pin.rs:93 也将其 pin 为 <axis:string>。因此 food 等任意值会被命令树接受并执行到 handler 后才拒绝,客户端补全和解析契约没有表达两个合法轴。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "REQUEST_CHANGES",
"confidence": 0,
"plan_intent": {
"status": "unclear",
"reason": "Codex initial-B 执行失败",
"missing": []
},
"summary": "Codex initial-B 执行失败:exit=524 | stderr: HTTP 524: <!DOCTYPE html>\n<!--[if lt IE 7]> <html class=\"no-js ie6 oldie\" lang=\"en-US\"> <![endif]-->\n<!--[if IE 7]> <html class=\"no-js ie7 oldie\" lang=\"en-US\"> <![endif]-->\n<!--[if IE 8]> <html class=\"no-js ie8 oldie\" lang=\"en-US\"> <![endif]-->\n<!--[if gt IE 8]><!--> <html class=\"no-js\" lang=\"en-US\"> <!--<![endif]-->\n<head>\n\n<title>claudeopus.world | 524: A timeout occurred</title>\n<meta charset=\"UTF-8\" />\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=Edge\" />\n<meta name=\"robots\" content=\"noindex, nofollow\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<link rel=\"stylesheet\" id=\"cf_styles-css\" href=\"/cdn-cgi/styles/main.css\" />\n</head>\n<body>\n<div id=\"cf-wrapper\">\n <div id=\"cf-error-details\" class=\"p-0\">\n <header class=\"mx-auto pt-10 lg:pt-6 lg:px-8 w-240 lg:w-full mb-8\">\n \n...[truncated 5868 chars]...\nutton type=\"button\" id=\"cf-footer-ip-reveal\" class=\"cf-footer-ip-reveal-btn\">Click to reveal</button>\n <span class=\"hidden\" id=\"cf-footer-ip\">20.55.14.48</span>\n <span class=\"cf-footer-separator sm:hidden\">•</span>\n </span>\n <span class=\"cf-footer-item sm:block sm:mb-1\"><span>Performance & security by</span> <a rel=\"noopener noreferrer\" href=\"https://www.cloudflare.com/5xx-error-landing?utm_source=errorcode_524&utm_campaign=api.claudeopus.world\" id=\"brand_link\" target=\n...[truncated]",
"findings": [
{
"severity": "major",
"file": ".github/scripts/review.mjs",
"line": "0",
"title": "Codex reviewer initial-B 执行失败",
"evidence": "exit=524 | stderr: HTTP 524: <!DOCTYPE html>\n<!--[if lt IE 7]> <html class=\"no-js ie6 oldie\" lang=\"en-US\"> <![endif]-->\n<!--[if IE 7]> <html class=\"no-js ie7 oldie\" lang=\"en-US\"> <![endif]-->\n<!--[if IE 8]> <html class=\"no-js ie8 oldie\" lang=\"en-US\"> <![endif]-->\n<!--[if gt IE 8]><!--> <html class=\"no-js\" lang=\"en-US\"> <!--<![endif]-->\n<head>\n\n<title>claudeopus.world | 524: A timeout occurred</title>\n<meta charset=\"UTF-8\" />\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n<meta http-equiv=\"X-UA-Compatible\" content=\"IE=Edge\" />\n<meta name=\"robots\" content=\"noindex, nofollow\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<link rel=\"stylesheet\" id=\"cf_styles-css\" href=\"/cdn-cgi/styles/main.css\" />\n</head>\n<body>\n<div id=\"cf-wrapper\">\n <div id=\"cf-error-details\" class=\"p-0\">\n <header class=\"mx-auto pt-10 lg:pt-6 lg:px-8 w-240 lg:w-full mb-8\">\n \n...[truncated 5868 chars]...\nutton type=\"button\" id=\"cf-footer-ip-reveal\" class=\"cf-footer-ip-reveal-btn\">Click to reveal</button>\n <span class=\"hidden\" id=\"cf-footer-ip\">20.55.14.48</span>\n <span class=\"cf-footer-separator sm:hidden\">•</spa\n...[truncated]"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 94,
"plan_intent": {
"status": "misaligned",
"reason": "主体实现符合 PR-1 的 P0 方向:双轴边界、六境界倍率、200 tick 消耗、活动租约、持久化及新角色/转世重置均已接入,且未引入真元流动或绕开 qi_physics。未完全符合 plan 的原因是正式复活状态转换缺少承诺的契约测试,且 dev 命令树没有实现 plan 锁定的 satiety|hydration 字面量分支。",
"missing": [
"通过真实正式复活路径验证 Nourishment 重置为 80/80、活动窗口清零并立即落盘的状态转换测试",
"将 /nourish set 的 axis 收紧为 Brigadier 字面量分支,并按实际树形 pin 两条命令路径"
]
},
"summary": "P0 核心数值和持久化链路基本完整,六境界与食水常数也符合 plan,未发现灵气守恒问题。但正式复活契约未被测试锁定,命令树契约还把有限枚举实现成了任意字符串,因此不能按 PR 声明验收。",
"findings": [
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1567, 3363-3448, 4424-4548, 4973-5097",
"title": "正式复活重置没有对应的生产路径契约测试",
"evidence": "revive_lifecycle 确实重置 Nourishment 和活动窗口并排队持久化,但新增测试只分别覆盖 NearDeath 自救保持、Reincarnate 重置和 CreateNewCharacter 重置;没有测试执行正式 Revive 后断言 80/80、窗口清零及 SQLite 落盘。plan §4 明确把“正式复活重置”列为饱和测试交付物,PR Body 也声称已补齐。"
},
{
"severity": "major",
"file": "server/src/cmd/dev/nourish.rs",
"line": "20-34",
"title": "命令树将有限 axis 契约实现成任意 string",
"evidence": "plan 锁定的语法是 /nourish set satiety|hydration <value>,实现却注册 argument(\"axis\").with_parser::<String>(),因此 Brigadier 树会接受并向客户端公开任意字符串,合法值只能执行后在 handle_nourish 中拒绝。这不是声明的命令树契约。"
},
{
"severity": "minor",
"file": "server/src/cmd/registry_pin.rs",
"line": "93-96",
"title": "registry pin 固化了错误的通配 axis 路径",
"evidence": "COMMAND_TREE_PATHS 当前只有 nourish set <axis:string> <value:float>,既不能证明 satiety 和 hydration 两个字面量存在,也会把偏离 plan 的树形当成稳定契约。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 93,
"plan_intent": {
"status": "aligned",
"reason": "实现总体符合 PR-1 的 P0 原意:双轴与五段边界、六境界消耗倍率、生产 schedule、持久化、重连保留、NearDeath 保留、新角色/转世重置及 dev 命令均有真实接线,且未提前实现 P1-P4,也未引入真元流动。",
"missing": [
"缺少 plan §4 明确要求的“正式复活重置 80/80”状态转换测试,现有新增测试只覆盖 NearDeath 自救、Reincarnate 和 CreateNewCharacter。",
"缺少对 /nourish show 实际输出内容的 happy-path 契约断言。"
]
},
"summary": "P0 主链已接通,但 PR Body 声称补齐的正式复活测试并未出现在 diff 中,无法锁定 Revive 与 Reincarnate 两条独立生命周期语义。另有一处 dev show 测试只验证只读性,没有验证命令交付物。",
"findings": [
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1570",
"title": "正式复活重置分支没有对应的状态转换测试",
"evidence": "这里在 revive_lifecycle 中重置 Nourishment 和 NourishmentActivityWindow,并排队立即持久化;但新增测试仅在 3363 附近验证 NearDeath 自救保留,在 4424 附近验证 Reincarnate,在 4973 附近验证 CreateNewCharacter。diff 中没有通过正式 Revive action 执行该分支并断言 ECS 与 SQLite 均为 80/80 的测试,与 plan §4 及 PR Body 的“正式复活重置/生命周期饱和测试”声明不符。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "238-253",
"title": "nourish show 测试没有验证任何输出契约",
"evidence": "nourish_show_is_read_only 发送 Show 后只断言 Nourishment 未变化;即使 handle_nourish 不再发送消息、漏掉某一轴、数值错误或 band 错误,该测试仍会通过。命令树测试也只锁定路径,不覆盖 show 的 happy path。"
}
]
}
][
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 97,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:其他 reviewer 的意见未消除首轮核心问题。P0 主体虽已接通,且未发现绕开 qi_physics 的真元流动,但周期消耗仍被实现为可跨重连持久化的玩家个人 200-tick 累计窗口,明确违背 plan 要求的 CombatClock 全局 is_multiple_of(200) sweep。正式 Revive 分支也仍缺 plan 明列的独立状态转换与落盘测试。",
"missing": [
"恢复 CombatClock 全局 200-tick sweep 语义,并删除个人窗口带来的未授权持久化契约;若确需个人窗口,必须先修改 plan 并完成决议",
"通过真实正式 Revive action 验证 Nourishment 重置为 80/80、活动状态清零且 SQLite 立即落盘的独立测试",
"将 /nourish set 的 axis 在 Brigadier 树中收紧为 satiety 和 hydration 两个字面量分支,并更新 registry pin"
]
},
"summary": "维持 REQUEST_CHANGES。Reviewer C、D 对正式复活测试缺口的交叉确认强化了首轮结论;Reviewer B 执行失败不构成有效反证。更关键的是,其他意见没有解释或推翻个人累计窗口与 plan 全局 sweep 的直接冲突,当前实现不能按 PR-1 的 P0 原意验收。 Plan 原意未确认:最终确认:其他 reviewer 的意见未消除首轮核心问题。P0 主体虽已接通,且未发现绕开 qi_physics 的真元流动,但周期消耗仍被实现为可跨重连持久化的玩家个人 200-tick 累计窗口,明确违背 plan 要求的 CombatClock 全局 is_multiple_of(200) sweep。正式 Revive 分支也仍缺 plan 明列的独立状态转换与落盘测试。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "242-272",
"title": "周期消耗使用玩家个人累计窗口,违背全局 CombatClock sweep 契约",
"evidence": "tick_nourishment 每个 distinct tick 向 NourishmentActivityWindow 计数,累计到 200 后才扣减,完全没有 clock.tick.is_multiple_of(200) 门控。658-686 的测试进一步明确锁定:全局 tick 199 加入的玩家直到 tick 398 才消费。plan §1 和“接入面”明确要求仿 shelflife/sweep.rs 以 CombatClock 的 is_multiple_of(200) 做全局 sweep,因此这不是等价实现。该偏移还新增了 nourishment_activity_window 的跨重连持久化 schema,使在线时长累计成为 plan 未决议的新产品语义。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1567",
"title": "正式 Revive 重置和即时落盘缺少独立生产路径测试",
"evidence": "revive_lifecycle 中存在 80/80 重置、活动状态清零和延迟持久化代码,但新增测试只覆盖 NearDeath 自救保留、Reincarnate 和 CreateNewCharacter。没有测试通过正式 Revive action 进入该分支,并同时断言 ECS 与 SQLite。plan §4 和 §8.1 #3 明确将正式复活与新角色/转世定义为两条独立生命周期接线,且在饱和测试清单中单列“正式复活重置”。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "19-34",
"title": "命令树未表达 satiety|hydration 的有限字面量契约",
"evidence": "实现注册的是 argument(\"axis\").with_parser::<String>(),所以客户端命令树接受并补全任意字符串,未知值直到 handler 执行后才被拒绝。server/src/cmd/registry_pin.rs:93 又将错误形状固定为 nourish set <axis:string> <value:float>。plan §4 指定的 Brigadier 语法是 /nourish set satiety|hydration <value>,PR Body 也声明增加了命令树契约。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "238-253",
"title": "nourish show 测试没有锁定命令输出",
"evidence": "nourish_show_is_read_only 只断言 Nourishment 未变化;即使 Show 不再发送消息、遗漏任一轴、显示错误数值或错误 band,该测试仍会通过,未覆盖 dev 命令的 happy-path 交付契约。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "REQUEST_CHANGES",
"confidence": 97,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:P0 主链虽已注册到生产 schedule,并覆盖双轴、境界倍率、持久化和多数生命周期路径,但核心周期调度没有按 plan 明确要求的 CombatClock 全局 200-tick sweep 实现,而是改成了可跨重连持久化的玩家个人累计窗口;正式复活和命令树交付物也未达到 plan 锁定的测试与语法契约。",
"missing": [
"按 clock.tick.is_multiple_of(200) 执行的全局 sweep 语义,以及对应生产 schedule 边界测试",
"通过真实 Revive action 验证 ECS 重置、活动状态清理和 SQLite 即时落盘的契约测试",
"将 satiety 和 hydration 注册为 Brigadier 字面量分支,并分别 pin 命令树路径",
"验证 /nourish show 实际输出双轴值和 band 的 happy-path 测试"
]
},
"summary": "复投后维持 REQUEST_CHANGES。其他 reviewer 指出的正式复活测试和命令树问题成立;此外,周期消耗的实际运行语义与 plan 明确指定的全局 CombatClock sweep 相反,这是核心正确性和 plan 对齐问题,不能以现有个人窗口测试作为替代验收。 Plan 原意未确认:最终确认:P0 主链虽已注册到生产 schedule,并覆盖双轴、境界倍率、持久化和多数生命周期路径,但核心周期调度没有按 plan 明确要求的 CombatClock 全局 200-tick sweep 实现,而是改成了可跨重连持久化的玩家个人累计窗口;正式复活和命令树交付物也未达到 plan 锁定的测试与语法契约。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "242-272, 658-686",
"title": "周期消耗被接成玩家个人累计窗口,违反全局 200-tick sweep 契约",
"evidence": "tick_nourishment 每个不同 tick 给玩家的 NourishmentActivityWindow 加一,累计到 200 后才扣减,完全没有 clock.tick.is_multiple_of(200) 门控。测试还明确锁定玩家在全局 tick 199 加入后直到 tick 398 才首次结算。plan 接入面和 §1 则明确要求仿 shelflife 由 CombatClock 的 is_multiple_of(200) 执行全局 sweep。当前实现还会在 CombatClock 从 1 跳到 200 时只累计两个观察 tick,而不会在 tick 200 结算,进一步证明运行节拍取决于玩家经历的 Update 次数而非计划指定的时钟边界。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1570, 3363-3448, 4424-4548, 4973-5097",
"title": "正式 Revive 重置与即时持久化分支没有独立契约测试",
"evidence": "revive_lifecycle 中确实重置 Nourishment、清理活动窗口并排队持久化,但新增测试只覆盖 NearDeath 自救保留、Reincarnate 和 CreateNewCharacter;没有测试从非默认食水存档执行真实正式 Revive action,再同时断言 ECS 为 80/80、活动状态已清理且 SQLite 已即时更新。plan §4 和 §8.1 #3 明确要求正式复活与新角色/转世是两条独立生命周期接线,并将正式复活测试列为 P0 交付物。"
},
{
"severity": "major",
"file": "server/src/cmd/dev/nourish.rs",
"line": "19-34",
"title": "有限 axis 被注册成任意字符串,命令树未表达计划锁定的两个分支",
"evidence": "assemble_graph 注册的是 <axis:string>,所以任意字符串都能通过 Brigadier 解析并被公开给客户端,直到 handler 执行时才由 NourishmentAxis::parse 拒绝。server/src/cmd/registry_pin.rs:93-96 也只 pin 了 nourish set <axis:string> <value:float>。这不等价于 plan 指定的 /nourish set satiety|hydration <value>,也无法提供正确的客户端补全和解析契约。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "238-253",
"title": "show 测试只验证只读性,没有锁定命令输出",
"evidence": "nourish_show_is_read_only 仅断言组件未变化;即使 Show 不发送消息、遗漏某一轴、显示错误数值或错误 band,该测试仍会通过。PR Body 声称增加 /nourish show dev 命令及命令契约,但当前没有其 happy-path 输出验收。"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:PR 虽已接通双轴、五段边界、六境界倍率、持久化和主要生命周期路径,且未引入真元流动或绕过 qi_physics,但核心周期消耗被改成可跨会话持久化的玩家个人 200-tick 窗口,明确偏离 plan 锁定的 CombatClock 全局 is_multiple_of(200) sweep。正式复活验收测试和 satiety|hydration 字面量命令树契约也仍未交付。",
"missing": [
"按 CombatClock 全局 200-tick 边界执行 sweep,或先修改 plan 并取得对个人窗口及新增持久化 schema 的明确决议",
"通过真实正式 Revive action 验证 80/80 重置、活动状态清零、PlayerRevived 发出及 SQLite 即时落盘",
"将 /nourish set 收紧为 satiety 和 hydration 两个 Brigadier 字面量分支,并分别 pin 命令树路径",
"验证 /nourish show 实际输出双轴数值和 band 的 happy-path 测试"
]
},
"summary": "复投后结论仍为 REQUEST_CHANGES,并新增采纳全局 sweep 偏离这一核心 major。当前实现的数值公式和六境界倍率本身正确,也未发现灵气守恒问题;但周期语义与 plan 相反,且明确承诺的正式复活契约测试和有限命令树契约仍缺失,不能验收。 Plan 原意未确认:最终确认:PR 虽已接通双轴、五段边界、六境界倍率、持久化和主要生命周期路径,且未引入真元流动或绕过 qi_physics,但核心周期消耗被改成可跨会话持久化的玩家个人 200-tick 窗口,明确偏离 plan 锁定的 CombatClock 全局 is_multiple_of(200) sweep。正式复活验收测试和 satiety|hydration 字面量命令树契约也仍未交付。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "242-272, 658-686",
"title": "周期消耗实现为玩家个人累计窗口,违反全局 CombatClock sweep 契约",
"evidence": "tick_nourishment 没有检查 clock.tick.is_multiple_of(200),而是每个玩家累计 200 个 distinct tick 后独立扣减。new_player_at_global_tick_199_accumulates_a_full_personal_window_before_loss 进一步锁定玩家到 tick 398 才首次结算,与 plan §1 要求仿 shelflife 在全局 200-tick 边界 sweep 的语义相反。NourishmentActivityWindow 还因此被加入持久化 schema,而 plan §4 只要求持久化 Nourishment;其 NourishmentSweepCursor 又不持久化,个人窗口与所谓 distinct tick 在跨会话时也不是完整的一致契约。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1567, 3363-3448, 4424-4548, 4973-5097",
"title": "正式复活重置与即时落盘没有生产路径契约测试",
"evidence": "revive_lifecycle 中确实重置 Nourishment、清零活动状态并排队持久化,但新增测试只覆盖 NearDeath 自救保持、Reincarnate 和 CreateNewCharacter。没有测试从非默认状态执行正式 Revive action,同时断言生命周期恢复、PlayerRevived、ECS 80/80、活动状态清零以及 SQLite 即时落盘。plan §4、§8.1 #3 和 PR Body 均将正式复活作为独立交付物。"
},
{
"severity": "major",
"file": "server/src/cmd/dev/nourish.rs",
"line": "19-34",
"title": "有限 axis 契约被实现为任意字符串参数",
"evidence": "plan 锁定的 Brigadier 语法是 /nourish set satiety|hydration <value>,实现却注册 argument(\"axis\").with_parser::<String>()。因此 food 等任意字符串会被命令树接受并暴露给客户端,只在执行 handler 后才拒绝;补全、解析和权限前的命令树契约均未表达两个合法轴。"
},
{
"severity": "minor",
"file": "server/src/cmd/registry_pin.rs",
"line": "93-96",
"title": "registry pin 固化了错误的通配 axis 路径",
"evidence": "COMMAND_TREE_PATHS 只包含 nourish set <axis:string> <value:float>,既不能证明 satiety 与 hydration 两个字面量分支存在,也会将当前偏离 plan 的树形固定为稳定契约。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "238-253",
"title": "nourish show 测试未验证命令输出",
"evidence": "nourish_show_is_read_only 只断言组件未变化;即使 Show 不发送消息、漏掉某一轴、输出错误数值或错误 band,该测试仍会通过,未锁定该 dev 命令的 happy path。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 97,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:主体接线覆盖了 P0 的双轴、边界、境界倍率、生命周期、持久化和 dev 命令,但周期消耗被实现为可跨重连持久化的玩家个人 200-tick 窗口,明确偏离 plan 指定的 CombatClock 全局 is_multiple_of(200) sweep。正式复活和命令树两项声明的交付物也没有被对应契约测试锁定。",
"missing": [
"按 CombatClock 全局 200-tick 边界执行 sweep 的实现与测试,或先通过 plan 决议批准个人窗口及新增持久化契约",
"通过真实 Revive action 验证 ECS 重置 80/80、活动状态清零和 SQLite 即时落盘的状态转换测试",
"将 /nourish set 的 axis 实现为 satiety、hydration 两个 Brigadier 字面量分支,并 pin 实际命令树",
"验证 /nourish show 实际消息内容的 happy-path 测试"
]
},
"summary": "维持 REQUEST_CHANGES,并将 plan 对齐结论调整为 misaligned。个人累计窗口改变了核心消耗节拍,还引入了 plan 未声明的存档 schema 和大量配套复杂度;此外,正式复活验收分支缺测,命令树把有限枚举错误公开成任意字符串。当前不满足 P0 的 plan 原意和测试交付标准。 Plan 原意未确认:最终确认:主体接线覆盖了 P0 的双轴、边界、境界倍率、生命周期、持久化和 dev 命令,但周期消耗被实现为可跨重连持久化的玩家个人 200-tick 窗口,明确偏离 plan 指定的 CombatClock 全局 is_multiple_of(200) sweep。正式复活和命令树两项声明的交付物也没有被对应契约测试锁定。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "242-272",
"title": "周期消耗使用玩家个人累计窗口,偏离全局 CombatClock sweep",
"evidence": "tick_nourishment 没有使用 clock.tick.is_multiple_of(200) 门控,而是每个 distinct tick 为玩家累加 activity_window,累计到 200 后才扣减。658-686 行的测试进一步明确锁定了玩家在全局 tick 199 加入后直到 tick 398 才首次结算的个人窗口语义,与 plan 接入面和 §1 要求仿 shelflife 使用全局 is_multiple_of(N) sweep 相反。NourishmentActivityWindow 的跨重连持久化也是由这一偏离衍生出的未声明 schema。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1567",
"title": "正式复活重置与即时持久化没有生产路径契约测试",
"evidence": "revive_lifecycle 在这里重置 Nourishment、清空活动状态并排队持久化,但新增测试只覆盖 NearDeath 自救、Reincarnate 和 CreateNewCharacter。没有测试从非默认食水存档执行正式 Revive action,并同时断言 ECS 与 SQLite 均变为 80/80 和空活动状态。plan §4、§8.1 #3 及 PR Body 均将正式复活列为独立交付和已补齐的生命周期测试。"
},
{
"severity": "major",
"file": "server/src/cmd/dev/nourish.rs",
"line": "19-34",
"title": "命令树将有限 axis 契约实现为任意字符串",
"evidence": "plan 明确规定 /nourish set satiety|hydration <value>,实现却注册 argument(\"axis\").with_parser::<String>(),使客户端命令树接受并补全任意字符串,只在 handler 执行阶段拒绝非法值。server/src/cmd/registry_pin.rs:93 又将错误树形固化为 nourish set <axis:string> <value:float>,无法证明两个合法字面量分支存在。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "238-253",
"title": "nourish show 测试没有验证输出契约",
"evidence": "nourish_show_is_read_only 仅断言 Nourishment 未变化。即使 Show 不发送消息、漏掉某一轴、显示错误数值或错误 band,该测试仍会通过,因此没有覆盖命令的 happy path 交付物。"
}
]
}
] |
|
/review |
🔭 Review · PR #1259❌ 未通过:0/4,存在 18 项真实 finding,要求修改
📋 Plan 原意确认
🧑⚖️ 复投结果
🔎 Findings
首轮与复投原始结构化摘要[
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 94,
"plan_intent": {
"status": "misaligned",
"reason": "PR 已落地双轴、五带、境界倍率、持久化和命令主干,但周期调度改成了未在 plan 决议中的“玩家个人滚动 200 tick 窗口”,活动 lease 又未真正参与倍率选择;正式复活的重置顺序及验收测试也未满足 §8.1 #3 的明确边界,因此不能确认 PR-1 完整符合 P0 原意。",
"missing": [
"按 CombatClock 每 200 tick 的既定 sweep 门控实现,或先在 plan 中明确批准个人滚动窗口及其重连语义",
"让 20-tick movement lease 真正影响活动倍率,或删除这条无效链路和相关声明",
"正式 Revive 在完整成功并 emit PlayerRevived 后重置的顺序,以及区别于 Reincarnate/CreateNewCharacter 的专门持久化测试"
]
},
"summary": "核心底盘大体存在,但有两处关键语义偏离 plan:周期门控被改为个人窗口,且宣称的 movement lease 对实际消耗倍率没有作用。正式复活还在 PlayerRevived 发送前重置,并缺少 plan 要求的普通 Revive 专项验收,因此请求修改。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "226-254, 1098-1130",
"title": "周期消耗被改成个人滚动窗口,偏离 plan 锁定的 CombatClock 200-tick sweep",
"evidence": "tick_nourishment 每遇到一个新的 clock.tick 就给玩家自己的 activity_window 加 1,直到个人累计 200 次才扣减;测试还明确锁定“global tick 199 加入后仍等待个人完整 200 tick”。关联 plan §1/§4 要求仿 shelflife 使用每 200 CombatClock tick 的 is_multiple_of(N) 门控,并未决议改为按玩家入场时间分相位或持久化部分 sweep。"
},
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "75-92, 185-204, 424-445",
"title": "20-tick movement lease 未接入实际消耗倍率,新增跟踪链路没有功能效果",
"evidence": "classify_activity 会在 lease 内返回 Moving 并增加 move_ticks,但 activity_multiplier 完全不读取 move_ticks,只看 dash_ticks 和由 MovementEvent 直接设置的 had_qualifying_movement。测试 activity_window_multiplier_requires_real_movement_in_its_own_window 进一步锁定 lease 产生的 Moving ticks 仍按 idle ×1.0 结算。因此删除 NourishmentMovementTracker 的 Moving 分支不会改变实际 loss,和 PR Body 所称“MovementEvent 驱动水平移动 20-tick session lease”不符。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1568, 3363-3435, 4425-4555, 4975-5099",
"title": "正式复活重置顺序违反 §8.1 #3,且没有普通 Revive 专项验收",
"evidence": "revive_lifecycle 在 clear_coffin_on_exit 和 revived.send(PlayerRevived) 之前就 reset_to_spawn/插入 80/80;plan §8.1 #3 明确要求 revive_lifecycle 完整成功并 emit PlayerRevived 后处理。新增测试只分别覆盖 NearDeath 自救不重置、Reincarnate 和 CreateNewCharacter 重置,没有对普通 Revive 动作断言 80/80、活动窗口清零及 SQLite 即时持久化。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "APPROVE",
"confidence": 96,
"plan_intent": {
"status": "aligned",
"reason": "实现符合五 PR 拆分中 PR-1 的 P0 原意:Nourishment 已由 lib/main 加载并注册生产 schedule,MovementEvent 有真实消费方,/nourish 同时接入 dev 注册与命令树 pin,饱食/水分及未完成活动窗口已覆盖 join hydration、autosave、disconnect、shutdown、正式复活和新角色/转世路径。未提前引入属于 PR-2 至 PR-4 的生理效果、物品或跨端 schema stub。",
"missing": []
},
"summary": "未发现定义未接入、事件无消费、registry 未加载或生命周期单向 stub。唯一可核验问题是 PR Body 中的定向测试计数与当前 diff 的测试集合不一致,门禁记录需要更新。",
"findings": [
{
"severity": "minor",
"file": "server/src/nourishment/tick.rs",
"line": "286-1210",
"title": "PR Body 的 cargo test nourishment 计数不可能对应当前 diff",
"evidence": "该测试模块本身已有 26 个 #[test],server/src/nourishment/mod.rs:199-465 另有 10 个;Rust 测试过滤会匹配完整模块路径 nourishment::...,因此仅这两个模块就至少应命中 36 个测试,而 PR Body 声称 27 passed。"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 93,
"plan_intent": {
"status": "misaligned",
"reason": "组件、200-tick 消耗、六境界倍率、持久化接线以及各生命周期分支的实现整体符合 PR-1/P0 原意,且未触碰真元或自建 qi 常数;但 plan 明确要求用专门测试 pin“正式复活重置 80/80”这一状态转换,本 diff 只锁了 NearDeath 自救、Reincarnate 和 CreateNewCharacter,没有覆盖普通正式 Revive 的生产调用链及即时持久化。",
"missing": [
"通过 handle_revival_action_intents 的普通正式 Revive 路径验证 Nourishment 重置为 80/80、NourishmentActivityWindow 清零、PlayerRevived 发出并且 SQLite bundle 立即写回",
"验证正式复活失败或未完成时不得提前重置 Nourishment"
]
},
"summary": "P0 主体逻辑和守恒边界基本正确,食水常数来自 CombatClock,未发现真元/灵气绕过 qi_physics。阻断点是正式复活这一关键状态机分支缺少 plan 明确要求的生产路径契约测试,当前测试集合无法证明重置只在成功的正式复活后发生且已即时持久化。",
"findings": [
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1567,3363-3455,4527-4550,5064-5100",
"title": "BLOCKING: 普通正式 Revive 的食水重置与即时持久化没有状态机 pin 测试",
"evidence": "revive_lifecycle 在 1540-1567 重置 Nourishment/ActivityWindow、排队持久化并发送 PlayerRevived;但新增生命周期测试只覆盖 NearDeath 稳定后保持原值、Reincarnate 重置和 CreateNewCharacter 重置。没有测试通过 handle_revival_action_intents 驱动普通正式 Revive,也没有断言该路径成功后 SQLite 为 80/80、失败分支保持原值。plan §4 和 §8.1 #3 明确把“正式复活重置”及其专门测试列为 P0 交付物。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 95,
"plan_intent": {
"status": "misaligned",
"reason": "Nourishment、五段边界、六境界倍率、持久化、生命周期重置和 dev 命令主体均已接线,但周期调度被实现为“每名玩家累计 200 个在线 distinct tick 的个人窗口”,而 plan 明确要求基于 CombatClock 的每 200 tick、仿 is_multiple_of(N) 的生产 sweep。该差异改变了首次加入、复活和重连后的扣减时点,属于阶段契约变更而非实现细节。",
"missing": [
"按 plan 使用 CombatClock 200-tick 全局门控,或先把个人窗口语义写入 plan 决议并重新核定相关持久化及边界契约",
"正式 Revive 路径的 80/80 重置、活动窗口清零和即时持久化生产路径测试"
]
},
"summary": "P0 大部分功能和错误分支测试较完整,但核心 sweep 时序与 plan 不一致,且正式复活这一必测状态转换没有被生产路径测试锁定。另有一套对实际扣减结果无贡献的 20-tick lease/分类计数被持久化,造成明显的实现膨胀。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "112-133, 228-259, 979-1031",
"title": "周期消耗被改成个人 200-tick 窗口,违反 plan 的 CombatClock 全局 sweep 契约",
"evidence": "NourishmentSweepCursor::claim_tick 与 activity_window.total_ticks() 让每个玩家从加入或重置时单独累计 200 个 distinct tick;测试 new_player_at_global_tick_199_accumulates_a_full_personal_window_before_loss 还明确锁定玩家不会在全局 tick 200 结算。plan 接入面则明确要求 CombatClock 节拍并仿 shelflife 的 is_multiple_of(N) 门控,§1 规定每 200 tick 扫一次。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1432-1569, 3363-3455, 4527-4557, 5064-5101",
"title": "正式 Revive 重置没有生产路径测试,P0 必测状态转换未锁定",
"evidence": "revive_lifecycle 中加入了 80/80 重置、活动窗口清零和延迟即时持久化,但新增测试只覆盖 NearDeath 自救不重置、Reincarnate 和 CreateNewCharacter。没有测试通过正式 RevivalAction/handle_revival_action_intents 路径断言 Revive 后 ECS 与 SQLite 同时为默认值;plan §4 明确把“正式复活重置”列为饱和测试交付物。"
},
{
"severity": "minor",
"file": "server/src/nourishment/tick.rs",
"line": "23-102, 162-224",
"title": "20-tick movement lease 和三类 tick 计数没有参与实际活动倍率,形成冗余持久化状态",
"evidence": "activity_multiplier 只检查 dash_ticks > 0 和 had_qualifying_movement;lease 产生的 Moving 只增加 move_ticks,而 move_ticks 本身不会提升倍率。代码和测试甚至明确锁定“lease-derived Moving ticks without a new qualifying event must not elevate a fresh sweep”。因此 idle_ticks/move_ticks 的分类及 NourishmentMovementTracker lease 对最终扣减没有独立影响,却被加入 serde、SQLite、断线、关服和生命周期重置链。"
}
]
}
][
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 97,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:结论不调整。PR 已接通双轴、五带、境界倍率、持久化和命令主干,但核心周期语义被改成玩家个人累计 200 个在线 distinct tick,而 plan 明确锁定 CombatClock 的 is_multiple_of(N) 全局 sweep;正式 Revive 又在 emit PlayerRevived 前重置,且没有 plan 指定的普通 Revive 生产路径与失败分支测试。因此不能确认 PR-1 符合 P0 原意。",
"missing": [
"按 CombatClock 每 200 tick 的全局 is_multiple_of(N) 门控结算,取消未获 plan 决议的个人滚动窗口及其部分窗口持久化;若坚持现语义,应先修改并重新批准 plan",
"正式 Revive 在完整成功并 emit PlayerRevived 后再重置 Nourishment 与活动状态",
"通过 handle_revival_action_intents 的普通 Revive 路径验证成功后的 80/80、窗口清零、PlayerRevived 和 SQLite 即时写回,并验证失败或未完成时不重置",
"删除对结算结果无贡献的 movement lease、idle/move 分类与冗余持久化状态,或让其真正承担 plan 已批准的活动判定语义"
]
},
"summary": "复投后维持 REQUEST_CHANGES。运行接线完整不能抵消核心 sweep 契约被改写;另外,正式 Revive 的明确顺序和专项验收仍未交付。20-tick lease 当前不影响最终倍率,形成与 PR Body 不符的冗余链路。 Plan 原意未确认:最终确认:结论不调整。PR 已接通双轴、五带、境界倍率、持久化和命令主干,但核心周期语义被改成玩家个人累计 200 个在线 distinct tick,而 plan 明确锁定 CombatClock 的 is_multiple_of(N) 全局 sweep;正式 Revive 又在 emit PlayerRevived 前重置,且没有 plan 指定的普通 Revive 生产路径与失败分支测试。因此不能确认 PR-1 符合 P0 原意。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "112-133, 228-259, 1098-1130",
"title": "BLOCKING: 周期消耗实现为玩家个人 200-tick 窗口,违反 CombatClock 全局 sweep 契约",
"evidence": "NourishmentSweepCursor::claim_tick 对每名玩家独立去重,tick_nourishment 每经历一个新 tick 就给该玩家的 activity_window 加一,累计到 200 才扣减。测试 new_player_at_global_tick_199_accumulates_a_full_personal_window_before_loss 进一步锁定玩家在全局 tick 200 不结算,而要等自己的第 200 个在线 tick。plan 接入面明确要求仿 shelflife 使用 CombatClock 的 is_multiple_of(N) 门控,§1 也规定每 200 tick 扫一次;个人分相位、仅累计在线 tick及持久化部分窗口均未获决议。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "943-980, 1540-1568, 3363-3455, 4425-4555, 4975-5099",
"title": "BLOCKING: 正式 Revive 的重置顺序和专项状态机测试未满足 §8.1 #3",
"evidence": "revive_lifecycle 在 1540-1552 先重置或插入 Nourishment 与 NourishmentActivityWindow,随后才清棺、排队持久化并在 1567 发送 PlayerRevived;这与“revive_lifecycle 完整成功并 emit PlayerRevived 后处理”的明确决议相反。新增测试只覆盖 NearDeath 自救、Reincarnate 和 CreateNewCharacter,没有通过 handle_revival_action_intents 驱动普通 Revive,也没有锁定成功后的事件、ECS、SQLite,或失败分支保持原值。"
},
{
"severity": "minor",
"file": "server/src/nourishment/tick.rs",
"line": "23-102, 134-224, 424-445",
"title": "20-tick movement lease 和 move_ticks 不参与最终活动倍率,形成冗余状态链",
"evidence": "classify_activity 会让 lease 内的 tick 记为 Moving,但 activity_multiplier 只读取 dash_ticks 和由合格 MovementEvent 直接设置的 had_qualifying_movement,完全不读取 move_ticks。测试 activity_window_multiplier_requires_real_movement_in_its_own_window 还明确锁定 lease 派生的 Moving tick 仍按 idle 倍率结算。因此移除 NourishmentMovementTracker、lease 和 move_ticks 分类不会改变实际 loss,和 PR Body 所称由 20-tick session lease 驱动活动判定不一致。"
},
{
"severity": "minor",
"file": "server/src/nourishment/tick.rs",
"line": "286-1210",
"title": "PR Body 的定向测试计数与当前测试集合不一致",
"evidence": "tick.rs 的 nourishment 测试模块已有 26 个 #[test],server/src/nourishment/mod.rs:199-465 另有 10 个;cargo test nourishment 会按完整测试路径匹配 nourishment 模块,因此仅这两处就至少应命中 36 个测试,无法对应 PR Body 声称的 27 passed。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "REQUEST_CHANGES",
"confidence": 0,
"plan_intent": {
"status": "unclear",
"reason": "Codex final-B 执行失败",
"missing": []
},
"summary": "Codex final-B 执行失败:exit=503 | stderr: HTTP 503: No available channel for model gpt-5.6-sol (distributor) (request id: 202607250939231175729798268d9d6yvenhPKQ) Plan 原意未确认:Codex final-B 执行失败",
"findings": [
{
"severity": "major",
"file": ".github/scripts/review.mjs",
"line": "0",
"title": "Codex reviewer final-B 执行失败",
"evidence": "exit=503 | stderr: HTTP 503: No available channel for model gpt-5.6-sol (distributor) (request id: 202607250939231175729798268d9d6yvenhPKQ)"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:PR 的双轴、境界倍率、持久化和主要生命周期接线基本正确,也未发现真元绕过 qi_physics;但周期结算被实现为玩家个人累计 200 个在线 tick,而关联 plan 明确锁定 CombatClock 每 200 tick 的全局 sweep。此外,正式 Revive 在 PlayerRevived 发出前即重置,且缺少 plan 指定的生产路径成功、失败及即时持久化测试,因此尚不符合 P0 原意和交付物。",
"missing": [
"按 CombatClock 的全局 200-tick 相位执行 sweep,或先正式修改 plan 决议并重新核定加入、重连、复活和持久化语义",
"将正式 Revive 的 Nourishment 重置放到完整成功并 emit PlayerRevived 之后",
"通过 handle_revival_action_intents 的普通 Revive 生产路径验证成功后的 80/80、活动状态清零、事件发出和 SQLite 即时写回,并验证失败路径不重置",
"删除不影响最终倍率的 20-tick movement lease 和冗余计数,或明确其契约并让其真正参与结算"
]
},
"summary": "复投后维持 REQUEST_CHANGES,并扩大原阻断结论:除正式 Revive 契约测试缺失外,当前个人滚动窗口直接改变了 plan 锁定的 sweep 时序;正式复活的实现顺序也与 §8.1 #3 相反。核心数值公式和灵气守恒边界未见问题,但上述两项均属于 P0 状态机及周期契约的 major 偏差。 Plan 原意未确认:最终确认:PR 的双轴、境界倍率、持久化和主要生命周期接线基本正确,也未发现真元绕过 qi_physics;但周期结算被实现为玩家个人累计 200 个在线 tick,而关联 plan 明确锁定 CombatClock 每 200 tick 的全局 sweep。此外,正式 Revive 在 PlayerRevived 发出前即重置,且缺少 plan 指定的生产路径成功、失败及即时持久化测试,因此尚不符合 P0 原意和交付物。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "112-133, 226-259, 1098-1130",
"title": "BLOCKING: 周期消耗实现成玩家个人 200-tick 窗口,违反 CombatClock 全局 sweep 契约",
"evidence": "NourishmentSweepCursor::claim_tick 只负责每名玩家去重,tick_nourishment 每经历一个新的时钟 tick 就给该玩家的 activity_window 加一,累计到 200 才扣减。测试 new_player_at_global_tick_199_accumulates_a_full_personal_window_before_loss 进一步明确锁定玩家要等到全局 tick 398 才首次结算。plan 接入面和 §1 则明确要求仿 shelflife 使用 CombatClock 的 is_multiple_of(N) 门控、每 200 tick 执行一次 sweep。两者会对首次加入、重连和正式复活后的首次扣减时点产生不同结果,属于可观察契约变更。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1568, 3363-3455, 4425-4555, 4975-5100",
"title": "BLOCKING: 正式 Revive 的重置顺序与必测生产路径均不符合 §8.1 #3",
"evidence": "revive_lifecycle 在 1540-1555 已重置或插入 Nourishment 和 NourishmentActivityWindow,随后才执行 clear_coffin_on_exit、排队持久化并在 1566 emit PlayerRevived;plan 明确要求在 revive_lifecycle 完整成功并 emit PlayerRevived 后处理重置。新增测试只覆盖 NearDeath 自救保持原值、Reincarnate 和 CreateNewCharacter,没有通过 handle_revival_action_intents 驱动普通 Revive,也未验证该路径的 PlayerRevived、ECS 80/80、窗口清零和 SQLite 即时写回,更没有失败或未完成 Revive 不得提前重置的断言。"
},
{
"severity": "minor",
"file": "server/src/nourishment/tick.rs",
"line": "75-102, 162-224, 424-445",
"title": "20-tick movement lease 不影响实际活动倍率,形成冗余状态和持久化复杂度",
"evidence": "classify_activity 会在 lease 有效期内返回 Moving 并增加 move_ticks,但 NourishmentActivityWindow::activity_multiplier 完全不读取 move_ticks,只检查 dash_ticks 和由 MovementEvent 直接设置的 had_qualifying_movement。测试 activity_window_multiplier_requires_real_movement_in_its_own_window 还明确锁定 lease 产生的 Moving tick 仍按 idle 倍率结算。因此 NourishmentMovementTracker、Moving 分类以及大部分 idle_ticks/move_ticks 状态对最终 loss 没有独立贡献,与 PR Body 所称由 20-tick session lease 驱动活动判定不一致。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:主体组件、五段边界、六境界倍率、生产接线和持久化大体完整,但核心周期语义被改成每名玩家累计 200 个在线 distinct tick 的个人滚动窗口,明确背离 plan 锁定的 CombatClock 全局 200-tick sweep;正式 Revive 的重置顺序和专项生产路径测试也仍未满足 §8.1 #3。",
"missing": [
"按 CombatClock 的全局 200-tick 相位门控结算,或先通过 plan 决议明确批准个人滚动窗口及其加入、重连和持久化语义",
"正式 Revive 完整成功并 emit PlayerRevived 后再重置 80/80,并通过生产 RevivalAction 路径验证 ECS、事件和 SQLite",
"删除对实际活动倍率无贡献的 20-tick movement lease 与冗余分类状态,或让其承担经过 plan 批准且可测试的真实语义"
]
},
"summary": "维持 REQUEST_CHANGES。Reviewer B 确认了模块和持久化接线,但这不能消除阶段契约偏差。当前实现用个人在线计数取代全局 CombatClock sweep,直接改变晚加入、复活和重连后的首次扣减时点;普通正式 Revive 又在 PlayerRevived 发送前重置,且没有生产路径成功与失败测试。另有一套被持久化和广泛测试、却不影响最终倍率的 movement lease,增加了明显的维护成本。 Plan 原意未确认:最终确认:主体组件、五段边界、六境界倍率、生产接线和持久化大体完整,但核心周期语义被改成每名玩家累计 200 个在线 distinct tick 的个人滚动窗口,明确背离 plan 锁定的 CombatClock 全局 200-tick sweep;正式 Revive 的重置顺序和专项生产路径测试也仍未满足 §8.1 #3。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/tick.rs",
"line": "112-133, 226-259, 1098-1130",
"title": "BLOCKING: 个人 200-tick 滚动窗口违反 CombatClock 全局 sweep 契约",
"evidence": "NourishmentSweepCursor::claim_tick 配合 activity_window.total_ticks(),要求每名玩家从加入、重置或恢复的窗口起累计 200 个 distinct tick 后才扣减。测试 new_player_at_global_tick_199_accumulates_a_full_personal_window_before_loss 明确锁定玩家不会在全局 tick 200 结算,而要等到自己的第 200 个在线 tick。plan §1 和接入面明确要求仿 shelflife 的 is_multiple_of(N) 门控,每 200 个 CombatClock tick 执行生产 sweep;这不是等价实现,会改变加入、复活和重连后的扣减相位。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1568, 3363-3455, 4425-4557, 4975-5101",
"title": "BLOCKING: 正式 Revive 重置发生在 PlayerRevived 之前,且缺少生产状态机验收",
"evidence": "revive_lifecycle 在 1540-1558 先重置或插入 Nourishment 与 NourishmentActivityWindow,随后才 clear_coffin_on_exit、排队持久化并在 1567 发送 PlayerRevived。plan §8.1 #3 明确要求 revive_lifecycle 完整成功并 emit PlayerRevived 后处理重置。新增测试只覆盖 NearDeath 自救保持原值、Reincarnate 和 CreateNewCharacter,没有通过 handle_revival_action_intents 驱动普通 Revive,也没有验证失败或未完成的正式复活不会提前重置。"
},
{
"severity": "minor",
"file": "server/src/nourishment/tick.rs",
"line": "75-102, 133-224, 424-445",
"title": "20-tick movement lease 不参与最终倍率,形成无效状态和测试膨胀",
"evidence": "classify_activity 会在 lease 有效期内返回 Moving 并累计 move_ticks,但 activity_multiplier 不读取 move_ticks,只读取 dash_ticks 和由 qualifying MovementEvent 直接设置的 had_qualifying_movement。测试 activity_window_multiplier_requires_real_movement_in_its_own_window 进一步锁定仅由 lease 产生的 Moving ticks 仍按 idle 倍率结算。因此移除 NourishmentMovementTracker 的 Moving 分支不会改变任何实际食水扣减,和 PR Body 所称的“20-tick session lease”功能不一致。"
},
{
"severity": "minor",
"file": "server/src/nourishment/tick.rs",
"line": "286-1210",
"title": "PR Body 的定向测试计数与当前测试集合不一致",
"evidence": "tick.rs 测试模块已有约 26 个 #[test],server/src/nourishment/mod.rs:199-465 另有 10 个位于 nourishment 模块路径下;cargo test nourishment 应至少匹配这些测试,而 PR Body 仍声明 27 passed。该记录无法对应当前 diff,不能作为固定 SHA 的可复核门禁证据。"
}
]
}
] |
|
/review |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
server/src/cmd/dev/nourish.rs (1)
92-100: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
/120硬编码,应引用NOURISH_MAX_VALUE。
band_of的分档全部由常量驱动,唯独 show 输出的量程写死。上限一旦调整,dev 命令会给出与实际分档矛盾的读数。♻️ 建议改用常量
NourishCmd::Show => { client.send_chat_message(format!( - "[dev] nourish satiety={:.1}/120 ({:?}) hydration={:.1}/120 ({:?})", + "[dev] nourish satiety={:.1}/{max:.0} ({:?}) hydration={:.1}/{max:.0} ({:?})", nourishment.satiety, band_of(nourishment.satiety), nourishment.hydration, band_of(nourishment.hydration), + max = crate::nourishment::NOURISH_MAX_VALUE, )); }
command_integration_nourish_show_reports_both_axes_without_mutation的期望字符串可保持不变(NOURISH_MAX_VALUE当前为 120.0)。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/cmd/dev/nourish.rs` around lines 92 - 100, Update the NourishCmd::Show output formatting to use NOURISH_MAX_VALUE instead of hardcoding 120 for both satiety and hydration ranges, preserving the existing display format and band_of behavior.server/src/cultivation/mod.rs (1)
620-627: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value日志文案有多余的尾随分隔符。
列表以
nourishment/结尾后紧接),实际输出为...digestion_load/nourishment/), not just the ...。同时 Line 601 与 Line 3093 的注释写「14 个 sibling slice」,但列表中除cultivation外只有 13 项。✏️ 建议修正
- meridian_severed/poison_toxicity/digestion_load/nourishment/\ - ), not just the \ - `cultivation` field", + meridian_severed/poison_toxicity/digestion_load/nourishment\ + ), not just the `cultivation` field",🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/cultivation/mod.rs` around lines 620 - 627, Remove the trailing slash from the final nourishment entry in the persisted cultivation bundle rejection message, and update the sibling-slice count in the comments near the related handling sites to match the actual list: 13 slices excluding cultivation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@server/src/cmd/dev/nourish.rs`:
- Around line 210-225: 在
`command_integration_nourish_set_unknown_literal_does_not_mutate`
附近新增端到端集成测试,使用命令执行路径验证 `nourish set hydration 33` 将 `hydration` 设置为 33.0,同时保持
`satiety` 的默认值不变;测试必须通过 `execute_command` 触发 `assemble_graph`,并覆盖 `satiety` 与
`hydration` literal 到各自 `NourishmentAxis` 的正确绑定。
In `@server/src/nourishment/mod.rs`:
- Around line 414-456: 在该测试创建实体的组件元组中,更新 Cultivation 初始化,使其显式设置 realm 为
Realm::Awaken,而不是直接使用 Cultivation::default();保留其他 Cultivation 默认字段不变,并确保
expected_dash_loss 继续与 Awaken 境界的倍率计算一致。
In `@server/src/nourishment/tick.rs`:
- Around line 370-405: 修改测试
dash_has_priority_and_duplicate_or_regressed_ticks_cannot_pollute,使首次采样与回退 tick
采样的 flags 组合不同,确保回退采样若被接受会改变最终断言,从而真正验证 observe 的单调性守卫;同时将 dash
优先级断言拆分到独立测试,专门覆盖同 tick 内 move+dash 时使用 DASH 倍率。
In `@server/src/persistence/mod.rs`:
- Around line 6221-6230: 更新 run_player_cultivation_bundle_read_hook 及其测试钩子逻辑,将
db_path.canonicalize() 的强制 expect 改为非致命的 ok() 判空处理;路径无法解析时直接退出钩子,不要让 panic
传播到无关并发测试线程,并保留路径可解析时的比较行为。
---
Outside diff comments:
In `@server/src/cmd/dev/nourish.rs`:
- Around line 92-100: Update the NourishCmd::Show output formatting to use
NOURISH_MAX_VALUE instead of hardcoding 120 for both satiety and hydration
ranges, preserving the existing display format and band_of behavior.
In `@server/src/cultivation/mod.rs`:
- Around line 620-627: Remove the trailing slash from the final nourishment
entry in the persisted cultivation bundle rejection message, and update the
sibling-slice count in the comments near the related handling sites to match the
actual list: 13 slices excluding cultivation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 3c1121f8-2d5a-471f-9105-7bf013a781c4
📒 Files selected for processing (8)
server/src/cmd/dev/nourish.rsserver/src/cmd/registry_pin.rsserver/src/combat/lifecycle.rsserver/src/cultivation/mod.rsserver/src/nourishment/mod.rsserver/src/nourishment/tick.rsserver/src/persistence/mod.rsserver/src/player/mod.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: e2e
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
server/**/*.rs: Server Rust 代码提交前必须运行cargo fmt --check、cargo clippy --all-targets -- -D warnings和cargo test。
Dev harness 命令只能用于本地/dev 测试,显式绕过 worldview 修炼规则和 qi ledger 的入口不得复用于生产 gameplay 路径。
Files:
server/src/cmd/registry_pin.rsserver/src/cmd/dev/nourish.rsserver/src/nourishment/tick.rsserver/src/nourishment/mod.rsserver/src/cultivation/mod.rsserver/src/persistence/mod.rsserver/src/player/mod.rsserver/src/combat/lifecycle.rs
server/src/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
server/src/**/*.rs: 所有真元/灵气流动必须通过qi_physics::ledger::QiTransfer { from, to, amount, reason },不得直接增减账户或区域数值。
测试灵气总量时必须引用SPIRIT_QI_TOTAL,不得硬编码100。
离屏战斗死亡必须调用release_dormant_qi_to_zone并通过ledger.transfer(ReleaseToZone)归还真元。
新增衰减、抽取或衰减率常数前必须检查并复用qi_physics;不存在时先扩展其 constants,禁止在业务 plan 中重复定义。
天道时代衰减必须使用qi_physics::tiandao::era_decay_step,并通过WorldQiBudget::apply_era_decay和assert_conservation追踪沉降槽。
不得使用 armor stand 或 invisible mob 作为碰撞箱/交互载体;实体必须使用 Marker+自定义渲染,交互通过 C2S 请求处理。
headless/CI 启服必须设置BONG_SKIP_SKIN_PREFETCH=1,避免缺少MINESKIN_API_KEY导致 panic。
Files:
server/src/cmd/registry_pin.rsserver/src/cmd/dev/nourish.rsserver/src/nourishment/tick.rsserver/src/nourishment/mod.rsserver/src/cultivation/mod.rsserver/src/persistence/mod.rsserver/src/player/mod.rsserver/src/combat/lifecycle.rs
server/src/**/*.{rs,json}
📄 CodeRabbit inference engine (CLAUDE.md)
server/src/**/*.{rs,json}: 新增 zone 前必须核对docs/worldview.md区域表和server/zones.json中已有 ID。
ItemCategory只能使用 Pill、Herb、Scroll、Misc、Weapon、Armor、Tool、Treasure、RecipeFragment、RecipeHint、BoneCoin、Container;炼丹材料使用Misc,不得使用Material。
Files:
server/src/cmd/registry_pin.rsserver/src/cmd/dev/nourish.rsserver/src/nourishment/tick.rsserver/src/nourishment/mod.rsserver/src/cultivation/mod.rsserver/src/persistence/mod.rsserver/src/player/mod.rsserver/src/combat/lifecycle.rs
**/*.{rs,ts,tsx,java,py}
📄 CodeRabbit inference engine (CLAUDE.md)
新增 skill/cast/主动能力必须同时提供独立 animation、particle/VFX、SFX、HUD 反馈和 hotbar/SkillBar PNG icon;仅实现 server 或 schema 不算完成。
Files:
server/src/cmd/registry_pin.rsserver/src/cmd/dev/nourish.rsserver/src/nourishment/tick.rsserver/src/nourishment/mod.rsserver/src/cultivation/mod.rsserver/src/persistence/mod.rsserver/src/player/mod.rsserver/src/combat/lifecycle.rs
**/*.{rs,ts,tsx,java}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{rs,ts,tsx,java}: 六境界必须使用“醒灵→引气→凝脉→固元→通灵→化虚”,不得使用练气、筑基、金丹、元婴等旧称。
命名不得使用末法禁词玄、陨、星、仙、太、古,除明确允许的俗世矿名例外。
Files:
server/src/cmd/registry_pin.rsserver/src/cmd/dev/nourish.rsserver/src/nourishment/tick.rsserver/src/nourishment/mod.rsserver/src/cultivation/mod.rsserver/src/persistence/mod.rsserver/src/player/mod.rsserver/src/combat/lifecycle.rs
**/*.{rs,ts,tsx,java,json,md}
📄 CodeRabbit inference engine (CLAUDE.md)
唯一真货币是骨币;矿物是交易筹码,灵石是燃料/衰变物,金银不是货币。
Files:
server/src/cmd/registry_pin.rsserver/src/cmd/dev/nourish.rsserver/src/nourishment/tick.rsserver/src/nourishment/mod.rsserver/src/cultivation/mod.rsserver/src/persistence/mod.rsserver/src/player/mod.rsserver/src/combat/lifecycle.rs
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
**/*: 禁止git stash push后不执行对应git stash pop;不得留下孤儿 WIP stash。
每个逻辑单元使用中文 atomic commit;agent 产生的每个 commit 必须包含真实模型 ID 的Model:trailer。
未经明确确认不得执行 force push、hard reset、amend、交互式 rebase、批量删除/移动文件或依赖版本/生产配置修改;严禁--no-verify、--no-gpg-sign及关闭签名。
PR review 只能通过独立评论/review触发;不得等待 Codex,review 修改后必须重新等待 re-review。
Files:
server/src/cmd/registry_pin.rsserver/src/cmd/dev/nourish.rsserver/src/nourishment/tick.rsserver/src/nourishment/mod.rsserver/src/cultivation/mod.rsserver/src/persistence/mod.rsserver/src/player/mod.rsserver/src/combat/lifecycle.rs
🔇 Additional comments (29)
server/src/cmd/dev/nourish.rs (4)
53-66:/nourish仍无 dev 权限门控。
handle_nourish只以「执行者是否挂了Nourishment组件」作为可用性判据,任何在线玩家都能用/nourish set直写饱食/水分并绕过周期消耗。该问题在上一轮 review 中已提出且未见处置。按 coding guidelines「Dev harness 命令只能用于本地/dev 测试,显式绕过 worldview 修炼规则和 qi ledger 的入口不得复用于生产 gameplay 路径」,此入口需要权限白名单或 dev/local 开关。
As per coding guidelines: "Dev harness 命令只能用于本地/dev 测试,显式绕过 worldview 修炼规则和 qi ledger 的入口不得复用于生产 gameplay 路径。"
Source: Coding guidelines
10-46: LGTM!
67-91: LGTM!Also applies to: 101-103
106-209: LGTM!Also applies to: 226-353
server/src/nourishment/mod.rs (4)
18-18: LGTM!
191-192: LGTM!Also applies to: 194-209
211-222: LGTM!Also applies to: 389-413, 457-491, 535-539
193-193: 🚀 Performance & Scalability无需修改
add_event::<MovementEvent>()。在 Bevy 0.14.2 中该调用对已存在的
Events<MovementEvent>资源是幂等的,不会因 valence 已注册而产生重复事件清理。server/src/nourishment/tick.rs (7)
20-64: LGTM!
66-84: LGTM!
86-95: LGTM!
97-129: LGTM!
160-169: LGTM!
176-368: LGTM!
145-159: 🎯 Functional Correctness无需修改:死亡/待复活期间的周期扣减符合
plan-satiety-hydration-v1的“自然消耗 + 复活重置”设计,NearDeath 稳定后也保留原有值而非复活重置。server/src/cmd/registry_pin.rs (1)
96-98: LGTM!Also applies to: 186-186
server/src/persistence/mod.rs (2)
6254-6331: LGTM!
10403-10483: LGTM!Also applies to: 10486-10639
server/src/player/mod.rs (2)
394-415: LGTM!Also applies to: 529-550, 682-704
1126-1168: LGTM!Also applies to: 1250-1335, 1411-1477
server/src/cultivation/mod.rs (3)
668-668: LGTM!Also applies to: 942-978, 1003-1009
1848-1887: LGTM!Also applies to: 1931-1954, 2048-2067, 2842-2984, 3159-3186, 3280-3293
752-759: 🎯 Functional Correctness无需修改
Nourishment反序列化行为。
Nourishment已通过自定义Deserialize逐字段解析:缺失/非法轴回填NOURISH_SPAWN_VALUE,非法浮点数拒绝并还原,有限越界值会被clamp(MIN, MAX)裁剪,因此测试期望与实现一致。server/src/combat/lifecycle.rs (6)
1606-1611: bundle 不完整分支仍在用warn!,建议降级为debug!。
revive_lifecycle/reset_for_new_character都会被只挂Lifecycle的 NPC 与测试实体走到,此时缺少Username/Cultivation属预期状态,warn!会持续刷噪音并淹没下面 1636 行真正的持久化失败告警。
121-141: LGTM!
1543-1554: LGTM!Also applies to: 1566-1566
1938-1949: LGTM!Also applies to: 2018-2018
753-768: LGTM!Also applies to: 3754-3834
2396-2463: LGTM!Also applies to: 2644-2808, 3836-3909, 4983-5003, 5512-5545
🔭 Review · PR #1259❌ 未通过:0/4,存在 25 项真实 finding,要求修改
📋 Plan 原意确认
🧑⚖️ 复投结果
🔎 Findings
首轮与复投原始结构化摘要[
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 96,
"plan_intent": {
"status": "misaligned",
"reason": "主体实现覆盖了 P0 的双轴组件、五段边界、六境界倍率、全局 sweep、活动窗口、持久化入口和 dev 命令,但正式复活的事件顺序明确违反 §8.1 #3,生命周期重置的独立落盘也仍允许 ECS、事件与 SQLite 半提交;此外,生产 schedule 没有把 join hydration 纳入全局边界结算的显式顺序。",
"missing": [
"按 plan 锁定 PlayerRevived emit 后再执行 80/80 与活动窗口重置",
"把 nourishment 生命周期重置纳入可传播失败并阻止 ECS/event 提交的原子事务",
"显式保证 join hydration 的组件插入及 deferred flush 先于同 tick 全局 sweep"
]
},
"summary": "P0 大部分交付物已落地,但三个生命周期/调度契约尚未闭合。尤其当前 deferred 落盘失败只记日志,仍可能公告复活成功并保留 ECS 80/80,同时 SQLite 留在旧值,不能通过本阶段验收。",
"findings": [
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1569",
"title": "正式复活在 PlayerRevived 之前重置,顺序与 plan 决议相反",
"evidence": "代码先执行 nourishment.reset_to_spawn() 和 nourishment_activity.reset(),随后才调用 revived.send(PlayerRevived { entity });而 plan §8.1 #3 明确要求 revive_lifecycle 完整成功并 emit PlayerRevived 后处理重置。EventWriter 不同步调用 reader 并不能改变该源码顺序;先 send、再在本 system 返回前重置,reader 仍会看到最终状态。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1563-1633, 1935-2015",
"title": "生命周期重置落盘是 best-effort,SQLite 失败仍会产生半提交",
"evidence": "revive_lifecycle/reset_for_new_character 先修改 ECS,再通过 Commands.add 延迟调用 persist_player_cultivation_bundle_with_nourishment;闭包遇到任何错误仅 tracing::warn 后返回,无法让调用方回滚,revive_lifecycle 仍会发送 PlayerRevived。由此可出现 ECS 已重置为 80/80、事件已公告成功,但 SQLite 仍保存旧饱食/水分的状态,直接违背 PR Body 声称的 ECS、SQLite、PlayerRevived 不发生半提交。"
},
{
"severity": "major",
"file": "server/src/nourishment/mod.rs",
"line": "149-166, 333-345",
"title": "全局 sweep 未显式等待 join hydration,新上线玩家可能漏掉边界且永不追债",
"evidence": "生产链只约束 tick_combat_clock、两个 movement system、sample_activity、tick_nourishment 和 revival intent,没有对 attach_cultivation_to_joined_clients 建立 before/after 边;后者通过 Commands 插入 Cultivation/Nourishment。tick_nourishment 的 query 同时要求这两个组件,因此边界 Update 中 hydration 尚未执行或尚未 flush 的在线 Client 会被跳过,而 gate 已认领该边界且设计上不追债。现有 production schedule 测试预先手工插入了 Cultivation、Nourishment 和 NourishmentActivityWindow,没有覆盖真实 join 加载路径。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "REQUEST_CHANGES",
"confidence": 97,
"plan_intent": {
"status": "misaligned",
"reason": "主体范围确为 PR-1/P0,组件、全局 sweep、命令注册、join/autosave/disconnect/shutdown 等多数接线已落地;但生产调度没有完整建立 plan 声明的时钟与接入顺序,正式复活/新角色的 nourishment 落盘仍是失败后仅告警的异步旁路,且 PlayerRevived 与 reset 的顺序直接违背 active plan §8.1 #3。",
"missing": [
"显式锁定 CombatClock advance → accepted movement/dash → activity sample → sweep 的完整生产调度边",
"确保真实 join hydration 在边界 sweep claim 前完成并可被查询",
"将正式复活和新角色的 80/80 重置纳入同步、失败可中止或回滚的持久化提交",
"同步解决 PlayerRevived 与 nourishment reset 相对顺序的 plan/代码漂移"
]
},
"summary": "P0 的定义和大部分加载路径已经接入,但仍有关键运行断链:调度图没有真正锁住 clock→movement,也没有锁住 join hydration→sweep;生命周期重置则可能在 SQLite 未更新时照常提交 ECS 和 PlayerRevived。当前不能通过。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/mod.rs",
"line": "197-214",
"title": "生产调度没有真正建立 CombatClock → movement/dash 的顺序边",
"evidence": "这里建立的是 tick_combat_clock → nourishment chain,以及 movement handlers → sample_activity;图中没有 tick_combat_clock → handle_movement_action_intents/tick_movement_actions 的边。因此两个 movement system 仍可先读取旧 CombatClock,再由 clock writer 推进时钟。现有测试也拆成了“真实 clock writer 但无 movement register”和“真实 movement register 但无 clock writer”,没有覆盖 199→200 时依赖新 tick 才能接受的 movement/dash。"
},
{
"severity": "major",
"file": "server/src/nourishment/mod.rs",
"line": "197-214",
"title": "真实 join hydration 未排序到全局 sweep 之前,边界加入者可被永久漏结算",
"evidence": "nourishment chain 没有 after(crate::cultivation::attach_cultivation_to_joined_clients);其中的 apply_deferred 也不能保证 cultivation attach 已先运行。tick_nourishment 在 server/src/nourishment/tick.rs:151-178 先 claim 全局 boundary,再只遍历同时具有 Nourishment、Cultivation 和 Client 的实体。若 join-time EntityCommands 尚未应用,该玩家会被查询排除,而全局 gate 已 claim,当前边界不会重试。所谓 late-join 测试是手工预挂完整组件,并未走真实 join hydration。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1637, 1935-2015",
"title": "复活和新角色的 nourishment 重置仍可形成 ECS/event 已提交、SQLite 未提交的半状态",
"evidence": "revive_lifecycle 先直接重置 Nourishment 和 activity,随后仅把 queue_nourishment_cultivation_bundle_persist 放入 deferred command,再发送 PlayerRevived 并返回 true。该 closure 遇到 bundle 组件不全会直接 warn+return,SQLite 写失败也只 warn;无法撤销此前 ECS reset 或事件。reset_for_new_character 同样先完成状态重置,再排队这个失败仅告警的写入。新增 SQLite open 失败测试只覆盖前置 revival persistence 失败,未覆盖这个重置后的第二阶段 bundle 写入失败。"
},
{
"severity": "minor",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1566",
"title": "正式复活的 reset/event 顺序与 active plan §8.1 #3 相反",
"evidence": "docs/plan-satiety-hydration-v1.md:179-188 锁定“完整成功并 emit PlayerRevived 后”重置;代码却先 reset_to_spawn/插入默认组件,再在末尾 revived.send。PR Body 虽说明不采纳该顺序,但本 PR 同时更新的 active plan 并未修改对应决议,形成代码与生效 plan 的双真相。"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 95,
"plan_intent": {
"status": "misaligned",
"reason": "整体范围和数值模型符合 PR-1/P0 原意,但未兑现 plan 锁定的“边界 tick 全服在线玩家统一结算”和生命周期重置持久化完整性:同 tick 加入水合没有调度依赖,正式复活及新角色重置又通过可失败且不回滚的第二次延迟写盘完成。",
"missing": [
"真实 join hydration 与 200-tick sweep 的显式先后关系及生产路径边界测试",
"Nourishment 生命周期重置与 SQLite、ECS、PlayerRevived/新角色成功结果之间的原子提交"
]
},
"summary": "发现两个 major。全局 sweep 可在 join hydration 的 deferred insert 之前抢占边界并永久跳过该玩家;复活和新角色则会先提交 ECS/事件,再异步尝试 Nourishment bundle 写盘,失败仅告警,仍存在半提交。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/mod.rs",
"line": "154-172",
"title": "同一边界加入的在线玩家可能在水合前被 sweep 永久跳过",
"evidence": "生产 schedule 只把 nourishment 链排在 CombatClock、movement 之后以及 revival 之前,没有排在 `attach_cultivation_to_joined_clients` 之后。后者通过 deferred commands 插入 `Cultivation` 和 `Nourishment`,而 `tick_nourishment` 在 `server/src/nourishment/tick.rs:145-164` 先全局 claim 边界,再查询同时具有 `NourishmentActivityWindow + Nourishment + Cultivation` 的 Client;若 join hydration 尚未 apply,该在线玩家被排除,且全局 gate 已认领该 tick,不会补结算。现有 late-join 测试均预先直接插入这些组件,没有覆盖真实 join attach 路径。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1632, 1935-2016",
"title": "生命周期重置的 Nourishment 写盘不是原子的,失败后仍会宣告成功",
"evidence": "`revive_lifecycle` 先把 ECS 重置为 80/80,再通过 `commands.add` 排队执行独立的 cultivation bundle 写盘,随后立即 `revived.send(PlayerRevived { entity })`。该延迟写盘会重新打开 SQLite,失败分支仅记录 warning,不回滚 ECS 或撤销事件。`reset_for_new_character` 也采用同一模式。因此即使前置 revival transition 已成功,第二次 open/transaction 仍可能失败,形成 ECS=80/80、SQLite=旧值且 PlayerRevived 已发出的半提交;PR 中的 SQLite 失败测试只覆盖前置 transition 失败,未覆盖这次独立写入失败。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 94,
"plan_intent": {
"status": "misaligned",
"reason": "双轴模型、边界、境界倍率、命令和主要持久化入口基本符合 P0 原意,但正式复活的 nourishment 落盘并未与 ECS 重置及 PlayerRevived 原子提交;同时真实 join hydration 与全局 sweep 缺少确定调度关系,无法保证计划要求的同一边界全服统一结算。",
"missing": [
"正式复活成功链中,nourishment SQLite 写入失败时不得留下 ECS 已重置、PlayerRevived 已发送而存档仍为旧值的半提交。",
"真实 joined-client hydration 必须确定发生在边界 activity sample/global sweep 之前,并用生产 attach 路径锁定边界行为。"
]
},
"summary": "发现两个契约级问题:复活后的 nourishment 持久化是失败仅告警的延迟写,原子性声明不成立;join hydration 与 sweep 无显式顺序,边界玩家可能被漏结算或把活动旗标带到下一窗口。需要修改后再审。",
"findings": [
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1637",
"title": "正式复活的 nourishment 重置、SQLite 与 PlayerRevived 不是原子提交",
"evidence": "revive_lifecycle 先把 Nourishment 重置为 80/80,再通过 commands.add 排队执行 queue_nourishment_cultivation_bundle_persist,随后立即 revived.send(PlayerRevived { entity })。延迟闭包中的 persist_player_cultivation_bundle_with_nourishment 失败只 tracing::warn 并返回,不会撤销 ECS 重置或阻止事件。因此,只要前置 revival transition 成功而这次 bundle upsert 失败,SQLite 会保留旧食水值,但 ECS 已是 80/80 且 PlayerRevived 已发布。现有 SQLite open 失败测试是在首个持久化入口就失败,没有覆盖前置提交成功、延迟 nourishment 写失败这一分支。"
},
{
"severity": "major",
"file": "server/src/nourishment/mod.rs",
"line": "166-184(结合 server/src/nourishment/tick.rs:145-174)",
"title": "真实 join hydration 未排在全局 sweep 前,边界结算具有调度不确定性",
"evidence": "nourishment::register 只把链排在 tick_combat_clock、两个 movement system 之后,没有与 cultivation::attach_cultivation_to_joined_clients 建立顺序。tick_nourishment 又要求实体已同时具有 Nourishment 和 Cultivation,而 join hydration 通过 Commands 延迟插入这些组件。注册先后不构成 Bevy 调度顺序,所以边界 Update 中 hydration 可能在 sweep 后才生效,在线新玩家会漏掉该次全局结算;sample_activity 却可先记录其活动,导致旗标留到下一边界。现有 late-join 和 production schedule 测试都手工预插入 Cultivation/Nourishment,没有走真实 attach 路径,未锁定该断链。"
}
]
}
][
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:PR 已覆盖 P0 的主要类型、数值、全局 sweep、活动窗口、持久化入口和 dev 命令,但仍违反 active plan 锁定的正式复活顺序,并存在生命周期半提交、真实 join 边界漏结算以及 CombatClock 与 movement 未完整排序四个契约级问题,不能视为符合 plan 原意和交付声明。",
"missing": [
"按 §8.1 #3 在正式复活完整成功并 emit PlayerRevived 后执行 80/80 与活动窗口重置",
"将正式复活和新角色的 nourishment 重置纳入失败可传播的持久化事务,禁止延迟闭包仅告警",
"显式保证真实 join hydration 及其 deferred flush 先于同 tick activity sample/global sweep",
"显式建立 tick_combat_clock → movement/dash 的生产调度边,并以真实 199→200 接受条件测试锁定"
]
},
"summary": "复投后维持 REQUEST_CHANGES,并新增确认运行接线中的时钟排序 major。四位 reviewer 对生命周期原子性和 join hydration 断链的证据一致,且 diff 本身可直接验证;PR Body 所称的完整生产顺序与 ECS、SQLite、PlayerRevived 原子性目前均未由实现兑现。 Plan 原意未确认:最终确认:PR 已覆盖 P0 的主要类型、数值、全局 sweep、活动窗口、持久化入口和 dev 命令,但仍违反 active plan 锁定的正式复活顺序,并存在生命周期半提交、真实 join 边界漏结算以及 CombatClock 与 movement 未完整排序四个契约级问题,不能视为符合 plan 原意和交付声明。",
"findings": [
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1569",
"title": "正式复活的 reset 与 PlayerRevived 顺序直接违反 active plan",
"evidence": "`revive_lifecycle` 先调用 `nourishment.reset_to_spawn()` 并重置或插入 `NourishmentActivityWindow`,最后才执行 `revived.send(PlayerRevived { entity })`。`docs/plan-satiety-hydration-v1.md` §8.1 #3 明确锁定“完整成功并 emit PlayerRevived 后处理”重置。EventWriter 不同步调用 reader,只能说明同 system 内先 send 再 reset 仍可让后续 reader 看到最终状态,不能把当前相反的源码顺序变成符合 plan。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1563-1633, 1935-2015",
"title": "生命周期 nourishment 落盘失败仅告警,仍会形成 ECS、事件与 SQLite 半提交",
"evidence": "正式复活和新角色路径均先修改 ECS,再通过 `commands.add` 调用 `queue_nourishment_cultivation_bundle_persist`。闭包遇到 bundle 组件不全或 SQLite 写入失败只记录 warning,错误无法返回给生命周期调用方,也无法撤销 ECS 重置或阻止 `PlayerRevived`。因此可出现 ECS 已为 80/80、正式复活事件已发布,但 SQLite 仍保存旧食水值。现有 SQLite open 失败测试失败在前置 `persist_revival_transition`,没有覆盖该前置事务成功、第二次 bundle 写入失败的分支。"
},
{
"severity": "major",
"file": "server/src/nourishment/mod.rs",
"line": "191-209",
"title": "真实 join hydration 未排序到全局 sweep 前,边界在线玩家可能永久漏结算",
"evidence": "nourishment 生产链没有对 `crate::cultivation::attach_cultivation_to_joined_clients` 建立 before/after 关系。join hydration 通过 deferred Commands 插入 `Cultivation` 和 `Nourishment`,而 `tick_nourishment` 在 `server/src/nourishment/tick.rs:151-178` 先全局 claim boundary,再查询同时具有这些组件的 Client。若 attach 尚未执行或尚未 flush,该玩家会被 query 排除,但全局 gate 已认领该边界且不会追债。现有 late-join 测试均手工预插组件,没有覆盖真实 attach 路径。"
},
{
"severity": "major",
"file": "server/src/nourishment/mod.rs",
"line": "197-207",
"title": "调度图没有建立 CombatClock → movement/dash 的完整顺序",
"evidence": "整个 nourishment chain 被排在 `tick_combat_clock` 之后,`sample_activity` 又被排在两个 movement system 之后,但这只形成 `clock → sample` 与 `movement → sample`,没有形成 `clock → handle_movement_action_intents/tick_movement_actions`。两个 movement system 与 clock writer 对 `CombatClock` 的读写冲突只能保证串行,不能保证 writer 先运行,因此 movement/dash 仍可能按旧 tick 判定。现有测试分别覆盖真实 clock writer 或真实 movement register,没有在同一 production schedule 中锁定依赖新 tick 才能接受的 199→200 movement/dash。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:PR-1 的双轴模型、全局 sweep、join/autosave/disconnect/shutdown、dev 命令等主体接线已经落地,但生产调度仍未真正锁定 plan 声明的 CombatClock → accepted movement/dash 顺序,也未保证真实 join hydration 在边界 sweep claim 前完成;正式复活和新角色的 80/80 重置仍通过失败仅告警的延迟写盘提交,无法兑现 ECS、SQLite、PlayerRevived 不发生半提交的契约。此外,代码与 active plan §8.1 #3 的事件/reset 顺序仍不一致。",
"missing": [
"显式建立 CombatClock advance → movement intent/tick → activity sample → global sweep 的完整生产调度边",
"显式建立 joined-client cultivation/nourishment hydration → deferred flush → activity sample/sweep 的顺序",
"把正式复活及新角色的 nourishment 重置纳入失败可传播、可中止或可回滚的持久化提交",
"统一 active plan 与实现中的 PlayerRevived/reset 相对顺序,并以事件读取侧测试锁定契约"
]
},
"summary": "维持首轮 REQUEST_CHANGES。其他 reviewer 的意见没有消除首轮风险,反而从 plan、正确性和测试角度交叉确认了 join hydration 与生命周期原子性问题;独立复核后,CombatClock 与 movement 之间缺少真实顺序边的问题也仍成立。当前存在三个 major 和一个 plan 漂移 minor,不能通过。 Plan 原意未确认:最终确认:PR-1 的双轴模型、全局 sweep、join/autosave/disconnect/shutdown、dev 命令等主体接线已经落地,但生产调度仍未真正锁定 plan 声明的 CombatClock → accepted movement/dash 顺序,也未保证真实 join hydration 在边界 sweep claim 前完成;正式复活和新角色的 80/80 重置仍通过失败仅告警的延迟写盘提交,无法兑现 ECS、SQLite、PlayerRevived 不发生半提交的契约。此外,代码与 active plan §8.1 #3 的事件/reset 顺序仍不一致。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/mod.rs",
"line": "197-214",
"title": "BLOCKING: 调度链没有建立 CombatClock → movement/dash 的生产顺序",
"evidence": "整个 nourishment tuple 被排在 tick_combat_clock 之后,sample_activity 又分别排在 handle_movement_action_intents 和 tick_movement_actions 之后;这只形成 clock → sample 与 movement → sample,两者不能推出 clock → movement。两个 movement system 仍可能先读取旧 CombatClock,再由 clock writer 推进时钟。现有 production_schedule_ticks_clock_before_sampling_and_settles_boundary_activity_once 注册了真实 clock writer 但没有 movement::register;production_schedule_counts_dash_intent_as_dash_on_its_starting_tick 注册了真实 movement systems 但没有 clock writer,因而没有覆盖 199→200 时 movement/dash 必须读取新 tick 的完整生产图。"
},
{
"severity": "major",
"file": "server/src/nourishment/mod.rs",
"line": "197-214",
"title": "BLOCKING: 真实 join hydration 未排序到全局 sweep claim 之前",
"evidence": "nourishment 调度没有依赖 cultivation::attach_cultivation_to_joined_clients。该 join system 通过 Commands 延迟插入 Cultivation、Nourishment 和活动窗口,而当前链中的 apply_deferred 只由 attach_activity_window 的 chain 顺序约束,不能保证 cultivation attach 已先执行。tick_nourishment 在 server/src/nourishment/tick.rs:145-178 先全局 claim 当前 boundary,再只查询同时具有 Nourishment、NourishmentActivityWindow、Cultivation 和 Client 的实体;如果 join hydration 尚未执行或尚未 flush,该在线玩家会被排除,而 gate 已认领边界且不会重试。现有 late-join 测试均手工预插完整组件,没有经过真实 attach 路径。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1637, 1935-2015",
"title": "BLOCKING: 生命周期 nourishment 重置仍可造成 ECS/event 与 SQLite 半提交",
"evidence": "revive_lifecycle 先直接把 Nourishment 和活动窗口重置,再用 commands.add 排队执行 queue_nourishment_cultivation_bundle_persist,随后发送 PlayerRevived 并返回成功。延迟闭包若发现 bundle 组件不全会直接 warn 并返回,SQLite open、transaction 或 upsert 失败也只 warn,无法撤销 ECS 重置或阻止事件。reset_for_new_character 使用同一模式。因此可出现 ECS 已是 80/80、正式复活事件已发布或新角色状态已提交,但 SQLite 仍保存旧食水值。新增 SQLite open 失败测试只让前置 persist_revival_transition 失败,没有覆盖前置 transition 成功、第二次延迟 nourishment bundle 写入失败的分支。"
},
{
"severity": "minor",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1569",
"title": "正式复活的 reset/event 顺序与 active plan §8.1 #3 相反",
"evidence": "代码先执行 nourishment.reset_to_spawn 或插入默认组件,再调用 revived.send(PlayerRevived { entity });docs/plan-satiety-hydration-v1.md:179-188 明确写的是 revive_lifecycle 完整成功并 emit PlayerRevived 后处理重置。EventWriter 不同步调用 reader只能说明当前代码可能具有合理运行语义,不能让 PR Body 中的说明覆盖仍处于 Active 状态的 plan 决议,当前形成了实现与计划两个契约源。"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:双轴模型、数值边界、境界倍率和主要持久化入口基本符合 PR-1/P0,但生产调度没有兑现 plan 声明的 clock→movement→sample→sweep 及 join hydration→sweep 顺序;生命周期重置仍以失败仅告警的第二次延迟写盘提交,并且正式复活的 reset/event 源码顺序直接违背 active plan §8.1 #3。",
"missing": [
"建立 CombatClock writer 到两个 movement system 的直接生产调度边,并用真实 movement register 锁定 199→200 边界",
"保证真实 join hydration 的组件插入和 deferred flush 在全局 boundary claim 之前完成",
"把正式复活及新角色的 Nourishment 重置纳入失败可传播、不会产生 ECS/SQLite/event 半提交的事务",
"按 active plan §8.1 #3 锁定 PlayerRevived 与正式复活重置的顺序,或先正式修订 plan 决议"
]
},
"summary": "维持 REQUEST_CHANGES。首轮两个 major 均成立;结合其他 reviewer 的调度核查,另确认生产图缺少 CombatClock→movement 的直接边,以及正式复活顺序与 active plan 决议冲突。现有测试分别覆盖时钟 writer 或 movement register,没有覆盖完整生产链;SQLite 失败测试也只命中前置 revival transition,未覆盖重置后的独立 bundle 写入失败。 Plan 原意未确认:最终确认:双轴模型、数值边界、境界倍率和主要持久化入口基本符合 PR-1/P0,但生产调度没有兑现 plan 声明的 clock→movement→sample→sweep 及 join hydration→sweep 顺序;生命周期重置仍以失败仅告警的第二次延迟写盘提交,并且正式复活的 reset/event 源码顺序直接违背 active plan §8.1 #3。",
"findings": [
{
"severity": "major",
"file": "server/src/nourishment/mod.rs",
"line": "197-214",
"title": "生产图未建立 CombatClock 到 movement/dash 的直接顺序边",
"evidence": "nourishment 链整体排在 tick_combat_clock 之后,sample_activity 又排在 handle_movement_action_intents 和 tick_movement_actions 之后,但这只形成 clock→sample 与 movement→sample 两组汇合边,没有形成 clock→movement。两个 movement system 仍可能先读取旧 CombatClock,再由 writer 将 199 推进到 200。现有 production_schedule_ticks_clock_before_sampling 测试没有注册真实 movement systems,而 production_schedule_counts_dash_intent 测试没有注册真实 clock writer,因而未锁定 PR Body 声明的完整生产顺序。"
},
{
"severity": "major",
"file": "server/src/nourishment/mod.rs",
"line": "197-214",
"title": "真实 join hydration 未排在全局 sweep 前,边界玩家可能被永久漏结算",
"evidence": "该 schedule 没有与 cultivation::attach_cultivation_to_joined_clients 建立顺序。join hydration 通过 Commands 延迟插入 Cultivation、Nourishment 和活动窗口;当前链中的 apply_deferred 只有在 attach system 已先运行时才能冲刷这些命令。tick_nourishment 在 server/src/nourishment/tick.rs:151-178 先 claim 全局 boundary,再查询同时具有 Nourishment、NourishmentActivityWindow、Cultivation 和 Client 的实体。如果 attach 尚未运行或尚未 flush,新上线玩家会被排除,而全局 gate 已认领该 tick且设计上不追债。现有 late-join 测试均手工预插入完整组件,没有经过真实 join attach。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1633, 1935-2016",
"title": "生命周期重置写盘失败仅告警,仍可留下 ECS、SQLite 和事件半提交",
"evidence": "revive_lifecycle 先直接把 Nourishment 重置为 80/80并清空活动窗口,再通过 commands.add 排队调用 persist_player_cultivation_bundle_with_nourishment,随后立即发送 PlayerRevived 并返回 true。延迟闭包遇到 bundle 组件不全或 SQLite 写入失败只 tracing::warn 后返回,无法回滚 ECS 或撤销事件。reset_for_new_character 采用相同模式。因此前置 revival transition 成功、第二次 bundle open/transaction 失败时,会形成 ECS=80/80、SQLite=旧值且 PlayerRevived 已发布的半状态。新增 SQLite open 失败测试只让前置持久化入口失败,没有覆盖前置提交成功而该延迟写失败的分支。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1569",
"title": "正式复活的 reset/event 顺序与 active plan §8.1 #3 相反",
"evidence": "代码在 revived.send(PlayerRevived { entity }) 之前执行 nourishment.reset_to_spawn 和活动窗口重置;docs/plan-satiety-hydration-v1.md:179-188 则明确规定 revive_lifecycle 完整成功并 emit PlayerRevived 后处理重置。EventWriter 不同步调用 reader,只能说明 reader 最终看到重置后的 ECS,不能消除源码与生效 plan 的顺序冲突。PR Body 中单方面声明“不采纳”也不能覆盖同一 PR 仍标为 Active 且明确要求以 §8.1 为准的 plan。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:主体模型、持久化入口和命令契约基本符合 P0,但关键生产链仍未兑现 plan 原意。正式复活和新角色的 nourishment 重置通过失败仅告警的第二次 deferred 写盘完成,无法保证 ECS、SQLite 与 PlayerRevived 不半提交;真实 join hydration 未排序到全局 sweep 前;重新核查 Reviewer B 的意见后,确认 CombatClock 到 movement/dash 之间也确实缺少直接调度边。代码还与 active plan §8.1 #3 锁定的 PlayerRevived/reset 顺序相反。",
"missing": [
"将正式复活和新角色的 80/80 nourishment 写入纳入生命周期成功事务,失败时不得提交 ECS 状态或 PlayerRevived",
"显式建立 attach_cultivation_to_joined_clients hydration及其 deferred flush → activity sample → global sweep 的生产顺序",
"显式建立 tick_combat_clock → handle_movement_action_intents/tick_movement_actions 的直接调度边",
"按 active plan 更新 PlayerRevived 与 nourishment reset 的源码顺序,或先正式修改 plan 决议,不能仅在 PR Body 中建立另一套契约",
"增加覆盖真实 join attach、真实 clock writer、真实 movement register及第二阶段持久化失败的生产路径测试"
]
},
"summary": "复投后维持 REQUEST_CHANGES,并新增确认一个首轮遗漏的调度 major:当前图只有 clock→nourishment 和 movement→sample,不能推出 clock→movement。连同 join hydration 漏扫和生命周期第二次 best-effort 写盘,PR Body 声称的完整生产顺序及原子性矩阵均未被实现或测试锁定。 Plan 原意未确认:最终确认:主体模型、持久化入口和命令契约基本符合 P0,但关键生产链仍未兑现 plan 原意。正式复活和新角色的 nourishment 重置通过失败仅告警的第二次 deferred 写盘完成,无法保证 ECS、SQLite 与 PlayerRevived 不半提交;真实 join hydration 未排序到全局 sweep 前;重新核查 Reviewer B 的意见后,确认 CombatClock 到 movement/dash 之间也确实缺少直接调度边。代码还与 active plan §8.1 #3 锁定的 PlayerRevived/reset 顺序相反。",
"findings": [
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1637, 1935-2015",
"title": "生命周期的 nourishment 重置、SQLite 写入与成功事件仍可半提交",
"evidence": "revive_lifecycle 先直接把 Nourishment 和活动窗口重置,再通过 commands.add 排队 queue_nourishment_cultivation_bundle_persist,随后立即发送 PlayerRevived。闭包发现 cultivation bundle 组件不全会直接 warn 后返回,SQLite open、事务或 upsert 失败也只 warn,无法回滚 ECS 或阻止事件。reset_for_new_character 使用同一旁路。因此可出现 ECS=80/80、PlayerRevived 已发布而 SQLite 仍为旧值;现有 reincarnate_persistence_failure 测试只让前置 persist_revival_transition 打开数据库失败,没有覆盖前置事务成功、这次 deferred bundle 写入失败的分支。"
},
{
"severity": "major",
"file": "server/src/nourishment/mod.rs",
"line": "197-214",
"title": "真实 join hydration 未排在全局 sweep 前,边界玩家可能被永久漏结算",
"evidence": "nourishment::register 的 chain 没有 after(crate::cultivation::attach_cultivation_to_joined_clients)。join hydration 在 server/src/cultivation/mod.rs:597-1007 通过 EntityCommands 延迟插入 Cultivation、Nourishment 和活动窗口,而 tick_nourishment 在 server/src/nourishment/tick.rs:145-174 先全局 claim 当前边界,再查询同时具有这些组件的 Client。若 attach 尚未执行或 deferred insert 尚未 flush,该在线玩家会被排除,但全局 gate 已认领边界且不会追债。现有 late-join 与 production schedule 测试均手工预插完整组件,没有经过真实 attach 路径。"
},
{
"severity": "major",
"file": "server/src/nourishment/mod.rs",
"line": "197-214",
"title": "CombatClock 到 movement/dash 之间没有直接调度边,完整生产顺序声明不成立",
"evidence": "整个 nourishment chain 被标记为 after(tick_combat_clock),sample_activity 又 after 两个 movement system;这只能得到 clock→sample 和 movement→sample,不能推出 clock→movement。handle_movement_action_intents 与 tick_movement_actions 都读取 CombatClock,因此 scheduler 虽会因资源冲突串行化 clock writer 和 reader,但两者相对顺序未定义,movement 仍可能读取推进前的 tick。测试也拆成了两半:production_schedule_ticks_clock_before_sampling_and_settles_boundary_activity_once 注册真实 clock writer但未注册 movement;production_schedule_counts_dash_intent_as_dash_on_its_starting_tick 注册真实 movement但没有真实 clock writer,未锁定 PR Body 声称的 clock→accepted movement/dash 契约。"
},
{
"severity": "minor",
"file": "server/src/combat/lifecycle.rs",
"line": "1540-1569",
"title": "PlayerRevived 与 nourishment reset 的源码顺序仍与 active plan 相反",
"evidence": "代码先执行 reset_to_spawn/reset,再 revived.send;docs/plan-satiety-hydration-v1.md:179-188 的 §8.1 #3 明确锁定 revive_lifecycle 完整成功并 emit PlayerRevived 后处理重置。EventWriter 不同步调用 reader只能说明同一 system 内读者观察结果可能等价,不能让代码自动符合仍处于 Active 状态的 plan。PR Body 的“未采纳”说明也不能覆盖仓库内生效的 plan 决议。"
}
]
}
] |
|
补充提交元数据说明:历史 commit 本轮新 HEAD: |
|
/review |
🔭 Review · PR #1259❌ 未通过:0/4,存在 23 项真实 finding,要求修改
📋 Plan 原意确认
🧑⚖️ 复投结果
🔎 Findings
首轮与复投原始结构化摘要[
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 91,
"plan_intent": {
"status": "misaligned",
"reason": "双轴、周期结算、持久化和 dev 命令总体对应 PR-1/P0,但正式复活路径违反了 §8.1 #3 明确规定的 PlayerRevived 与重置顺序,并额外引入了 plan 未授权的非饱食组件复活资格门。",
"missing": [
"按 plan 锁定正式复活成功、发送 PlayerRevived、再重置 80/80 的顺序,或先通过独立 plan 决议正式修改该契约",
"移除或单独论证正式复活对 QiColor、Karma、PracticeLog、InsightQuota、UnlockedPerceptions、InsightModifiers、MeridianSeveredPermanent 的新增强制依赖"
]
},
"summary": "PR-1 的主要 P0 交付物已接线,但正式复活契约存在两处可核验偏离。当前实现既颠倒 plan 明文规定的事件与重置顺序,又扩大了复活拒绝条件,因此不能按关联 plan 验收。",
"findings": [
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1693-1712",
"title": "正式复活在发送 PlayerRevived 前就重置双轴,违反 §8.1 #3 的顺序契约",
"evidence": "代码先调用 nourishment.reset_to_spawn() 或插入 spawn_default,再清理活动窗口,最后才执行 revived.send(PlayerRevived { entity });plan 明确要求“完整成功并 emit PlayerRevived 后处理”。PR Body 也明确承认未采纳该顺序,而关联 plan 并未同步改变此决议。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1008-1034",
"title": "P0 为正式复活新增七个非饱食组件的强制资格门",
"evidence": "survived 分支要求 QiColor、Karma、PracticeLog、InsightQuota、UnlockedPerceptions、InsightModifiers、MeridianSeveredPermanent 全部为 Some,否则直接 continue 拒绝复活。plan P0 只要求正式复活成功后重置并持久化 Nourishment,没有授权因这些 sibling slice 缺失而改变既有复活资格边界。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "REQUEST_CHANGES",
"confidence": 91,
"plan_intent": {
"status": "misaligned",
"reason": "双轴、周期结算、持久化和命令接线总体符合 P0 原意,但正式复活/新角色被声明为原子生命周期转换,当前成功路径仍允许在运行时依赖不完整时先提交 SQLite、随后留下缺组件或错误维度的 ECS 半状态。",
"missing": [
"在持久化提交前完整校验新角色和复活所需的 ECS 组件及 DimensionLayers,或为所有缺失组件提供确定的插入路径",
"增加运行时依赖缺失时不改 SQLite、不发 PlayerRevived、不隐藏死亡界面的拒绝测试"
]
},
"summary": "P0 主链已接入,但生命周期原子性门并未覆盖全部运行时依赖。当前可出现 SQLite 已提交且操作返回成功,而实体仍缺少核心组件或留在旧维度的半提交状态。",
"findings": [
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1528-1760, 1946-2193",
"title": "复活和新生事务会在运行时依赖缺失时提交半状态",
"evidence": "reset_for_new_character 在 persist_new_character_transition 成功后,对 LifeRecord、DeathRegistry、PlayerState、Position、Wounds、Stamina、CombatState 等仅使用 if let Some 覆写,缺失时既不拒绝也不插入,最后仍返回 true,调用方随即隐藏死亡界面。publish_overworld_runtime 在 DimensionLayers 缺失时直接 return,但事务已把位置和维度持久化为 Overworld;复活路径同样未把 DimensionLayers 纳入提交前 gate。因此查询中合法的 Option 缺失状态可导致 SQLite 已切换新生、ECS 却缺核心切片或仍处于旧维度。"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 91,
"plan_intent": {
"status": "aligned",
"reason": "实现方向符合 PR-1 的 P0 原意:双轴底盘、周期结算、活动窗口、持久化、正式复活与新角色重置、dev 命令均有对应落点;可见改动未新增真元流动,也未引入 qi_physics 之外的灵气常数。",
"missing": []
},
"summary": "P0 主体方向正确,但生命周期改造仍有两条可验证的原子性缺口:新角色事务可在 ECS 必需切片缺失时提交,化虚复活的配额释放也游离于主事务之外。两者都可能造成 SQLite 与运行时状态不一致。",
"findings": [
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1946-2195",
"title": "新角色事务可在必需 ECS 切片缺失时提交,违反半提交原子性声明",
"evidence": "reset_for_new_character 在 persist_new_character_transition 成功后才发布 ECS,但 life_record、death_registry、player_state、position、wounds、stamina、combat_state 都是 Option;后续多个分支仅在 Some 时覆写,缺失时既不拒绝也不 insert。由于上游 NearDeathPersistenceQueryItem 同样把这些切片声明为 Option,合法进入 CreateNewCharacter gate 的实体可以先把 SQLite 切成新角色并返回 true,却继续缺少或保留不完整的运行时核心状态。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1600-1655",
"title": "化虚复活的境界降级与飞升配额释放不在同一事务",
"evidence": "persist_revival_transition_with_bundle 已先提交降级后的 cultivation、life record、nourishment 和玩家切片;随后才单独调用 release_ascension_quota_slot。该调用失败时只记录 warn,函数仍继续修改 ECS、清棺并发送 PlayerRevived。因此 SQLite open/写入失败可留下“玩家已不在化虚,但全局配额仍被占用”的状态,阻塞后续飞升,且无法由主事务回滚。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "97-104",
"title": "命令输出绕过集中常数硬编码上限 120",
"evidence": "nourish show 的两处显示直接写成 /120,而双轴上限属于 nourishment 模块的模型常数。后续按计划校准或调整模型常数时,命令输出与实际 clamp 上限可能漂移,现有测试又用同一硬编码字符串锁住了漂移。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 92,
"plan_intent": {
"status": "misaligned",
"reason": "P0 的 Nourishment 组件、命令和生命周期持久化方向与 plan 基本一致,但正式复活的状态转换顺序明确违背 §8.1 #3:plan 要求 emit PlayerRevived 后再重置 80/80,代码却先重置 Nourishment 和活动窗口、清理棺材运行时状态,最后才发送事件。此外,PR-1 引入了多项未由本 plan 授权的新角色生命周期语义。",
"missing": [
"按 active plan 对齐 PlayerRevived 与 Nourishment reset 的顺序,或先正式修改 §8.1 决议并补对应状态转换测试",
"提供本次新增教程、断脉、毒性、消化、维度与棺材事务语义的上游契约,或将其移出 Nourishment PR-1",
"所给 diff 在 cultivation/mod.rs 中途截断,无法从材料核验 nourishment/mod.rs、nourishment/tick.rs 及 autosave/disconnect/shutdown 的核心实现与完整测试"
]
},
"summary": "请求修改。可见实现存在一处明确的 plan 状态转换违约,并把 Nourishment P0 扩展成了跨教程、毒性、断脉、维度和棺材的生命周期重写;现有测试主要锁定实现选择,不能替代这些选择所缺失的 plan 契约。",
"findings": [
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1690-1715",
"title": "正式复活在 PlayerRevived 发送前重置 Nourishment,顺序与 active plan 相反",
"evidence": "revive_lifecycle 先执行 nourishment.reset_to_spawn()、重置 NourishmentActivityWindow,并调用 clear_coffin_runtime,之后才执行 revived.send(PlayerRevived { entity })。plan §8.1 #3 明确规定“完整成功并 emit PlayerRevived 后处理”。PR Body 也明确承认未采纳该顺序,因此不是材料不明,而是已知契约偏离。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1946-2205",
"title": "Nourishment PR-1 无契约地扩张了新角色生命周期重置范围",
"evidence": "reset_for_new_character 除新增 fresh_nourishment 外,还新建并持久化 MeridianSeveredPermanent、PoisonToxicity、DigestionLoad、TutorialState,修改 TutorialTelemetry,并发布 Overworld layer/visibility、清理 coffin runtime。关联 plan 的 PR-1 交付物只要求 Nourishment P0、持久化和复活/新角色的 80/80 重置,没有授权这些独立 gameplay 状态的重定义。"
},
{
"severity": "minor",
"file": "server/src/combat/lifecycle.rs",
"line": "约4280-4295",
"title": "边界顺序测试注释引用错误章节且重复叙述断言",
"evidence": "nourishment_sweep_runs_before_revival_reset_on_the_same_boundary_tick 的长注释写“plan-satiety-hydration-v1 §5”,但全局 200-tick sweep 属于 §1/§4,§5 是 P2 物品迁移;后续多行又完整复述了测试输入和最终断言。"
}
]
}
][
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 96,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:P0 双轴、周期结算、持久化和 dev 命令总体对应 plan,但正式复活明确违反 §8.1 #3 的事件与重置顺序,并擅自扩大复活资格条件;此外,新角色事务可在运行时核心切片或 DimensionLayers 缺失时先提交 SQLite,再返回成功并留下 ECS 半状态,不满足 plan 声明的生命周期交付质量。",
"missing": [
"按 §8.1 #3 在正式复活完整成功并 emit PlayerRevived 后重置 Nourishment,或先通过独立 plan 决议正式修改该契约",
"移除正式复活对七个非 Nourishment sibling slice 的新增强制资格门,或提供经 plan 审议的生命周期契约与迁移保证",
"在新角色持久化提交前验证全部必需 ECS 组件和 DimensionLayers,或为缺失组件提供确定的插入路径",
"补充运行时依赖缺失时 SQLite、ECS、死亡界面及事件均不发生半提交的测试"
]
},
"summary": "维持 REQUEST_CHANGES。其他 reviewer 的意见进一步确认了生命周期原子性缺口,但不改变首轮核心判断:实现者不能以 Bevy 事件异步消费为由单方面推翻 active plan 的明文顺序,也不能在 Nourishment P0 中无契约扩大正式复活拒绝条件。新角色成功路径另有可验证的 SQLite/ECS 半提交风险。 Plan 原意未确认:最终确认:P0 双轴、周期结算、持久化和 dev 命令总体对应 plan,但正式复活明确违反 §8.1 #3 的事件与重置顺序,并擅自扩大复活资格条件;此外,新角色事务可在运行时核心切片或 DimensionLayers 缺失时先提交 SQLite,再返回成功并留下 ECS 半状态,不满足 plan 声明的生命周期交付质量。",
"findings": [
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1693-1712",
"title": "正式复活在发送 PlayerRevived 前重置 Nourishment,违反 active plan 的明文顺序",
"evidence": "成功路径先调用 nourishment.reset_to_spawn() 或插入 spawn_default,随后重置 NourishmentActivityWindow、清理棺材运行时状态,最后才执行 revived.send(PlayerRevived { entity })。§8.1 #3 明确要求 revive_lifecycle 完整成功并 emit PlayerRevived 后处理 80/80 重置。EventWriter::send 不同步调用 reader 只能说明当前实现的运行时观察方式,不能修改已经锁定的 plan 状态转换契约;PR Body 也明确承认有意未采纳该顺序。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1008-1034",
"title": "正式复活新增七个非饱食组件的强制资格门,扩大了 plan 未授权的拒绝边界",
"evidence": "survived 分支要求 QiColor、Karma、PracticeLog、InsightQuota、UnlockedPerceptions、InsightModifiers、MeridianSeveredPermanent 全部存在,任一缺失就 continue,正式复活不发生。此前这些组件不是复活资格条件;plan P0 只要求正式复活成功后重置并持久化 Nourishment,没有规定缺少任一 sibling slice 即剥夺复活资格。Query 将这些字段声明为 Option,也证明缺失状态在系统边界上可进入,而非由类型保证不可达。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1946-2195",
"title": "新角色事务可在运行时依赖缺失时提交 SQLite 并返回成功,留下 ECS 半状态",
"evidence": "reset_for_new_character 在 persist_new_character_transition 成功后才发布 ECS;LifeRecord、DeathRegistry、PlayerState、Position、Wounds、Stamina、CombatState 等仍是 Option,缺失时多数分支既不拒绝也不插入。publish_overworld_runtime 在 DimensionLayers 缺失时直接 return,但调用方最终仍返回 true,随后隐藏死亡和终结界面。于是合法的 Option 缺失输入可把 SQLite 切换为新角色和 Overworld,而实体仍缺核心组件或停留在旧维度。现有 precommit failpoint 测试只覆盖数据库提交失败,未覆盖提交前运行时依赖缺失。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "REQUEST_CHANGES",
"confidence": 96,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:双轴模型、周期结算、持久化和 dev 命令主链接线基本符合 P0,但正式复活顺序明确违反 §8.1 #3,且复活与新角色转换仍可在 SQLite 提交后留下不完整 ECS 或错误维度;PR 声明的生命周期原子性交付物尚未成立。",
"missing": [
"按 active plan 对齐 PlayerRevived 与 Nourishment 重置顺序,或先通过独立 plan 决议修改该契约",
"在事务提交前校验全部必需 ECS 组件和 DimensionLayers,或为缺失组件提供确定的插入路径",
"增加运行时依赖缺失时不改 SQLite、不发 PlayerRevived、不隐藏死亡界面的拒绝测试",
"将化虚降境与飞升配额释放纳入同一 SQLite 事务"
]
},
"summary": "维持 REQUEST_CHANGES。其他 reviewer 的意见没有推翻首轮运行接线问题;复核后还确认了正式复活顺序违约和化虚配额事务断裂。当前成功路径可能对外宣告完成,但 SQLite、ECS、维度层或全局飞升配额仍处于不一致状态。 Plan 原意未确认:最终确认:双轴模型、周期结算、持久化和 dev 命令主链接线基本符合 P0,但正式复活顺序明确违反 §8.1 #3,且复活与新角色转换仍可在 SQLite 提交后留下不完整 ECS 或错误维度;PR 声明的生命周期原子性交付物尚未成立。",
"findings": [
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1528-1760, 1946-2193",
"title": "复活和新角色事务可在运行时依赖缺失时提交半状态",
"evidence": "reset_for_new_character 在 persist_new_character_transition 成功后才发布 ECS,但 LifeRecord、DeathRegistry、PlayerState、Position、Wounds、Stamina、CombatState 都是 Option;缺失时多数分支既不拒绝也不 insert,函数仍返回 true,调用方随后隐藏死亡界面。revive_lifecycle 对 Wounds、Stamina、CombatState 同样仅在 Some 时重置。publish_overworld_runtime 在 DimensionLayers 缺失时直接 return,而事务已经把位置和维度持久化为 Overworld。由于查询合法接受这些缺失状态,可出现 SQLite 已提交而实体仍缺核心组件、未恢复生命状态或仍停留在旧维度的半提交。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1693-1712",
"title": "正式复活在发送 PlayerRevived 前重置双轴,违反 active plan 的顺序契约",
"evidence": "成功路径先执行 Nourishment::reset_to_spawn 或插入 spawn_default,再重置 NourishmentActivityWindow、清理棺材运行时状态,最后才发送 PlayerRevived。plan §8.1 #3 明确要求 revive_lifecycle 完整成功并 emit PlayerRevived 后处理 80/80 重置;PR Body 也明确承认未采纳该顺序,但没有同步修改 active plan 决议。EventWriter::send 为异步队列并不能使实现自动符合相反的书面状态转换顺序。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1600-1655",
"title": "化虚复活的降境提交与飞升配额释放不在同一事务",
"evidence": "persist_revival_transition_with_bundle 先提交降境后的 Cultivation、LifeRecord、Nourishment 和玩家切片;随后才单独调用 release_ascension_quota_slot。后者失败时仅记录 warn,流程仍继续更新 ECS、清棺并发送 PlayerRevived。因此第二次 SQLite open 或写入失败会留下玩家已不在化虚、但全局 ascension quota 仍被占用的不一致状态,并可能永久阻塞后续飞升。"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 96,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:P0 双轴模型、周期结算、持久化和 dev 命令的主体方向符合 plan,但正式复活实现明确违反 §8.1 #3 的 PlayerRevived 与 80/80 重置顺序;同时,PR 声明的生命周期原子性没有覆盖 ECS 核心切片、DimensionLayers 和化虚飞升配额,因此尚未达到 plan 所要求的完整交付。未发现新增真元流动或绕过 qi_physics ledger 的灵气守恒问题。",
"missing": [
"按 §8.1 #3 实现正式复活成功并 emit PlayerRevived 后再处理 Nourishment 重置,或先通过独立 plan 决议正式修改该契约",
"在 SQLite 提交前验证所有必需 ECS 组件和 DimensionLayers,或为缺失组件提供确定的插入路径,并补缺失依赖拒绝测试",
"将化虚境界降级与飞升配额释放纳入同一 SQLite 事务"
]
},
"summary": "维持 REQUEST_CHANGES,并将 plan 判断调整为 misaligned。其他 reviewer 指出的正式复活顺序偏离可由 active plan 和代码直接核验;首轮发现的两项原子性问题仍成立。当前成功路径可能在 SQLite 已提交后留下不完整 ECS、错误维度或未释放的飞升配额,不能通过。 Plan 原意未确认:最终确认:P0 双轴模型、周期结算、持久化和 dev 命令的主体方向符合 plan,但正式复活实现明确违反 §8.1 #3 的 PlayerRevived 与 80/80 重置顺序;同时,PR 声明的生命周期原子性没有覆盖 ECS 核心切片、DimensionLayers 和化虚飞升配额,因此尚未达到 plan 所要求的完整交付。未发现新增真元流动或绕过 qi_physics ledger 的灵气守恒问题。",
"findings": [
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1693-1712",
"title": "正式复活在 PlayerRevived 前完成双轴重置,违反 active plan 的顺序契约",
"evidence": "revive_lifecycle 先重置或插入 Nourishment、清空 NourishmentActivityWindow,并清理 coffin runtime,最后才调用 revived.send(PlayerRevived { entity });staged_nourishment 也已在此前的持久化事务中写成 80/80。plan §8.1 #3 明确要求正式复活完整成功并 emit PlayerRevived 后处理重置。PR Body 对 Bevy 事件队列语义的解释不能替代对 active plan 的正式修改。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1528-1760, 1946-2195",
"title": "复活和新角色事务可在运行时依赖缺失时提交 SQLite 半状态",
"evidence": "两条路径都先提交持久化事务,再发布 ECS。publish_overworld_runtime 在 DimensionLayers 缺失时直接 return,因此 SQLite 可以已经记录 Overworld 和新位置,而实体仍留在旧维度。reset_for_new_character 对 LifeRecord、DeathRegistry、PlayerState、Position、Wounds、Stamina、CombatState 等 Option 只在 Some 时覆写,缺失时既不拒绝也不插入,之后仍返回 true并隐藏死亡界面;revive_lifecycle 对 Wounds、Stamina、CombatState、PlayerState 和维度资源也存在同类缺口。NearDeathPersistenceQueryItem 明确允许这些状态合法地以 None 进入系统。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1600-1655",
"title": "化虚复活的境界降级与飞升配额释放不在同一事务",
"evidence": "persist_revival_transition_with_bundle 已先提交降级后的 Cultivation、LifeRecord、Nourishment 和玩家切片,之后才单独调用 release_ascension_quota_slot。释放失败只记录 warn,函数仍继续发布 ECS、清棺并发送 PlayerRevived。由此可形成玩家已不再处于化虚境界,但全局 occupied_slots 仍被占用的持久化状态,阻塞后续飞升且无法由前一事务回滚。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "97-104",
"title": "nourish show 绕过模型常数硬编码上限 120",
"evidence": "两轴输出直接使用 /120,而实际范围和 clamp 上限由 nourishment 模块定义。后续按 P4 同步校准常数时,命令显示及其字符串测试可能与模型真实上限漂移。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 96,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:P0 双轴模型、周期结算、持久化和 dev 命令总体符合方向,但正式复活明确违反本 PR 同时写入 active plan 的事件顺序;生命周期事务还引入了未完整校验的运行时依赖、额外复活资格门和半提交路径。现有测试主要覆盖 SQLite precommit 失败,未锁定运行时组件或资源缺失后的原子拒绝,因此不能按 plan 验收。",
"missing": [
"按 §8.1 #3 在正式复活完整成功并 emit PlayerRevived 后重置 Nourishment,或先通过独立 plan 决议修改该契约",
"在 SQLite 提交前验证新角色和复活所需的全部 ECS 组件及 DimensionLayers,或为每个缺失组件提供确定的插入路径",
"补充运行时依赖缺失时 SQLite、ECS、死亡界面、棺材状态和 PlayerRevived 均不发生半提交的测试",
"移除或独立论证正式复活新增的非 Nourishment 组件资格门",
"将化虚降级与飞升配额释放纳入同一事务,覆盖释放失败分支"
]
},
"summary": "维持 REQUEST_CHANGES,并吸收其他 reviewer 提出的可核验原子性问题。当前不仅存在明确的 plan 顺序违约,还可能在 SQLite 已提交后留下缺组件或错误维度的 ECS 状态;化虚复活也可能成功降级却未释放全局配额。这些都是 major,现有 happy path 和 precommit failpoint 测试不能覆盖。 Plan 原意未确认:最终确认:P0 双轴模型、周期结算、持久化和 dev 命令总体符合方向,但正式复活明确违反本 PR 同时写入 active plan 的事件顺序;生命周期事务还引入了未完整校验的运行时依赖、额外复活资格门和半提交路径。现有测试主要覆盖 SQLite precommit 失败,未锁定运行时组件或资源缺失后的原子拒绝,因此不能按 plan 验收。",
"findings": [
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1690-1715",
"title": "正式复活在发送 PlayerRevived 前重置 Nourishment,违反 active plan 的顺序契约",
"evidence": "revive_lifecycle 先执行 nourishment.reset_to_spawn() 或插入 spawn_default,再重置 NourishmentActivityWindow、清理棺材运行时状态,最后才 revived.send(PlayerRevived { entity })。plan §8.1 #3 明确要求正式复活完整成功并 emit PlayerRevived 后处理重置;PR Body 也明确承认有意不采用该顺序。Bevy 事件异步消费并不能授权实现反转 plan 已锁定的状态转换顺序。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1946-2193",
"title": "新角色事务可在核心 ECS 切片或 DimensionLayers 缺失时提交半状态",
"evidence": "persist_new_character_transition 成功后,LifeRecord、DeathRegistry、PlayerState、Position、Wounds、Stamina、CombatState 等仍只用 if let Some 覆写,缺失时既不拒绝也不插入,但函数最终返回 true,调用方随即隐藏死亡和终结界面。publish_overworld_runtime 在 DimensionLayers 缺失时直接 return,而 SQLite 已持久化 Overworld 维度和新位置。NearDeathPersistenceQueryItem 将这些依赖声明为 Option,因此这是查询允许的真实分支,不只是类型上不可达的测试状态。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1008-1034",
"title": "正式复活新增七个非 Nourishment 组件的强制资格门",
"evidence": "survived 分支要求 QiColor、Karma、PracticeLog、InsightQuota、UnlockedPerceptions、InsightModifiers 和 MeridianSeveredPermanent 全部存在,否则直接 continue 拒绝复活。此前正式复活的必要切片边界更小,关联 plan 的 P0 只要求 Nourishment 在成功复活后重置并持久化,没有授权以这些 sibling slice 是否存在来改变玩家的复活资格。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1600-1655",
"title": "化虚降级与飞升配额释放不在同一事务",
"evidence": "persist_revival_transition_with_bundle 已先提交降级后的 cultivation、life record、位置和 nourishment,之后才单独调用 release_ascension_quota_slot。释放失败仅记录 warn,函数仍发布 ECS 状态、清理棺材并发送 PlayerRevived。由此可能形成玩家已不在化虚但全局配额仍被占用的持久化状态,阻塞后续飞升;现有 precommit failpoint 测试没有覆盖该第二次数据库操作失败。"
},
{
"severity": "minor",
"file": "server/src/cmd/dev/nourish.rs",
"line": "97-104",
"title": "命令输出硬编码上限 120,可能与模型常数漂移",
"evidence": "nourish show 的两个轴均直接格式化为 /120,测试也锁定同一硬编码文本,而 Nourishment 的范围上限属于 nourishment 模块集中维护的模型常数。后续按 P4 校准时,模型 clamp 与命令显示可能独立漂移。"
}
]
}
] |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
server/src/cultivation/mod.rs (1)
1063-1069: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win注释与实现不符:描述的是
|| reincarnation.is_some(),代码是&& !is_reincarnating。代码本身正确——转世时故意不插入
default_lifespan,因为 1085-1090 会插入fresh_lifespan,两者同帧插入会形成覆盖竞争。但注释写的是「若不加|| reincarnation.is_some()这份 exhausted 值会原样留在 ECS 上」,与实际条件相反,且把「覆盖 exhausted 寿元」的职责错误归给了本分支。按注释理解去改条件会直接引入双重插入。📝 建议同步注释
- // 转世必须无条件换掉寿元组件——`restored_lifespan` 里躺着的是刚才那个已终结角色 - // 耗尽的 120/120,`restored_lifespan.is_none()` 在这条分支恒为 false(attach_player_state - // 已经把它挂上了),若不加 `|| reincarnation.is_some()` 这份 exhausted 值会原样留在 - // ECS 上,下一次 lifespan tick 立刻把刚转世的新角色又判定老死——死循环重现。 - if restored_lifespan.is_none() && !is_reincarnating { + // 转世路径不在这里插 `default_lifespan`:`restored_lifespan` 里躺着的是刚终结角色 + // 耗尽的 120/120,但覆盖它的职责归下面提交分支插入的 `fresh_lifespan` + // (`LifespanComponent::new(bundle.spec.lifespan_cap)`)。若这里也插一份,同一次 + // flush 内两个 `LifespanComponent` 会互相覆盖,实际生效值取决于命令顺序。 + if restored_lifespan.is_none() && !is_reincarnating {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/cultivation/mod.rs` around lines 1063 - 1069, Update the comment above the restored_lifespan condition to match the existing logic: when is_reincarnating is true, this branch intentionally skips default_lifespan insertion because the later fresh_lifespan insertion replaces the exhausted component. Remove the claim that this condition must add a reincarnation-specific fallback, and preserve the current condition to avoid same-frame component insertion conflicts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@server/src/combat/lifecycle.rs`:
- Around line 1970-1973: Remove the unused _tutorial_state, _meridian_severed,
_poison_toxicity, and _digestion_load parameters from the affected function
signature and update every call site to stop querying, passing, and
destructuring these values. Preserve the existing fresh_* handling and ensure
NearDeathPersistenceQueryItem unpacking remains consistent.
- Around line 1733-1758: 统一 layer_id 的两个分支对 VisibleEntityLayers 的更新语义,避免 Some
分支保留额外层而 None 分支清空所有层。调整 layer_id 匹配中的可见层处理,使两条路径对既有层执行一致的移除或保留策略,同时确保 overworld
最终被插入。
- Around line 1011-1033: 在处理修炼 bundle 不完整的分支中,不能仅记录 warn! 后 continue,否则
AwaitingRevival 会持续触发自动重试。更新该分支以进入明确的终态,优先复用 terminate_lifecycle
的失败路径;若现有流程不适用,至少清除 revival_decision_deadline_tick,确保
auto_confirm_revival_decisions 不再每 tick 重发 Reincarnate。
- Around line 2125-2129: Update the inventory assignment around the visible
inventory handling to use a match over the optional inventory, consuming
fresh_inventory directly in each mutually exclusive branch instead of cloning
it; apply the same ownership pattern to fresh_lifespan in the corresponding
earlier branch.
- Around line 1728-1731: 在处理 dimension_layers 的
update_revival_player_slices_in_transaction 相关流程中,为 DimensionLayers 缺失的提前 return
分支加入 tracing::warn!,明确记录无法发布维度层及其可能导致存档维度不一致;保留现有 return 行为,不改动 overworld
或其他维度发布逻辑。
- Around line 133-166: 将 NearDeathPersistenceQueryItem 从嵌套元组别名改为派生 QueryData
的具名结构体,为各阶段和字段提供明确名称。同步更新 near_death_tick 与 handle_revival_action_intents
的查询解包和字段访问,移除对元组位置(如 _0、0.0)的依赖,并保持现有查询字段与行为不变。
In `@server/src/cultivation/mod.rs`:
- Around line 1092-1136: 抽取一个共享 helper,统一承载复活发布逻辑:Position 归位、取消隐身、各可见性层切换至
Overworld、CurrentDimension 更新,以及清理 coffin registry、移除 CoffinComponent 并发送
CoffinStateChanged。让 cultivation 中当前代码与 combat::revive_lifecycle 共同调用该
helper,确保两条路径保持一致。
In `@server/src/player/state.rs`:
- Around line 4210-4213: 更新相关测试的播种逻辑:将 line 4171 附近的 save_player_slices 替换为
save_player_slices_with_coffin(..., Some(CoffinGrade::Jade), None),确保初始
persisted state 的 in_coffin 为 true。保留对 loaded.in_coffin 为 false 的断言,使测试真正验证
update_revival_player_slices_in_transaction 在同一事务中清除棺材标志;可参考
clear_coffin_flag_for_username_zeroes_in_coffin_and_resets_grade 的播种方式。
---
Outside diff comments:
In `@server/src/cultivation/mod.rs`:
- Around line 1063-1069: Update the comment above the restored_lifespan
condition to match the existing logic: when is_reincarnating is true, this
branch intentionally skips default_lifespan insertion because the later
fresh_lifespan insertion replaces the exhausted component. Remove the claim that
this condition must add a reincarnation-specific fallback, and preserve the
current condition to avoid same-frame component insertion conflicts.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 75e021ac-141b-41f3-97c0-6c7ff167f404
📒 Files selected for processing (9)
server/src/coffin/mod.rsserver/src/combat/lifecycle.rsserver/src/cultivation/character_select.rsserver/src/cultivation/mod.rsserver/src/movement/mod.rsserver/src/nourishment/mod.rsserver/src/persistence/mod.rsserver/src/player/state.rsserver/src/world/spawn_tutorial.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: e2e
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
server/**/*.rs: Server Rust 代码提交前必须运行cargo fmt --check、cargo clippy --all-targets -- -D warnings和cargo test。
Dev harness 命令只能用于本地/dev 测试,显式绕过 worldview 修炼规则和 qi ledger 的入口不得复用于生产 gameplay 路径。
Files:
server/src/world/spawn_tutorial.rsserver/src/coffin/mod.rsserver/src/cultivation/character_select.rsserver/src/movement/mod.rsserver/src/player/state.rsserver/src/persistence/mod.rsserver/src/nourishment/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
server/src/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
server/src/**/*.rs: 所有真元/灵气流动必须通过qi_physics::ledger::QiTransfer { from, to, amount, reason },不得直接增减账户或区域数值。
测试灵气总量时必须引用SPIRIT_QI_TOTAL,不得硬编码100。
离屏战斗死亡必须调用release_dormant_qi_to_zone并通过ledger.transfer(ReleaseToZone)归还真元。
新增衰减、抽取或衰减率常数前必须检查并复用qi_physics;不存在时先扩展其 constants,禁止在业务 plan 中重复定义。
天道时代衰减必须使用qi_physics::tiandao::era_decay_step,并通过WorldQiBudget::apply_era_decay和assert_conservation追踪沉降槽。
不得使用 armor stand 或 invisible mob 作为碰撞箱/交互载体;实体必须使用 Marker+自定义渲染,交互通过 C2S 请求处理。
headless/CI 启服必须设置BONG_SKIP_SKIN_PREFETCH=1,避免缺少MINESKIN_API_KEY导致 panic。
Files:
server/src/world/spawn_tutorial.rsserver/src/coffin/mod.rsserver/src/cultivation/character_select.rsserver/src/movement/mod.rsserver/src/player/state.rsserver/src/persistence/mod.rsserver/src/nourishment/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
server/src/**/*.{rs,json}
📄 CodeRabbit inference engine (CLAUDE.md)
server/src/**/*.{rs,json}: 新增 zone 前必须核对docs/worldview.md区域表和server/zones.json中已有 ID。
ItemCategory只能使用 Pill、Herb、Scroll、Misc、Weapon、Armor、Tool、Treasure、RecipeFragment、RecipeHint、BoneCoin、Container;炼丹材料使用Misc,不得使用Material。
Files:
server/src/world/spawn_tutorial.rsserver/src/coffin/mod.rsserver/src/cultivation/character_select.rsserver/src/movement/mod.rsserver/src/player/state.rsserver/src/persistence/mod.rsserver/src/nourishment/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
**/*.{rs,ts,tsx,java,py}
📄 CodeRabbit inference engine (CLAUDE.md)
新增 skill/cast/主动能力必须同时提供独立 animation、particle/VFX、SFX、HUD 反馈和 hotbar/SkillBar PNG icon;仅实现 server 或 schema 不算完成。
Files:
server/src/world/spawn_tutorial.rsserver/src/coffin/mod.rsserver/src/cultivation/character_select.rsserver/src/movement/mod.rsserver/src/player/state.rsserver/src/persistence/mod.rsserver/src/nourishment/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
**/*.{rs,ts,tsx,java}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{rs,ts,tsx,java}: 六境界必须使用“醒灵→引气→凝脉→固元→通灵→化虚”,不得使用练气、筑基、金丹、元婴等旧称。
命名不得使用末法禁词玄、陨、星、仙、太、古,除明确允许的俗世矿名例外。
Files:
server/src/world/spawn_tutorial.rsserver/src/coffin/mod.rsserver/src/cultivation/character_select.rsserver/src/movement/mod.rsserver/src/player/state.rsserver/src/persistence/mod.rsserver/src/nourishment/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
**/*.{rs,ts,tsx,java,json,md}
📄 CodeRabbit inference engine (CLAUDE.md)
唯一真货币是骨币;矿物是交易筹码,灵石是燃料/衰变物,金银不是货币。
Files:
server/src/world/spawn_tutorial.rsserver/src/coffin/mod.rsserver/src/cultivation/character_select.rsserver/src/movement/mod.rsserver/src/player/state.rsserver/src/persistence/mod.rsserver/src/nourishment/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
**/*: 禁止git stash push后不执行对应git stash pop;不得留下孤儿 WIP stash。
每个逻辑单元使用中文 atomic commit;agent 产生的每个 commit 必须包含真实模型 ID 的Model:trailer。
未经明确确认不得执行 force push、hard reset、amend、交互式 rebase、批量删除/移动文件或依赖版本/生产配置修改;严禁--no-verify、--no-gpg-sign及关闭签名。
PR review 只能通过独立评论/review触发;不得等待 Codex,review 修改后必须重新等待 re-review。
Files:
server/src/world/spawn_tutorial.rsserver/src/coffin/mod.rsserver/src/cultivation/character_select.rsserver/src/movement/mod.rsserver/src/player/state.rsserver/src/persistence/mod.rsserver/src/nourishment/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
🔇 Additional comments (30)
server/src/nourishment/mod.rs (6)
443-443: 该段仍保留Cultivation::default(),与同 PR 其它测试不一致。
expected_dash_loss(Line 469-472)只乘活动倍率,等价于假定境界倍率为1.00,但这里未显式钉Realm::Awaken;同文件 Line 643-646 与server/src/nourishment/tick.rs的player()都已显式声明。此前评论标记为已处理,实际只覆盖了 dash 测试这一处。
216-238: LGTM!
240-380: LGTM!
382-422: LGTM!
460-516: LGTM!Also applies to: 519-621
623-725: LGTM!server/src/persistence/mod.rs (7)
6535-6535:#[cfg_attr(not(test), allow(dead_code))]仍在,请确认它是否仍与实际调用面一致。此前已就该属性提过意见并标记为已处理,但属性依旧保留。若该函数确有生产调用点,属性会永久屏蔽未来真正变成死代码时的告警;若目前只剩测试调用,则属性是必要的(否则
cargo clippy --all-targets -- -D warnings会失败)。请以实际调用面为准二选一。如按路径指引,Server Rust 代码提交前必须运行
cargo clippy --all-targets -- -D warnings。#!/bin/bash # 区分生产调用点与 #[cfg(test)] 内调用点 rg -nP --type=rust -C8 'persist_player_cultivation_bundle_with_nourishment\s*\(' -g '!**/target/**'Source: Coding guidelines
209-238: LGTM!
6336-6350: LGTM!
6352-6477: LGTM!
6479-6533: LGTM!
6556-6586: LGTM!Also applies to: 10739-10785
6285-6315: 🗄️ Data Integrity & Integration无需修改:现有
upsert_player_cultivation_bundle_in_transaction调用面中,preserve_existing_nourishment = false的调用点都传入了显式营养值,不会出现nourishment = None导致覆写为null的情况。server/src/coffin/mod.rs (2)
345-362: LGTM!
318-318: 🩺 Stability & Availability无需修改。
Bevy 当前
App::add_event::<T>()是幂等的:若Events<T>已存在,会跳过初始化与事件更新系统的重复添加,因此这里重复注册SneakEvent不会导致事件被二次清理。server/src/world/spawn_tutorial.rs (2)
240-240: LGTM!
224-225: 🎯 Functional Correctness无需修改。该路径不会重复自增:转世 fresh
TutorialState先落盘,后续 tutorial hydrate 再读回同一 fresh 值并插入组件,即使本地 Command 延迟生效也不会同时命中Without<TutorialState>导致TutorialTelemetry::started第二次自增。server/src/cultivation/mod.rs (5)
97-101: LGTM!Also applies to: 201-221
557-610: LGTM!
696-696: LGTM!Also applies to: 780-787
795-829: LGTM!Also applies to: 940-1031
1663-1934: LGTM!Also applies to: 1936-2637, 3409-3551, 3660-3863
server/src/movement/mod.rs (1)
157-164: 🎯 Functional Correctness无需修改:
tick_combat_clock已注册到生产 App。 它在build_server_app()的生产combat::register(&mut app)/CombatSystemSet::Intent路径中注册,不是#[cfg(test)]或 dev-only 代码;这里不需要改用其他时钟屏障。server/src/combat/lifecycle.rs (1)
2584-2685: LGTM!Also applies to: 4332-4430
server/src/player/state.rs (4)
754-793: LGTM!
1069-1113: LGTM!
805-829: 🗄️ Data Integrity & Integration复活路径无需修改
player_slow写入方式。
open_persistence_connection已先调用configure_connection()并执行PRAGMA foreign_keys = ON,且当前player_slow定义只有username TEXT PRIMARY KEY,没有REFERENCES player_core外键,不会插入所谓孤儿行。> Likely an incorrect or invalid review comment.
724-738: 🗄️ Data Integrity & Integration无需修改
player_known_techniques的提交事务写法新角色事务提交后,
save_player_known_techniques_slice会在 disconnect/shutdown flush、changed flush、autosave 等持久化路径以ON CONFLICT(username) DO UPDATE覆盖回当前实体的KnownTechniques::default();ECS 侧已经用默认值重置,不会保留上一世的技法熟练度。> Likely an incorrect or invalid review comment.server/src/cultivation/character_select.rs (2)
49-55: LGTM!
57-69: 🗄️ Data Integrity & Integration无需修改:另一条 CreateNewCharacter 入口已走事务化落库。
join 自动转世先用
prepare_new_character生成 bundle/inventory/component 暂存数据,再由persis_new_character_transition在同一 IMMEDIATE transaction 中落库current_char_id、player slices、cultivation bundle 和 life_record,成功后才应用 ECS;测试覆盖过崩溃回滚场景。
| ( | ||
| Option<&'a mut EntityLayerId>, | ||
| Option<&'a mut VisibleChunkLayer>, | ||
| Option<&'a mut VisibleEntityLayers>, | ||
| Option<&'a mut CurrentDimension>, | ||
| Option<&'a mut Flags>, | ||
| ), | ||
| Option<&'a Username>, | ||
| Option<&'a NpcMarker>, | ||
| Option<&'a NpcVisualProfile>, | ||
| Option<&'a mut PlayerInventory>, | ||
| Option<&'a mut SkillSet>, | ||
| Option<&'a FaunaTag>, | ||
| ( | ||
| ( | ||
| Option<&'a mut SkillSet>, | ||
| ( | ||
| Option<&'a FaunaTag>, | ||
| Option<&'a mut Nourishment>, | ||
| Option<&'a mut NourishmentActivityWindow>, | ||
| ), | ||
| ), | ||
| ( | ||
| Option<&'a QiColor>, | ||
| Option<&'a Karma>, | ||
| Option<&'a PracticeLog>, | ||
| Option<&'a InsightQuota>, | ||
| Option<&'a UnlockedPerceptions>, | ||
| Option<&'a InsightModifiers>, | ||
| Option<&'a MeridianSeveredPermanent>, | ||
| Option<&'a TutorialState>, | ||
| Option<&'a PoisonToxicity>, | ||
| Option<&'a DigestionLoad>, | ||
| ), | ||
| ), | ||
| ); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
🧩 Analysis chain
🌐 Web query:
Bevy 0.13 derive QueryData struct for large query tuples
💡 Result:
In Bevy 0.13, you can overcome the 15-element limit imposed on standard query tuples by using the #[derive(QueryData)] macro to define a custom struct [1][2]. This approach allows you to group any number of components or subqueries into a single, reusable, and named struct [1][3][4]. To implement this: 1. Define a struct and annotate it with #[derive(QueryData)] [1][5]. 2. If you need to mutate components, add the #[query_data(mutable)] attribute [1][6]. 3. Ensure every field in your struct also implements QueryData (such as components, Entity, or other nested QueryData structs) [1][3]. Example implementation: #[derive(QueryData)] pub struct LargeQuery { pub entity: Entity, pub component_a: &'static ComponentA, pub component_b: &'static ComponentB, // You can nest other QueryData structs or tuples here pub nested: NestedQuery, } // If mutable access is required: #[derive(QueryData)] #[query_data(mutable)] pub struct LargeMutableQuery { pub component_a: &'static mut ComponentA, pub component_b: &'static mut ComponentB, } Advantages of this method include: - Removing the hardcoded 15-element limit found with standard tuples [1][4]. - Providing named access to fields instead of index-based tuple access (e.g., query.component_a vs. query.0) [1][6]. - Enabling code reusability across multiple systems [1][3][4]. - Allowing custom methods to be implemented directly for your query data items [1][4]. Note that the QueryData derive macro does not automatically implement traits (like Debug) for the resulting query item structs. To add these, use the #[query_data(derive(TraitName))] attribute [1][6][5].
Citations:
- 1: https://docs.rs/bevy/0.13.0/bevy/ecs/query/trait.QueryData.html
- 2: https://github.com/bevyengine/bevy/blob/17ca59b4/crates/bevy_ecs/src/system/query.rs
- 3: https://github.com/bevyengine/bevy/blob/e903ce1a/crates/bevy_ecs/src/query/fetch.rs
- 4: https://taintedcoders.com/bevy/patterns/custom-queries
- 5: https://docs.rs/bevy/latest/bevy/ecs/query/trait.QueryData.html
- 6: https://github.com/bevyengine/bevy/blob/main/examples/ecs/custom_query_param.rs
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and inspect the relevant file/sections and search for the named query type/usages.
printf 'Files matching lifecycle.rs:\n'
fd 'lifecycle\.rs$' . || true
printf '\nRelevant definitions/usages:\n'
rg -n "NearDeathPersistenceQueryItem|RevivalRuntimeResources|near_death_tick|handle_revival_action_intents|QueryData" server/src/combat/lifecycle.rs || true
printf '\nFile size and surrounding line ranges:\n'
wc -l server/src/combat/lifecycle.rs
sed -n '1,220p' server/src/combat/lifecycle.rs
printf '\n--- later ranges ---\n'
sed -n '740,825p' server/src/combat/lifecycle.rsRepository: Kizunad/Bong
Length of output: 14353
将 NearDeathPersistenceQueryItem 抽取为具名 QueryData 结构体。
这里已是三层嵌套元组,near_death_tick 和 handle_revival_action_intents 都需要按相同形状解包;以后增减字段既要改 alias 又要改两处调用点,且容易因为元组位置难以识别。用 #[derive(QueryData)] 的结构体给阶段和字段命名,能避免依赖 _0/0.0 这类硬编码访问路径。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/combat/lifecycle.rs` around lines 133 - 166, 将
NearDeathPersistenceQueryItem 从嵌套元组别名改为派生 QueryData 的具名结构体,为各阶段和字段提供明确名称。同步更新
near_death_tick 与 handle_revival_action_intents 的查询解包和字段访问,移除对元组位置(如
_0、0.0)的依赖,并保持现有查询字段与行为不变。
| let ( | ||
| Some(qi_color), | ||
| Some(karma), | ||
| Some(practice_log), | ||
| Some(insight_quota), | ||
| Some(unlocked_perceptions), | ||
| Some(insight_modifiers), | ||
| Some(meridian_severed), | ||
| ) = ( | ||
| qi_color, | ||
| karma, | ||
| practice_log, | ||
| insight_quota, | ||
| unlocked_perceptions, | ||
| insight_modifiers, | ||
| meridian_severed, | ||
| ) | ||
| else { | ||
| tracing::warn!( | ||
| "[bong][combat] refusing revival for {entity:?}: cultivation bundle is incomplete" | ||
| ); | ||
| continue; | ||
| }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
bundle 不完整时 continue 会让玩家永久卡在死亡屏并每 tick 刷 warn。
lifecycle.state 保持 AwaitingRevival、revival_decision_deadline_tick 也不变。一旦 deadline 过期,auto_confirm_revival_decisions 会每 tick 重发 Reincarnate intent,于是这里每 tick 命中同一条 warn! 并 continue:玩家永远复活不了、也终结不了,日志被淹没。
建议在拒绝时给出终态(例如走 terminate_lifecycle 的失败分支)或至少清掉 revival_decision_deadline_tick 以停止自动重试。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/combat/lifecycle.rs` around lines 1011 - 1033, 在处理修炼 bundle
不完整的分支中,不能仅记录 warn! 后 continue,否则 AwaitingRevival 会持续触发自动重试。更新该分支以进入明确的终态,优先复用
terminate_lifecycle 的失败路径;若现有流程不适用,至少清除 revival_decision_deadline_tick,确保
auto_confirm_revival_decisions 不再每 tick 重发 Reincarnate。
| let Some(layers) = dimension_layers else { | ||
| return; | ||
| }; | ||
| let overworld = layers.overworld; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
dimension_layers 缺失时静默 return,但 SQLite 已把 last_dimension 写成 Overworld,造成运行时与存档错位。
update_revival_player_slices_in_transaction(server/src/player/state.rs)无条件写入 dimension_kind_to_sql(DimensionKind::default()),而这里在 DimensionLayers 资源缺失时直接返回,EntityLayerId/VisibleChunkLayer/CurrentDimension 全部保持 TSY。玩家本次会话仍在 TSY 层活动,但重连时按存档被放回 Overworld 坐标——TSY 内的复活锚点在 Overworld 是无效坐标。
生产链路上 DimensionLayers 应恒存在,但这是静默失败:建议在此分支加 tracing::warn!,或把维度发布视为复活前置条件在 SQLite 提交前校验。
🔧 最小修复:至少让静默失败可观测
let Some(layers) = dimension_layers else {
+ tracing::warn!(
+ "[bong][combat] skipped Overworld runtime publication for {entity:?}: DimensionLayers resource is missing while persistence already recorded Overworld"
+ );
return;
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let Some(layers) = dimension_layers else { | |
| return; | |
| }; | |
| let overworld = layers.overworld; | |
| let Some(layers) = dimension_layers else { | |
| tracing::warn!( | |
| "[bong][combat] skipped Overworld runtime publication for {entity:?}: DimensionLayers resource is missing while persistence already recorded Overworld" | |
| ); | |
| return; | |
| }; | |
| let overworld = layers.overworld; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/combat/lifecycle.rs` around lines 1728 - 1731, 在处理
dimension_layers 的 update_revival_player_slices_in_transaction 相关流程中,为
DimensionLayers 缺失的提前 return 分支加入 tracing::warn!,明确记录无法发布维度层及其可能导致存档维度不一致;保留现有
return 行为,不改动 overworld 或其他维度发布逻辑。
| match layer_id { | ||
| Some(mut layer_id) => { | ||
| let previous = layer_id.0; | ||
| layer_id.0 = overworld; | ||
| if let Some(mut visible_layers) = visible_entity_layers { | ||
| visible_layers.0.remove(&previous); | ||
| visible_layers.0.remove(&layers.tsy); | ||
| visible_layers.0.insert(overworld); | ||
| } else { | ||
| let mut visible_layers = VisibleEntityLayers::default(); | ||
| visible_layers.0.insert(overworld); | ||
| commands.entity(entity).insert(visible_layers); | ||
| } | ||
| } | ||
| None => { | ||
| commands.entity(entity).insert(EntityLayerId(overworld)); | ||
| if let Some(mut visible_layers) = visible_entity_layers { | ||
| visible_layers.0.clear(); | ||
| visible_layers.0.insert(overworld); | ||
| } else { | ||
| let mut visible_layers = VisibleEntityLayers::default(); | ||
| visible_layers.0.insert(overworld); | ||
| commands.entity(entity).insert(visible_layers); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
两条分支对 VisibleEntityLayers 的处理语义不一致。
layer_id 为 Some 时只移除 previous 与 layers.tsy 并保留其余层;为 None 时却 clear() 全清。当前只有 overworld/tsy 两个维度所以看不出差异,一旦引入第三层,两条路径会产生不同的可见性结果。建议统一为其中一种语义。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/combat/lifecycle.rs` around lines 1733 - 1758, 统一 layer_id 的两个分支对
VisibleEntityLayers 的更新语义,避免 Some 分支保留额外层而 None 分支清空所有层。调整 layer_id
匹配中的可见层处理,使两条路径对既有层执行一致的移除或保留策略,同时确保 overworld 最终被插入。
| _tutorial_state: Option<&TutorialState>, | ||
| _meridian_severed: Option<&MeridianSeveredPermanent>, | ||
| _poison_toxicity: Option<&PoisonToxicity>, | ||
| _digestion_load: Option<&DigestionLoad>, |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
四个 _ 前缀参数已完全无用,建议从签名与调用点一并删除。
_tutorial_state/_meridian_severed/_poison_toxicity/_digestion_load 在函数体内不再被读取(新角色一律使用 fresh_*),但调用点(Line 1192-1195)仍在从 ECS 查询里透传它们。这个函数参数已超过 30 个,删掉这 4 个能同时简化 NearDeathPersistenceQueryItem 的解包压力。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/combat/lifecycle.rs` around lines 1970 - 1973, Remove the unused
_tutorial_state, _meridian_severed, _poison_toxicity, and _digestion_load
parameters from the affected function signature and update every call site to
stop querying, passing, and destructuring these values. Preserve the existing
fresh_* handling and ensure NearDeathPersistenceQueryItem unpacking remains
consistent.
| if let Some(mut inventory) = inventory { | ||
| *inventory = fresh_inventory.clone(); | ||
| } else { | ||
| commands.entity(entity).insert(fresh_inventory); | ||
| } |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value
fresh_inventory.clone() 可以消除。
两个分支互斥,用 match 让每条路径各自消耗 fresh_inventory 即可,无需克隆整份背包(容器 + 全部物品实例)。同样的写法也适用于 Line 2102-2106 的 fresh_lifespan.clone()。
♻️ 建议改写
- if let Some(mut inventory) = inventory {
- *inventory = fresh_inventory.clone();
- } else {
- commands.entity(entity).insert(fresh_inventory);
- }
+ match inventory {
+ Some(mut inventory) => *inventory = fresh_inventory,
+ None => {
+ commands.entity(entity).insert(fresh_inventory);
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if let Some(mut inventory) = inventory { | |
| *inventory = fresh_inventory.clone(); | |
| } else { | |
| commands.entity(entity).insert(fresh_inventory); | |
| } | |
| match inventory { | |
| Some(mut inventory) => *inventory = fresh_inventory, | |
| None => { | |
| commands.entity(entity).insert(fresh_inventory); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/combat/lifecycle.rs` around lines 2125 - 2129, Update the
inventory assignment around the visible inventory handling to use a match over
the optional inventory, consuming fresh_inventory directly in each mutually
exclusive branch instead of cloning it; apply the same ownership pattern to
fresh_lifespan in the corresponding earlier branch.
* 归档 plan-nested-pack-base-v1:撤回被取代僵尸 plan,改写为 supersession 归档 实地验真确认 sub_container/MAX_PACK_NEST_DEPTH/PackItemSession/ PackContainerOpen|Move|Close/SubContainerPanel 在生产代码中零命中—— 本 plan 的 session-based 嵌套子容器协议从未落地一行代码。塔科夫式套包 体验实际由 plan-tarkov-backpack-v1(沿 plan-layered-equip-v1 → plan-backpack-equip-v1 血统)用完全不同的协议(owner_instance_id + 平展 PlayerInventory.containers + pack_<instance_id> 命名 + InventoryMoveIntent + WornContainerPanel/PackContainerWindow)交付。 改写文件明确标注被取代 + 禁止独立消费,记录真实替代 symbol 的 file:line 证据,并把未覆盖能力(随身子包容器化/水囊灌装/§808 转移税) 指向正确的下游 plan/reminder 归属,然后 git mv 入 finished_plans/。 Model: claude-sonnet-5 Co-Authored-By: Claude <noreply@anthropic.com> * 修正归档内容:上一提交仅捕获纯 rename(旧内容),补齐 supersession 改写正文 上一提交因写入时序问题,暂存区只落了 git mv 前的旧草案内容(100% 相似度 纯 rename,未带改写)。本提交把工作区已完成的改写正文(归档头部 + 历史 阶段表 + 替代关系与代码现实证据 + 未覆盖能力归属 + Finish Evidence) 正式落进 git 历史,docs/finished_plans/plan-nested-pack-base-v1.md 现内容与预期一致。 Model: claude-sonnet-5 Co-Authored-By: Claude <noreply@anthropic.com> * 修正归档中对 container-filter 的跨 PR 表述 去掉本归档对下游 plan 容器名单的复述(`herb_pouch`/`water_skin`/`herb_crate` 等取自 origin/main 旧正文, 与同文件「water_skin 归 satiety」一节自相矛盾),改为只声明归属、以该 plan 合入后的正文为唯一权威。 同时把「container-filter 依赖引用脱节需独立任务重写」从开放待办改为如实记录: 该重基正是在跑的 PR #1260(`docs/rebase-container-filter-owner-v1-r4`)的核心交付物, 并注明若 #1260 最终未合入则该需求回落为开放待办,避免归档留下假账。 Model: claude-opus-5 Co-Authored-By: Claude <noreply@anthropic.com> * 补注下游 satiety 承接 PR water_skin 灌装归属处补注:截至归档 origin/main 上该 plan 仍为 skeleton,其升 active + P0 实装由在跑的 PR #1259 承接。与上一 commit 对 #1260 的处理保持同一口径——如实标注在跑的承接方,不把已有责任主体的事写成开放待办。 Model: claude-opus-5 Co-Authored-By: Claude <noreply@anthropic.com> * 修正升 active 引注的 commit hash validator 抓出:原文把 7c36567 写成本 plan 的「升 active」参照,实际那是姊妹 plan tarkov-backpack-v1 自己的 skeleton→active 提交(#758,2026-06-26)。 本 plan 真正的升 active 是 091ba0e(#476,2026-06-10,skeleton 99 行 → active 204 行),与 plans-progress.yaml 的 pr_refs:[476] 吻合。 骨架首次提交 5752075/d74ddd379 引用无误,一并补上日期与 PR 号。 Model: claude-opus-5 Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: server_kizuna <serverkizuna@server.server> Co-authored-by: Claude <noreply@anthropic.com>
|
/review |
🔭 Review · PR #1259❌ 未通过:0/4,存在 22 项真实 finding,要求修改
📋 Plan 原意确认
🧑⚖️ 复投结果
🔎 Findings
首轮与复投原始结构化摘要[
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 96,
"plan_intent": {
"status": "misaligned",
"reason": "PR 覆盖了 P0 的双轴、周期 sweep、持久化、生命周期和 dev 命令主体,但正式复活路径违反 plan 已锁定的真元同额回流及完整 cultivation sibling bundle fail-closed 契约;此外,PR 在实施 P0 的同时才把 skeleton plan 提升为 Active,未形成可核验的 pre-P0 决策基线。",
"missing": [
"所有正额复活真元均经 qi_physics ledger 同额入账,不能按 QI_EPSILON 直接抹零",
"正式复活对完整持久化 sibling bundle 的缺件拒绝矩阵",
"在 P0 实施前独立收口并固定的 Active plan 基线"
]
},
"summary": "存在一处真元守恒 blocker:正好等于或小于 QI_EPSILON 的正额真元会从 cultivation 清零,却不进入任何 ledger。正式复活还允许 tutorial、毒性和消化等持久化 sibling 缺失时提交,与 plan 的完整 bundle fail-closed 边界不符。",
"findings": [
{
"severity": "blocker",
"file": "server/src/combat/lifecycle.rs",
"line": "1633-1636",
"title": "正额真元在 QI_EPSILON 边界被无账本抹除",
"evidence": "stage_revival_qi_release 对 amount <= QI_EPSILON 直接返回 Ok(None),随后 apply_revive_penalty 的 staged cultivation 仍以 qi_current = 0 提交。新增 reincarnate_at_qi_epsilon_does_not_require_release_resources 测试还明确锁定了“QI_EPSILON 正额真元清零、无 QiTransfer、无需 ZoneRegistry/WorldQiAccount”的行为,违反 plan §4/§8.1 #3 的“同额真元回流”和“正额真元回流须具备 ZoneRegistry / WorldQiAccount”,也违反真元必须走 qi_physics ledger 的守恒边界。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1799-1851",
"title": "正式复活没有对完整 cultivation sibling bundle fail-closed",
"evidence": "revive_lifecycle 的完整性 match 只强制 cultivation、meridians、contamination、life_record、qi_color、karma、practice_log、insight_quota、unlocked_perceptions、insight_modifiers 和 meridian_severed;tutorial_state、poison_toxicity、digestion_load 仍作为 Option 直接传入 PlayerCultivationBundle。因而这些持久化 sibling 缺失时复活仍可提交并 emit completion event,与 plan §4 和 §8.1 #3 明确要求的“复活前须具备可持久化的完整 cultivation sibling bundle,任一缺失 fail-closed”不符。"
},
{
"severity": "major",
"file": "docs/plan-satiety-hydration-v1.md",
"line": "3, 154-227",
"title": "P0 实施与 pre-P0 plan 决策门在同一 PR 中完成",
"evidence": "diff 显示该 PR 才把 docs/plans-skeleton/plan-satiety-hydration-v1.md 重命名为 active plan,并在同一变更中新增整段 §8.1 决议;但文档第 3 行及 §8 明确规定 skeleton 转 Active 前必须先收口,§8.1 又标称为 pre-P0 决议,而本 PR 自身已是 PR-1/P0 实施。基线中不存在一个已固定、可供本实现消费和核查的 Active plan。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "REQUEST_CHANGES",
"confidence": 94,
"plan_intent": {
"status": "misaligned",
"reason": "PR 已接入 Nourishment 注册、周期结算、持久化、命令和生命周期主路径,但正式复活仍违反 plan 锁定的正额真元同额入账、完整 sibling bundle fail-closed 及 PlayerRevived 完成事件契约。",
"missing": [
"所有正额复活真元必须经过 qi_physics ledger,不能按 epsilon 直接抹除",
"正式复活必须拒绝缺失 tutorial_state、poison_toxicity、digestion_load 等持久化 sibling 的实体",
"PlayerRevived 发出前必须保证 deferred 的维度、可见层和棺材组件变更已对所有消费者可见"
]
},
"summary": "P0 主体已有真实加载和调用路径,但三处跨系统契约仍未闭合。其中正额真元被直接清零是守恒阻断项,另外两个问题会让正式复活在缺组件或半发布状态下仍发出完成事件。",
"findings": [
{
"severity": "blocker",
"file": "server/src/combat/lifecycle.rs",
"line": "1646-1648",
"title": "正额真元在 epsilon 边界被清零且未进入 qi_physics ledger",
"evidence": "stage_revival_qi_release 对 amount <= QI_EPSILON 直接返回 None;随后 apply_revive_penalty 已把 qi_current 归零。新增测试 reincarnate_at_qi_epsilon_does_not_require_release_resources 还明确锁定 QI_EPSILON 的正额真元不要求 ZoneRegistry/WorldQiAccount、不发 QiTransfer。plan §4/§8.1 #3 要求正额真元同额进入 zone 或 pending_inflow,Bong 准则也禁止真元绕过 ledger。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1803-1918",
"title": "正式复活未对完整 cultivation sibling bundle fail-closed",
"evidence": "完整性 match 只强制 cultivation、meridians、contamination、life_record、qi_color、karma、practice_log、insight_quota、unlocked_perceptions、insight_modifiers、meridian_severed;tutorial_state、poison_toxicity、digestion_load 仍以 Option 直接传给 PlayerCultivationBundle。测试 fixture spawn_revival_action_actor 未挂这三个组件却可成功复活,证明缺失 sibling 的生产函数不会拒绝。该行为与 plan §4 和 §8.1 #3 的“完整 cultivation sibling bundle 缺失时 fail-closed”相反。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1978-2057",
"title": "PlayerRevived 可在 deferred 的运行时组件变更生效前发出",
"evidence": "publish_overworld_runtime 在缺少 EntityLayerId、VisibleChunkLayer、VisibleEntityLayers 或 CurrentDimension 时通过 Commands 延迟插入,clear_coffin_runtime 也通过 Commands 延迟移除 CoffinComponent;revive_lifecycle 随后立即 send PlayerRevived。coffin::register 另行显式加入 apply_deferred 才避免同 Update 的 pinning 读取旧组件,反证这些变更在 send 时尚未普遍发布。现有完成事件测试只读取直接修改的 Nourishment 和活动窗口,没有锁定维度层、可见层及 CoffinComponent。"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 97,
"plan_intent": {
"status": "misaligned",
"reason": "双轴、全局 sweep、生命周期持久化和复活事务整体符合 PR-1 原意,但正式复活对正额真元设置了未经 plan 授权的 QI_EPSILON 豁免,会将该区间内的 qi_current 清零而不进入 qi_physics ledger,违反 §4/§8.1 #3 的“同额真元回流”和灵气守恒契约。",
"missing": [
"所有正额真元,包括等于或小于 QI_EPSILON 的余额,都必须有可核验的 qi_physics ledger 去向;不能直接作为无操作丢弃。",
"增加 0、低于 QI_EPSILON、等于 QI_EPSILON 三个边界的守恒测试,断言复活前余额等于 zone 与 pending inflow 的入账总和。"
]
},
"summary": "存在一处阻断性的真元守恒漏洞:正式复活会无账本地销毁不超过 QI_EPSILON 的正额真元。当前测试还把这一行为锁成了预期,需同时修正实现和边界契约测试。",
"findings": [
{
"severity": "blocker",
"file": "server/src/combat/lifecycle.rs",
"line": "1639-1641",
"title": "正式复活会绕过 qi_physics ledger 销毁 epsilon 范围内的正额真元",
"evidence": "stage_revival_qi_release 在 amount <= QI_EPSILON 时直接返回 Ok(None),但随后 apply_revive_penalty 的 staged cultivation 仍以 qi_current = 0 持久化并发布。测试 reincarnate_at_qi_epsilon_does_not_require_release_resources 进一步断言 QI_EPSILON 可在缺少 ZoneRegistry/WorldQiAccount 时被清零且不产生 QiTransfer。Plan §8.1 #3 要求“正额真元回流须具备 ZoneRegistry / WorldQiAccount”并将同额回流纳入事务,没有 epsilon 销毁豁免。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 96,
"plan_intent": {
"status": "misaligned",
"reason": "PR 基本覆盖了 P0 的双轴、周期结算、持久化、命令和生命周期事务,但新角色转换仍存在可直接丢弃正额真元的路径,违反 plan 的生命周期原子性要求及 qi_physics ledger 守恒契约。",
"missing": [
"CreateNewCharacter 必须在旧角色仍携带正额 qi_current 时 fail-closed,或在同一事务中通过 qi_physics ledger 完成回流,不能直接用默认 Cultivation 覆盖。",
"需要增加 Terminated + 正额真元直接创建新角色的守恒测试,锁定 ECS、SQLite、zone/pending inflow 和 QiTransfer。"
]
},
"summary": "存在一项阻断问题:reset_for_new_character 不接收或检查旧 Cultivation,却直接发布 qi_current=0 的默认组件。现有测试甚至构造了 qi_current=1000 的 Terminated 角色并允许该路径成功,正额真元会绕过 qi_physics ledger 消失。",
"findings": [
{
"severity": "blocker",
"file": "server/src/combat/lifecycle.rs",
"line": "1273-1317, 2229-2515",
"title": "BLOCKING: CreateNewCharacter 可绕过 qi_physics ledger 丢弃旧角色真元",
"evidence": "handle_revival_action_intents 调用 reset_for_new_character 时没有传入查询所得的 cultivation;该函数随后无条件构造 fresh_cultivation = Cultivation::default() 并覆盖运行时状态。新增的 create_new_character_rehydrates_default_character_state_and_persists_slices 测试明确给 Terminated 实体放入 qi_current: 1_000.0,转换后只断言新 Cultivation.qi_current == 0.0,未发生 zone 或 pending_inflow 入账。这条入口只检查 LifecycleState::Terminated,无法证明旧角色真元已由 PlayerTerminated 消费者结算。"
}
]
}
][
{
"reviewer": "A",
"name": "Plan 原意核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:PR 虽覆盖 P0 的双轴、全局 sweep、持久化、dev 命令和生命周期主体,但仍违反 plan 已锁定的真元同额回流、完整 cultivation sibling bundle fail-closed、完成事件发布顺序及新角色生命周期守恒契约;同时在实施 P0 的同一 PR 内才建立所谓 pre-P0 Active 基线,不符合 plan 的决策门原意。",
"missing": [
"所有大于 0 的复活真元均通过 qi_physics ledger 同额入账,包括低于或等于 QI_EPSILON 的余额",
"正式复活对 tutorial_state、poison_toxicity、digestion_load 等完整持久化 sibling 的缺件拒绝矩阵",
"CreateNewCharacter 在旧 Cultivation 仍有正额 qi_current 时 fail-closed,或在同一事务内完成 ledger 回流",
"在 PlayerRevived 发出前完成 deferred 维度层、可见层和 CoffinComponent 变更的全局可见发布",
"在 P0 实施前独立固定并通过审查的 Active plan 决策基线"
]
},
"summary": "维持 REQUEST_CHANGES,并采纳其他 reviewer 揭示的两项独立风险:CreateNewCharacter 可直接覆盖旧角色正额真元,以及 PlayerRevived 可先于 deferred 运行时组件发布。现有 epsilon 无账本销毁仍是明确 blocker,正式复活的完整 sibling fail-closed 契约也未闭合。 Plan 原意未确认:最终确认:PR 虽覆盖 P0 的双轴、全局 sweep、持久化、dev 命令和生命周期主体,但仍违反 plan 已锁定的真元同额回流、完整 cultivation sibling bundle fail-closed、完成事件发布顺序及新角色生命周期守恒契约;同时在实施 P0 的同一 PR 内才建立所谓 pre-P0 Active 基线,不符合 plan 的决策门原意。",
"findings": [
{
"severity": "blocker",
"file": "server/src/combat/lifecycle.rs",
"line": "1633-1636",
"title": "BLOCKING: epsilon 范围内的正额真元会被无账本销毁",
"evidence": "stage_revival_qi_release 对 amount <= QI_EPSILON 直接返回 Ok(None),但 apply_revive_penalty 已将 staged Cultivation.qi_current 清零并随后持久化。测试 reincarnate_at_qi_epsilon_does_not_require_release_resources 还明确锁定 QI_EPSILON 正额余额无需 ZoneRegistry、WorldQiAccount 或 QiTransfer 即可消失。plan §4/§8.1 #3 要求正额真元同额进入 zone 或 pending inflow,不存在 epsilon 销毁豁免。"
},
{
"severity": "blocker",
"file": "server/src/combat/lifecycle.rs",
"line": "1273-1317, 2229-2515",
"title": "BLOCKING: CreateNewCharacter 可覆盖旧角色的正额真元",
"evidence": "handle_revival_action_intents 调用 reset_for_new_character 时没有传入旧 Cultivation;该函数无条件构造并发布 Cultivation::default()。create_new_character_rehydrates_default_character_state_and_persists_slices 明确给 Terminated 实体保留 qi_current = 1000.0,之后允许新角色创建成功并断言新值为 0,却没有对应 zone、pending inflow 或 QiTransfer。延迟一个 Update 只能帮助正常 PlayerTerminated 消费链,不能证明所有 Terminated 实体的旧真元已结算,入口本身也未 fail-closed。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1799-1918",
"title": "正式复活没有对完整 cultivation sibling bundle fail-closed",
"evidence": "revive_lifecycle 只强制 cultivation、meridians、contamination、life_record、qi_color、karma、practice_log、insight_quota、unlocked_perceptions、insight_modifiers 和 meridian_severed;tutorial_state、poison_toxicity、digestion_load 仍以 Option 传入 PlayerCultivationBundle。测试 fixture 也未挂这三个组件而可成功复活。这与 plan §4/§8.1 #3 的完整 cultivation sibling bundle 任一缺失即 fail-closed 直接冲突。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1978-2057",
"title": "PlayerRevived 可在 deferred 运行时组件变更生效前发出",
"evidence": "publish_overworld_runtime 对缺失的 EntityLayerId、VisibleChunkLayer、VisibleEntityLayers、CurrentDimension 使用 Commands 延迟插入,clear_coffin_runtime 也延迟移除 CoffinComponent;revive_lifecycle 随后在同一系统内立即发送 PlayerRevived。当前 completion observer 测试只验证直接修改的 Nourishment 和活动窗口,不能证明所有事件消费者看到的维度层、可见层和棺材组件均已完成发布。coffin::register 额外加入 apply_deferred 才保护 pinning,也说明该可见性依赖局部调度接线而非 completion event 自身契约。"
},
{
"severity": "major",
"file": "docs/plan-satiety-hydration-v1.md",
"line": "3, 154-227",
"title": "P0 实施与 pre-P0 决策门在同一 PR 中完成",
"evidence": "本 PR 才把 skeleton plan 移入 active 路径并新增 §8.1 决议,同时已经提交 PR-1/P0 的大规模实现。文档自身声明 skeleton 转 Active 前必须先收口,§8.1 也标称 pre-P0,因此仓库历史中不存在一个先于实现固定、可供消费和审查的 Active plan 基线。"
}
]
},
{
"reviewer": "B",
"name": "运行接线核查",
"vote": "REQUEST_CHANGES",
"confidence": 98,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:P0 的 Nourishment 注册、周期 sweep、持久化、dev 命令和生命周期主调用路径已接通,但正式复活和新角色转换仍违反 plan 锁定的真元守恒、完整 sibling bundle fail-closed 及 completion event 可见性契约,因此不符合 plan 原意。",
"missing": [
"所有大于 0 的复活真元均须同额进入 qi_physics ledger,不得设置 QI_EPSILON 销毁豁免",
"CreateNewCharacter 必须证明旧角色正额真元已经结算,或在自身事务中完成 ledger 回流",
"正式复活必须对 tutorial_state、poison_toxicity、digestion_load 等完整持久化 sibling 缺失 fail-closed",
"PlayerRevived 发出前必须确保 deferred 的维度层、可见层和 CoffinComponent 变更已实际应用"
]
},
"summary": "维持 REQUEST_CHANGES,并采纳 Reviewer D 揭示的独立守恒 blocker。当前有两条可无账本销毁正额真元的生产路径,另有完整 bundle 与完成事件发布契约未闭合。 Plan 原意未确认:最终确认:P0 的 Nourishment 注册、周期 sweep、持久化、dev 命令和生命周期主调用路径已接通,但正式复活和新角色转换仍违反 plan 锁定的真元守恒、完整 sibling bundle fail-closed 及 completion event 可见性契约,因此不符合 plan 原意。",
"findings": [
{
"severity": "blocker",
"file": "server/src/combat/lifecycle.rs",
"line": "1633-1648",
"title": "BLOCKING: epsilon 范围内的正额真元被清零但未进入 qi_physics ledger",
"evidence": "stage_revival_qi_release 对 amount <= QI_EPSILON 直接返回 Ok(None),随后处罚后的 Cultivation 仍以 qi_current = 0 持久化和发布。测试 reincarnate_at_qi_epsilon_does_not_require_release_resources 还明确锁定 QI_EPSILON 正额真元无需 ZoneRegistry、WorldQiAccount 或 QiTransfer 即可消失。plan §4/§8.1 #3 要求正额真元同额进入 zone 或 pending_inflow,不存在 epsilon 销毁豁免。"
},
{
"severity": "blocker",
"file": "server/src/combat/lifecycle.rs",
"line": "1273-1317, 2229-2515",
"title": "BLOCKING: CreateNewCharacter 可覆盖仍携带正额真元的旧 Cultivation",
"evidence": "handle_revival_action_intents 已查询到 cultivation,却没有将其传给 reset_for_new_character;后者直接构造 Cultivation::default() 并发布。create_new_character_rehydrates_default_character_state_and_persists_slices 将 Terminated 实体的 qi_current 设为 1000.0,随后允许转换成功并只断言新值为 0。单帧 defer 只能保护同批 PlayerTerminated 消费者,不能证明所有已处于 Terminated 的入口都完成过旧真元结算。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1799-1918",
"title": "正式复活仍允许缺失持久化 cultivation sibling",
"evidence": "完整性 match 强制了 cultivation、meridians、contamination、life_record、qi_color、karma、practice_log、insight_quota、unlocked_perceptions、insight_modifiers 和 meridian_severed,但 tutorial_state、poison_toxicity、digestion_load 仍以 Option 传入 PlayerCultivationBundle。spawn_revival_action_actor 未挂这三个组件仍能支撑成功复活测试,证明生产路径没有按 plan §4/§8.1 #3 对完整 sibling bundle fail-closed。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1978-2057",
"title": "PlayerRevived 可能在 deferred 运行时变更生效前被观察",
"evidence": "publish_overworld_runtime 对缺失的 EntityLayerId、VisibleChunkLayer、VisibleEntityLayers、CurrentDimension 使用 Commands 延迟插入,clear_coffin_runtime 也通过 Commands 延迟移除 CoffinComponent,随后同一系统立即 send PlayerRevived。事件队列可被同 Update 中排序在后的 reader 读取,而这些 Commands 尚不一定经过 apply_deferred。coffin::register 为避免同 Update pinning 读取旧 CoffinComponent 显式加入 apply_deferred,也反证调用点本身没有同步发布这些组件。现有 completion observer 只断言直接修改的 Nourishment 和活动窗口。"
}
]
},
{
"reviewer": "C",
"name": "正确性与守恒核查",
"vote": "REQUEST_CHANGES",
"confidence": 99,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:PR 已覆盖 P0 的双轴模型、周期 sweep、持久化和生命周期主体,但两条角色转换路径仍可绕过 qi_physics ledger 销毁正额真元,且正式复活未按 plan 对完整 cultivation sibling bundle 执行 fail-closed,因此不符合 plan 的守恒与原子生命周期原意。",
"missing": [
"所有大于 0 的复活真元,包括低于或等于 QI_EPSILON 的余额,都必须同额进入 zone 或 pending inflow,并产生可核验的 ledger 记录",
"CreateNewCharacter 必须拒绝仍携带正额 qi_current 的旧角色,或在同一事务中完成 qi_physics 回流",
"正式复活必须对 tutorial_state、poison_toxicity、digestion_load 等完整持久化 sibling bundle 缺件 fail-closed",
"补齐零值、低于 epsilon、等于 epsilon及 Terminated 正额真元创建新角色的守恒与回滚测试"
]
},
"summary": "维持 REQUEST_CHANGES,并吸收其他 reviewer 揭示的独立守恒入口。除首轮确认的 epsilon 真元销毁外,CreateNewCharacter 还能直接用默认 Cultivation 覆盖旧角色正额真元;正式复活也允许部分持久化 sibling 缺失。前两项均是 blocker。 Plan 原意未确认:最终确认:PR 已覆盖 P0 的双轴模型、周期 sweep、持久化和生命周期主体,但两条角色转换路径仍可绕过 qi_physics ledger 销毁正额真元,且正式复活未按 plan 对完整 cultivation sibling bundle 执行 fail-closed,因此不符合 plan 的守恒与原子生命周期原意。",
"findings": [
{
"severity": "blocker",
"file": "server/src/combat/lifecycle.rs",
"line": "1639-1641",
"title": "BLOCKING: 正式复活会无账本销毁 epsilon 范围内的正额真元",
"evidence": "stage_revival_qi_release 在 amount <= QI_EPSILON 时直接返回 Ok(None),但 apply_revive_penalty 已将 staged cultivation 的 qi_current 清零,随后该状态被持久化并发布。测试 reincarnate_at_qi_epsilon_does_not_require_release_resources 还明确锁定 QI_EPSILON 正额余额无需 ZoneRegistry、WorldQiAccount 或 QiTransfer 即可消失。Plan §4/§8.1 #3 要求所有正额真元同额回流到 zone 或 pending inflow,不存在 epsilon 销毁豁免。"
},
{
"severity": "blocker",
"file": "server/src/combat/lifecycle.rs",
"line": "1273-1317, 2229-2515",
"title": "BLOCKING: CreateNewCharacter 可直接覆盖旧角色正额真元",
"evidence": "handle_revival_action_intents 查询到了旧 Cultivation,却未将其传入 reset_for_new_character;该函数直接构造 fresh_cultivation = Cultivation::default() 并在提交后覆盖 ECS。测试 create_new_character_rehydrates_default_character_state_and_persists_slices 明确给 Terminated 实体设置 qi_current = 1_000.0,随后允许转换成功并断言新值为 0.0,没有对应 zone、pending inflow 或 QiTransfer。单纯延迟一个 Update 只能帮助同批 PlayerTerminated 消费者,不能保证所有 Terminated 实体的旧真元已经结算。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1799-1851",
"title": "正式复活未对完整 cultivation sibling bundle 缺件 fail-closed",
"evidence": "完整性 match 强制了 cultivation、meridians、contamination、life_record、qi_color、karma、practice_log、insight_quota、unlocked_perceptions、insight_modifiers 和 meridian_severed,但 tutorial_state、poison_toxicity、digestion_load 仍以 Option 传入 PlayerCultivationBundle。spawn_revival_action_actor 未挂载这些组件仍可完成复活,证明缺件会被接受。Plan §4/§8.1 #3 明确要求完整持久化 cultivation sibling bundle 任一缺失时 fail-closed。"
}
]
},
{
"reviewer": "D",
"name": "代码质量与测试核查",
"vote": "REQUEST_CHANGES",
"confidence": 99,
"plan_intent": {
"status": "misaligned",
"reason": "最终确认:PR 虽已覆盖 P0 的双轴、周期 sweep、持久化、命令和主要生命周期接线,但正式复活与新角色转换仍存在绕过 qi_physics ledger 销毁正额真元的路径,且正式复活未按 plan 对完整 cultivation sibling bundle fail-closed,因此不符合 plan 的守恒与事务原意。",
"missing": [
"所有大于 0 的复活真元,包括低于或等于 QI_EPSILON 的余额,都必须同额进入 qi_physics ledger,或在无法入账时拒绝提交",
"CreateNewCharacter 必须证明旧角色真元已结算;旧 Cultivation 仍有正额 qi_current 时应 fail-closed,或在同一事务中完成 ledger 回流",
"正式复活必须要求 tutorial_state、poison_toxicity、digestion_load 等全部持久化 sibling 存在,并增加逐项缺失拒绝测试"
]
},
"summary": "维持 REQUEST_CHANGES,并在复投中确认另外两项有效问题。当前共有两项真元守恒 blocker:正式复活会抹除 epsilon 范围内的正额真元,直接创建新角色也可用默认 Cultivation 覆盖未结算的旧真元。另有一项完整 bundle 契约 major。现有测试不但没有封住这些路径,其中两个测试还把无账本清零锁成成功行为。 Plan 原意未确认:最终确认:PR 虽已覆盖 P0 的双轴、周期 sweep、持久化、命令和主要生命周期接线,但正式复活与新角色转换仍存在绕过 qi_physics ledger 销毁正额真元的路径,且正式复活未按 plan 对完整 cultivation sibling bundle fail-closed,因此不符合 plan 的守恒与事务原意。",
"findings": [
{
"severity": "blocker",
"file": "server/src/combat/lifecycle.rs",
"line": "1633-1648",
"title": "BLOCKING: 正式复活会无账本销毁 epsilon 范围内的正额真元",
"evidence": "stage_revival_qi_release 对 amount <= QI_EPSILON 直接返回 Ok(None),但 apply_revive_penalty 产生的 staged Cultivation 仍以 qi_current=0 提交。测试 reincarnate_at_qi_epsilon_does_not_require_release_resources 明确断言 QI_EPSILON 的正额余额可在缺少 ZoneRegistry、WorldQiAccount 且无 QiTransfer 时被清零。plan §4/§8.1 #3 要求正额真元同额进入 zone 或 pending inflow,没有 epsilon 销毁豁免。"
},
{
"severity": "blocker",
"file": "server/src/combat/lifecycle.rs",
"line": "1273-1317, 2229-2515",
"title": "BLOCKING: CreateNewCharacter 可覆盖仍携带正额真元的旧 Cultivation",
"evidence": "handle_revival_action_intents 查询到了旧 cultivation,却没有把它传给 reset_for_new_character;该函数直接构造 Cultivation::default() 并在提交后覆盖旧组件,也不接收 ZoneRegistry、WorldQiAccount 或 QiTransfer。create_new_character_rehydrates_default_character_state_and_persists_slices 还给 Terminated 实体设置 qi_current=1000.0,并允许转换成功后只断言新值为 0。延迟一帧只能帮助同批 PlayerTerminated 消费者运行,无法保证已处于 Terminated 但仍带 Cultivation 的入口已经完成真元结算。"
},
{
"severity": "major",
"file": "server/src/combat/lifecycle.rs",
"line": "1799-1918",
"title": "正式复活未对完整 cultivation sibling bundle fail-closed",
"evidence": "revive_lifecycle 的完整性 match 强制了 cultivation、meridians、contamination、life_record、qi_color、karma、practice_log、insight_quota、unlocked_perceptions、insight_modifiers 和 meridian_severed,但 tutorial_state、poison_toxicity、digestion_load 仍以 Option 直接写入 PlayerCultivationBundle。spawn_revival_action_actor 未挂这三个组件仍可完成多条正式复活测试,证明缺失持久化 sibling 不会被拒绝。这与 plan §4/§8.1 #3 的“完整 cultivation sibling bundle 任一缺失 fail-closed”不一致。"
}
]
}
] |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
docs/plan-satiety-hydration-v1.md (3)
236-237: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win将自动归档流程写成 §10 末尾的独立章节。
当前只是普通 bullet,且未明确最终归档到
docs/finished_plans/。请新增标题为“单次 consume-plan 全自动到 merge”的末尾章节,明确用户提交/consume-plan后可等待自动实施、合并并归档。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/plan-satiety-hydration-v1.md` around lines 236 - 237, 将 docs/plan-satiety-hydration-v1.md 中现有的 consume-plan bullet 移除,并在 §10 末尾新增标题“单次 consume-plan 全自动到 merge”的独立章节,明确用户提交 `/consume-plan` 后可等待流程自动实施、合并,并最终归档至 `docs/finished_plans/`。Source: Coding guidelines
233-235: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win补齐 atomic commit 与
Model:trailer 要求。工作流只写了“atomic”,没有明确每个逻辑单元使用中文 commit,也没有要求 agent 产生的 commit 携带真实模型 ID 的
Model:trailer;请将这两项加入 §10。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/plan-satiety-hydration-v1.md` around lines 233 - 235, 更新 §10 的工作流要求,明确每个逻辑单元必须使用中文提交信息并保持 atomic commit;同时要求 agent 创建的每个 commit 携带真实模型 ID 的 Model: trailer。保留现有相关提交规范,确保两项要求纳入 §10 而非仅停留在其他章节。Source: Coding guidelines
107-109: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win统一 nourish-only 物品消费入口到 fresh/consume/clamp 事务。
apply_cast_item_effect目前仍按ItemEffect分派,而tick_casts_or_interrupt的扣货判定也落在ItemEffect::FoodRegen内。如果只写[item.nourish]或附带非消费效果的食物/饮水,会绕过 freshness、扣物和原子返还。应在计划中明确NourishProfile优先于ItemEffect的统一消费入口,并增加 nourish-only、CriticalBlock、负 nourish delta 的 pin test。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/plan-satiety-hydration-v1.md` around lines 107 - 109, 统一 apply_cast_item_effect 与 tick_casts_or_interrupt 的食物/饮水处理,改为以 NourishProfile 为优先的 fresh/consume/clamp 消费事务,而非仅匹配 ItemEffect::FoodRegen;确保 nourish-only 及带非消费效果的物品也经过 freshness、施法校验和成功扣除后再原子更新食水,CriticalBlock 不扣物不加值,负 nourish delta 合法且结果 clamp 到 0..120。补充覆盖 nourish-only、CriticalBlock、负 delta 及 100 以上可消费至 120 的 pin test。server/src/combat/lifecycle.rs (1)
1716-1758: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift40 个位置参数中存在大量同型相邻参数,建议按职责打包成具名结构体。
nourishment/nourishment_activity之外,layer_id/visible_chunk_layer/visible_entity_layers、以及 10 个Option<&…>培养切片彼此类型不同但极易在调用点错位;一旦两个同型参数互换(例如两个Option<&PoisonToxicity>-like 切片扩展后),编译器无法报错,只会静默写错持久化 bundle。建议拆成DimensionRuntimeMut、RevivalCultivationRefs、CoffinRuntimeCtx三组具名结构体,调用点按字段名传递。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/combat/lifecycle.rs` around lines 1716 - 1758, Refactor revive_lifecycle to replace its long positional parameter list with three named context structs: DimensionRuntimeMut for layer/visibility/dimension runtime state, RevivalCultivationRefs for the cultivation-related Option references, and CoffinRuntimeCtx for coffin commands, registry, events, username, and persistence. Update revive_lifecycle and every call site to construct and pass these structs by field name, preserving existing behavior while preventing same-typed parameters from being swapped.server/src/player/mod.rs (1)
424-446: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win三条落盘路径的 17 参调用完全重复,建议抽一个 helper 承载。
despawn_disconnected_clients、flush_connected_players_on_shutdown、autosave_player_cultivation_bundles都在做同一件事:从CultivationSnapshotQueryItem解构 →severed.cloned().unwrap_or_default()→ 按固定顺序传 17 个参数。本 PR 新增nourishment就需要同步改三处;下一次新增 slice 同样如此,漏改任一处就会出现「断线保存了新字段、autosave 没保存」这类只在特定路径复现的缺陷。建议抽一个persist_cultivation_snapshot(&settings, username, item) -> io::Result<()>,三处只调用它。Also applies to: 589-611, 763-786
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/player/mod.rs` around lines 424 - 446, 抽取统一的 persist_cultivation_snapshot 辅助函数,接收 settings、username 和 CultivationSnapshotQueryItem,集中完成字段解构、severed.cloned().unwrap_or_default() 处理及 17 参数的持久化调用。更新 despawn_disconnected_clients、flush_connected_players_on_shutdown 和 autosave_player_cultivation_bundles,使三条路径仅调用该 helper,并保持现有错误处理行为不变。
♻️ Duplicate comments (2)
docs/plan-satiety-hydration-v1.md (2)
119-121: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win继续统一
water_skin_filled的类别契约。计划要求使用
Misc,但现有server/src/inventory/mod.rsfixture 仍注册为ItemCategory::Liquid;Liquid不在允许类别列表中。请把 fixture、生产模板和 pin tests 一并迁移到Misc。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/plan-satiety-hydration-v1.md` around lines 119 - 121, 将 water_skin_filled 的类别统一迁移为 ItemCategory::Misc:更新 inventory/mod.rs 中的 test_template fixture、生产物品模板注册及相关 pin tests,移除对 ItemCategory::Liquid 的使用;保持 water_skin_filled 的其他属性与测试覆盖不变。Source: Coding guidelines
91-91: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win移除 P0 中的
vomit.rs。这里仍将
server/src/nourishment/vomit.rs列为 P0,但 §10 明确呕吐属于 PR-2/P1;请统一 P0 文件边界,避免 PR-1 的完成条件与实施范围冲突。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/plan-satiety-hydration-v1.md` at line 91, 将 P0 文件范围中的 `server/src/nourishment/vomit.rs` 从该条目移除,仅保留 `mod.rs`、`tick.rs`、`effects.rs` 及相关 `Nourishment`、`NourishBand`、`band_of` 和常数表内容;保持 §10 中 PR-2/P1 的呕吐实现归属不变。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/plan-satiety-hydration-v1.md`:
- Line 93: 更新复活真元回流描述,明确成功路径必须调用 qi_release_to_zone,并通过
qi_physics::ledger::QiTransfer { from, to, amount, reason }
记账,禁止直接修改余额;同时加入回流前后的守恒断言,并规定缺少 ZoneRegistry/WorldQiAccount、转移失败或 precommit
失败时保持旧状态、不发布 QiTransfer 或 completion event,支持显式重试。同步修改对应的另一处复活计划描述。
In `@server/src/coffin/mod.rs`:
- Line 319: 在包含 handle_sneak_leave_requests 与 handle_coffin_leave_requests 的
Update 调度配置中,显式声明 handle_coffin_leave_requests 在 handle_sneak_leave_requests
之后执行;保留两者现有逻辑,仅补充发送者到接收者的执行顺序约束,避免请求延迟一帧。
In `@server/src/combat/lifecycle.rs`:
- Around line 3017-3022: Update RevivalObservation to derive
valence::prelude::Resource alongside its existing derives, then remove the
manual valence::prelude::Resource implementation. Preserve the current fields
and Default/Debug derives.
- Around line 2001-2041: Update publish_overworld_runtime to accept
&DimensionLayers directly, remove the unreachable None guard and warning, and
continue using layers.overworld. Extract the duplicated VisibleEntityLayers
update/insert logic outside the layer_id match, while preserving the existing
behavior for both present and absent EntityLayerId components.
- Around line 3831-3851: Update the runtime_zone_qi and persisted_zone_qi
assertions in the revival transaction test to use a magnitude-appropriate
tolerance, such as 1e-9 or an equivalent relative comparison, instead of
f64::EPSILON. Preserve the existing expected_zone_qi calculation and
conservation checks while allowing harmless floating-point operation-order
differences.
- Around line 1774-1781: Update revive_lifecycle to allow revival when
Nourishment or NourishmentActivityWindow is missing instead of returning false.
At the successful runtime publication/reset stage, reset present components and
insert Nourishment::spawn_default() or NourishmentActivityWindow::default() for
missing components, matching the fallback behavior in reset_for_new_character.
In `@server/src/cultivation/mod.rs`:
- Around line 977-990: 更新转世处理流程中 default loadout 初始化和
persist_new_character_transition 失败的两个 continue 分支:不要让实体在
CultivationAttachFilter 失效后继续保持残缺会话;失败时主动断开对应客户端,或设置可由下一帧重试的标记。保持 staged
状态不落盘,并确保失败收口发生在跳过 entity_commands 之前。
In `@server/src/nourishment/mod.rs`:
- Around line 529-553: 将 persist_player_cultivation_bundle_with_nourishment
的长参数列表收敛为命名结构体 CultivationPersistBundle<'_>,并更新该函数及其所有调用点。按字段构造 bundle,尤其明确尾部各
Option 和 nourishment 字段,确保现有持久化映射不变;后续新增数据仅通过新增结构体字段传递。
In `@server/src/nourishment/tick.rs`:
- Around line 160-181: 将包含硬性 &Cultivation 的 players 查询改为可选
Cultivation,使缺少该组件的客户端仍进入循环;在 sweep_losses 中使用 cultivation.realm 对应倍率,缺失时回退为
1.0,并继续执行 nourishment.apply_loss 与 activity_window.clear_flags_after_sweep。
In `@server/src/player/state.rs`:
- Around line 1137-1141: 收紧 set_current_character_id 的可见性为 pub(crate),或在写入
player_core.current_char_id 前复用现有 UUID 解析校验,拒绝无效值并保持 io::Result<()> 错误语义;确保该函数与
import_player_bundle_rejects_invalid_current_char_id 一致地维护 current_char_id 必须为
UUID 的不变量。
- Around line 812-851: 在 upsert_new_character_player_slices_in_transaction 中,将
player_lifecycle 与其他玩家切片一起在同一事务内重置为
Lifecycle::default(),覆盖旧用户名对应的生命周期状态。确保新角色创建时不会继承旧角色的 AwaitingRevival
等死亡状态,并保持现有 player_lifespan、player_shrine 及 craft session 的事务流程不变。
---
Outside diff comments:
In `@docs/plan-satiety-hydration-v1.md`:
- Around line 236-237: 将 docs/plan-satiety-hydration-v1.md 中现有的 consume-plan
bullet 移除,并在 §10 末尾新增标题“单次 consume-plan 全自动到 merge”的独立章节,明确用户提交 `/consume-plan`
后可等待流程自动实施、合并,并最终归档至 `docs/finished_plans/`。
- Around line 233-235: 更新 §10 的工作流要求,明确每个逻辑单元必须使用中文提交信息并保持 atomic commit;同时要求
agent 创建的每个 commit 携带真实模型 ID 的 Model: trailer。保留现有相关提交规范,确保两项要求纳入 §10
而非仅停留在其他章节。
- Around line 107-109: 统一 apply_cast_item_effect 与 tick_casts_or_interrupt
的食物/饮水处理,改为以 NourishProfile 为优先的 fresh/consume/clamp 消费事务,而非仅匹配
ItemEffect::FoodRegen;确保 nourish-only 及带非消费效果的物品也经过
freshness、施法校验和成功扣除后再原子更新食水,CriticalBlock 不扣物不加值,负 nourish delta 合法且结果 clamp 到
0..120。补充覆盖 nourish-only、CriticalBlock、负 delta 及 100 以上可消费至 120 的 pin test。
In `@server/src/combat/lifecycle.rs`:
- Around line 1716-1758: Refactor revive_lifecycle to replace its long
positional parameter list with three named context structs: DimensionRuntimeMut
for layer/visibility/dimension runtime state, RevivalCultivationRefs for the
cultivation-related Option references, and CoffinRuntimeCtx for coffin commands,
registry, events, username, and persistence. Update revive_lifecycle and every
call site to construct and pass these structs by field name, preserving existing
behavior while preventing same-typed parameters from being swapped.
In `@server/src/player/mod.rs`:
- Around line 424-446: 抽取统一的 persist_cultivation_snapshot 辅助函数,接收
settings、username 和
CultivationSnapshotQueryItem,集中完成字段解构、severed.cloned().unwrap_or_default() 处理及
17 参数的持久化调用。更新 despawn_disconnected_clients、flush_connected_players_on_shutdown
和 autosave_player_cultivation_bundles,使三条路径仅调用该 helper,并保持现有错误处理行为不变。
---
Duplicate comments:
In `@docs/plan-satiety-hydration-v1.md`:
- Around line 119-121: 将 water_skin_filled 的类别统一迁移为 ItemCategory::Misc:更新
inventory/mod.rs 中的 test_template fixture、生产物品模板注册及相关 pin tests,移除对
ItemCategory::Liquid 的使用;保持 water_skin_filled 的其他属性与测试覆盖不变。
- Line 91: 将 P0 文件范围中的 `server/src/nourishment/vomit.rs` 从该条目移除,仅保留
`mod.rs`、`tick.rs`、`effects.rs` 及相关 `Nourishment`、`NourishBand`、`band_of`
和常数表内容;保持 §10 中 PR-2/P1 的呕吐实现归属不变。
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 7985fff3-f049-463b-9aa3-3075690db790
📒 Files selected for processing (30)
docs/plan-satiety-hydration-v1.mdserver/src/cmd/dev/mod.rsserver/src/cmd/dev/nourish.rsserver/src/cmd/dev/revive.rsserver/src/cmd/registry_pin.rsserver/src/coffin/mod.rsserver/src/combat/baomai_v3/skills.rsserver/src/combat/dugu_v2/skills.rsserver/src/combat/lifecycle.rsserver/src/combat/woliu_v2/mod.rsserver/src/combat/woliu_v2/tests.rsserver/src/combat/woliu_v2/tick.rsserver/src/cultivation/death_hooks.rsserver/src/cultivation/epitaph.rsserver/src/cultivation/mod.rsserver/src/cultivation/realm_vision/push.rsserver/src/cultivation/tribulation.rsserver/src/cultivation/void/actions.rsserver/src/inventory/mod.rsserver/src/inventory/tsy_loot_integration_test.rsserver/src/lib.rsserver/src/main.rsserver/src/network/audio_trigger.rsserver/src/network/mod.rsserver/src/nourishment/mod.rsserver/src/nourishment/tick.rsserver/src/npc/lifecycle.rsserver/src/persistence/mod.rsserver/src/player/mod.rsserver/src/player/state.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: e2e
🧰 Additional context used
📓 Path-based instructions (9)
server/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
server/**/*.rs: Server Rust 代码提交前必须运行cargo fmt --check、cargo clippy --all-targets -- -D warnings和cargo test。
Dev harness 命令只能用于本地/dev 测试,显式绕过 worldview 修炼规则和 qi ledger 的入口不得复用于生产 gameplay 路径。
Files:
server/src/lib.rsserver/src/cultivation/realm_vision/push.rsserver/src/cultivation/epitaph.rsserver/src/combat/woliu_v2/mod.rsserver/src/npc/lifecycle.rsserver/src/cmd/dev/mod.rsserver/src/inventory/mod.rsserver/src/main.rsserver/src/network/mod.rsserver/src/cmd/registry_pin.rsserver/src/cultivation/void/actions.rsserver/src/combat/baomai_v3/skills.rsserver/src/cmd/dev/revive.rsserver/src/network/audio_trigger.rsserver/src/combat/dugu_v2/skills.rsserver/src/inventory/tsy_loot_integration_test.rsserver/src/combat/woliu_v2/tests.rsserver/src/coffin/mod.rsserver/src/combat/woliu_v2/tick.rsserver/src/cultivation/death_hooks.rsserver/src/cultivation/tribulation.rsserver/src/nourishment/tick.rsserver/src/player/state.rsserver/src/cmd/dev/nourish.rsserver/src/player/mod.rsserver/src/nourishment/mod.rsserver/src/persistence/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
server/src/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
server/src/**/*.rs: 所有真元/灵气流动必须通过qi_physics::ledger::QiTransfer { from, to, amount, reason },不得直接增减账户或区域数值。
测试灵气总量时必须引用SPIRIT_QI_TOTAL,不得硬编码100。
离屏战斗死亡必须调用release_dormant_qi_to_zone并通过ledger.transfer(ReleaseToZone)归还真元。
新增衰减、抽取或衰减率常数前必须检查并复用qi_physics;不存在时先扩展其 constants,禁止在业务 plan 中重复定义。
天道时代衰减必须使用qi_physics::tiandao::era_decay_step,并通过WorldQiBudget::apply_era_decay和assert_conservation追踪沉降槽。
不得使用 armor stand 或 invisible mob 作为碰撞箱/交互载体;实体必须使用 Marker+自定义渲染,交互通过 C2S 请求处理。
headless/CI 启服必须设置BONG_SKIP_SKIN_PREFETCH=1,避免缺少MINESKIN_API_KEY导致 panic。
Files:
server/src/lib.rsserver/src/cultivation/realm_vision/push.rsserver/src/cultivation/epitaph.rsserver/src/combat/woliu_v2/mod.rsserver/src/npc/lifecycle.rsserver/src/cmd/dev/mod.rsserver/src/inventory/mod.rsserver/src/main.rsserver/src/network/mod.rsserver/src/cmd/registry_pin.rsserver/src/cultivation/void/actions.rsserver/src/combat/baomai_v3/skills.rsserver/src/cmd/dev/revive.rsserver/src/network/audio_trigger.rsserver/src/combat/dugu_v2/skills.rsserver/src/inventory/tsy_loot_integration_test.rsserver/src/combat/woliu_v2/tests.rsserver/src/coffin/mod.rsserver/src/combat/woliu_v2/tick.rsserver/src/cultivation/death_hooks.rsserver/src/cultivation/tribulation.rsserver/src/nourishment/tick.rsserver/src/player/state.rsserver/src/cmd/dev/nourish.rsserver/src/player/mod.rsserver/src/nourishment/mod.rsserver/src/persistence/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
server/src/**/*.{rs,json}
📄 CodeRabbit inference engine (CLAUDE.md)
server/src/**/*.{rs,json}: 新增 zone 前必须核对docs/worldview.md区域表和server/zones.json中已有 ID。
ItemCategory只能使用 Pill、Herb、Scroll、Misc、Weapon、Armor、Tool、Treasure、RecipeFragment、RecipeHint、BoneCoin、Container;炼丹材料使用Misc,不得使用Material。
Files:
server/src/lib.rsserver/src/cultivation/realm_vision/push.rsserver/src/cultivation/epitaph.rsserver/src/combat/woliu_v2/mod.rsserver/src/npc/lifecycle.rsserver/src/cmd/dev/mod.rsserver/src/inventory/mod.rsserver/src/main.rsserver/src/network/mod.rsserver/src/cmd/registry_pin.rsserver/src/cultivation/void/actions.rsserver/src/combat/baomai_v3/skills.rsserver/src/cmd/dev/revive.rsserver/src/network/audio_trigger.rsserver/src/combat/dugu_v2/skills.rsserver/src/inventory/tsy_loot_integration_test.rsserver/src/combat/woliu_v2/tests.rsserver/src/coffin/mod.rsserver/src/combat/woliu_v2/tick.rsserver/src/cultivation/death_hooks.rsserver/src/cultivation/tribulation.rsserver/src/nourishment/tick.rsserver/src/player/state.rsserver/src/cmd/dev/nourish.rsserver/src/player/mod.rsserver/src/nourishment/mod.rsserver/src/persistence/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
**/*.{rs,ts,tsx,java,py}
📄 CodeRabbit inference engine (CLAUDE.md)
新增 skill/cast/主动能力必须同时提供独立 animation、particle/VFX、SFX、HUD 反馈和 hotbar/SkillBar PNG icon;仅实现 server 或 schema 不算完成。
Files:
server/src/lib.rsserver/src/cultivation/realm_vision/push.rsserver/src/cultivation/epitaph.rsserver/src/combat/woliu_v2/mod.rsserver/src/npc/lifecycle.rsserver/src/cmd/dev/mod.rsserver/src/inventory/mod.rsserver/src/main.rsserver/src/network/mod.rsserver/src/cmd/registry_pin.rsserver/src/cultivation/void/actions.rsserver/src/combat/baomai_v3/skills.rsserver/src/cmd/dev/revive.rsserver/src/network/audio_trigger.rsserver/src/combat/dugu_v2/skills.rsserver/src/inventory/tsy_loot_integration_test.rsserver/src/combat/woliu_v2/tests.rsserver/src/coffin/mod.rsserver/src/combat/woliu_v2/tick.rsserver/src/cultivation/death_hooks.rsserver/src/cultivation/tribulation.rsserver/src/nourishment/tick.rsserver/src/player/state.rsserver/src/cmd/dev/nourish.rsserver/src/player/mod.rsserver/src/nourishment/mod.rsserver/src/persistence/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
**/*.{rs,ts,tsx,java}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{rs,ts,tsx,java}: 六境界必须使用“醒灵→引气→凝脉→固元→通灵→化虚”,不得使用练气、筑基、金丹、元婴等旧称。
命名不得使用末法禁词玄、陨、星、仙、太、古,除明确允许的俗世矿名例外。
Files:
server/src/lib.rsserver/src/cultivation/realm_vision/push.rsserver/src/cultivation/epitaph.rsserver/src/combat/woliu_v2/mod.rsserver/src/npc/lifecycle.rsserver/src/cmd/dev/mod.rsserver/src/inventory/mod.rsserver/src/main.rsserver/src/network/mod.rsserver/src/cmd/registry_pin.rsserver/src/cultivation/void/actions.rsserver/src/combat/baomai_v3/skills.rsserver/src/cmd/dev/revive.rsserver/src/network/audio_trigger.rsserver/src/combat/dugu_v2/skills.rsserver/src/inventory/tsy_loot_integration_test.rsserver/src/combat/woliu_v2/tests.rsserver/src/coffin/mod.rsserver/src/combat/woliu_v2/tick.rsserver/src/cultivation/death_hooks.rsserver/src/cultivation/tribulation.rsserver/src/nourishment/tick.rsserver/src/player/state.rsserver/src/cmd/dev/nourish.rsserver/src/player/mod.rsserver/src/nourishment/mod.rsserver/src/persistence/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
**/*.{rs,ts,tsx,java,json,md}
📄 CodeRabbit inference engine (CLAUDE.md)
唯一真货币是骨币;矿物是交易筹码,灵石是燃料/衰变物,金银不是货币。
Files:
server/src/lib.rsserver/src/cultivation/realm_vision/push.rsserver/src/cultivation/epitaph.rsserver/src/combat/woliu_v2/mod.rsserver/src/npc/lifecycle.rsserver/src/cmd/dev/mod.rsserver/src/inventory/mod.rsserver/src/main.rsserver/src/network/mod.rsserver/src/cmd/registry_pin.rsserver/src/cultivation/void/actions.rsserver/src/combat/baomai_v3/skills.rsserver/src/cmd/dev/revive.rsserver/src/network/audio_trigger.rsserver/src/combat/dugu_v2/skills.rsserver/src/inventory/tsy_loot_integration_test.rsserver/src/combat/woliu_v2/tests.rsserver/src/coffin/mod.rsserver/src/combat/woliu_v2/tick.rsserver/src/cultivation/death_hooks.rsdocs/plan-satiety-hydration-v1.mdserver/src/cultivation/tribulation.rsserver/src/nourishment/tick.rsserver/src/player/state.rsserver/src/cmd/dev/nourish.rsserver/src/player/mod.rsserver/src/nourishment/mod.rsserver/src/persistence/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
**/*: 禁止git stash push后不执行对应git stash pop;不得留下孤儿 WIP stash。
每个逻辑单元使用中文 atomic commit;agent 产生的每个 commit 必须包含真实模型 ID 的Model:trailer。
未经明确确认不得执行 force push、hard reset、amend、交互式 rebase、批量删除/移动文件或依赖版本/生产配置修改;严禁--no-verify、--no-gpg-sign及关闭签名。
PR review 只能通过独立评论/review触发;不得等待 Codex,review 修改后必须重新等待 re-review。
Files:
server/src/lib.rsserver/src/cultivation/realm_vision/push.rsserver/src/cultivation/epitaph.rsserver/src/combat/woliu_v2/mod.rsserver/src/npc/lifecycle.rsserver/src/cmd/dev/mod.rsserver/src/inventory/mod.rsserver/src/main.rsserver/src/network/mod.rsserver/src/cmd/registry_pin.rsserver/src/cultivation/void/actions.rsserver/src/combat/baomai_v3/skills.rsserver/src/cmd/dev/revive.rsserver/src/network/audio_trigger.rsserver/src/combat/dugu_v2/skills.rsserver/src/inventory/tsy_loot_integration_test.rsserver/src/combat/woliu_v2/tests.rsserver/src/coffin/mod.rsserver/src/combat/woliu_v2/tick.rsserver/src/cultivation/death_hooks.rsdocs/plan-satiety-hydration-v1.mdserver/src/cultivation/tribulation.rsserver/src/nourishment/tick.rsserver/src/player/state.rsserver/src/cmd/dev/nourish.rsserver/src/player/mod.rsserver/src/nourishment/mod.rsserver/src/persistence/mod.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
docs/**/*.md
📄 CodeRabbit inference engine (docs/CLAUDE.md)
docs/**/*.md: 新 plan 头部必须写明接入面:进料、出料、复用的共享类型/event/schema、server/agent/client 跨仓库契约,以及对应的worldview.md锚点。
涉及真元、灵气、衰减、逸散、半衰、距离损耗、排斥或吸力的 plan,必须调用qi_physics;新物理常数和公式必须先扩展qi_physics,不得在功能 plan 中重复实现。
所有真元/灵气流动必须遵守守恒律并通过qi_physics::ledger::QiTransfer;释放使用qi_release_to_zone,吸收使用qi_excretion,不得凭空生成或销毁真元。
涉及玩家可感知行为的 plan,必须在对应机制阶段中内联可直接实现的粒子、音效、HUD、环境、动画和 narration 规格;不得将视听内容笼统推迟到独立阶段。纯 server 逻辑 plan 例外。
每份 plan 必须列出开放问题;实施前必须追加§N.1 决议,逐项给出结论、实施方案、边界条件,并以文件:行号和 plan 章节双锚点落地。
scope 大于或等于 4 个 PR 的 plan,必须在末尾包含§10 实施工作流,并按依赖顺序在一个 plan 内序列化多个 PR,不得拆成多个 plan。
涉及 NBT 建筑、worldgen layout 或复杂视觉资产的 TODO,必须完成三轮提交:(round 1/3)、(round 2/3)、(round 3/3);终轮提交必须包含拼写准确的<PROMISE>担保块。
§10 最末必须包含“单次 consume-plan 全自动到 merge”章节,明确用户提交/consume-plan后即可等待最终归档至docs/finished_plans/。一个 PR 只允许修改一个 plan;
/consume-plan只能追加 Finish Evidence 或执行允许的 plan 归档移动,不得自动修改其他 docs 文件、CLAUDE.md或 worldview 文档。
Files:
docs/plan-satiety-hydration-v1.md
docs/plan-*.md
📄 CodeRabbit inference engine (CLAUDE.md)
docs/plan-*.md: Active plan 每个阶段必须写出可核验的模块路径、类型/函数、测试、schema、Redis key 或跨仓库契约 symbol。
plan 归档前必须所有阶段标记为✅ YYYY-MM-DD并补充严格标题为## Finish Evidence的证据章节。
Files:
docs/plan-satiety-hydration-v1.md
🔇 Additional comments (68)
docs/plan-satiety-hydration-v1.md (1)
3-3: LGTM!Also applies to: 62-66, 95-95, 156-175, 194-210, 212-229
server/src/cultivation/realm_vision/push.rs (1)
230-230: LGTM!server/src/network/audio_trigger.rs (1)
2542-2543: LGTM!Also applies to: 2568-2569, 2656-2657
server/src/combat/lifecycle.rs (15)
137-182: 📐 Maintainability & Code Quality查询/资源元组继续加深嵌套,仍建议改用具名
QueryData/SystemParam结构体。
NearDeathPersistenceQueryItem现已到第 4 层嵌套(((_, (_, _, _)), (…10 项…))),RevivalRuntimeResources也是 9 元位置元组;两处解包点(near_death_tick、handle_revival_action_intents)必须逐位对齐,增删字段极易错位。此前已提过同一问题,这里只是范围扩大。
804-809: LGTM!
1060-1091: LGTM!
1154-1174: LGTM!
1342-1345: LGTM!
1627-1713: LGTM!
1786-1954: LGTM!
1972-1995: LGTM!Also applies to: 2058-2092
2296-2476: LGTM!Also applies to: 2478-2539
2943-2991: LGTM!Also applies to: 3024-3152
3154-3830: LGTM!Also applies to: 3852-4423
5369-5448: LGTM!Also applies to: 5605-5618
6503-6680: LGTM!Also applies to: 6714-6828, 7351-8927, 8940-9026, 9207-10117
5498-5499: 🩺 Stability & Availability无需修改:nourishment::register 已将 nourishment sweep 链入 handle_revival_action_intents 之前。
tick::tick_nourishment .before(crate::combat::lifecycle::handle_revival_action_intents)已建立该测试依赖的顺序关系,这里不必再在两次独立注册之间另行添加约束。> Likely an incorrect or invalid review comment.
2265-2294: 🩺 Stability & Availability无需修改:生产启动路径已注册这些必需资源。
build_server_app()顺序调用了world::register()和cultivation::register(),其中已分别插入TutorialTelemetry、DimensionLayers、PendingJueBiTriggers和HalfStepRechallengeQueue,新建角色不会因此被拒绝。server/src/player/state.rs (4)
680-811: LGTM!
854-908: LGTM!
1128-1135: LGTM!Also applies to: 1142-1172
4372-4428: LGTM!server/src/persistence/mod.rs (1)
212-230: LGTM!Also applies to: 3374-3403, 6236-6273, 6275-6313, 6405-6485, 6613-6662, 10732-11017
server/src/player/mod.rs (1)
28-29: LGTM!Also applies to: 94-116, 382-382, 551-551, 1249-1291, 1486-1571, 1843-1909
server/src/cultivation/mod.rs (1)
97-101: LGTM!Also applies to: 201-221, 376-377, 439-442, 486-489, 563-574, 585-618, 704-704, 788-795, 952-959, 1071-1082, 1093-1198, 1964-2995, 3770-3912, 4087-4113, 4208-4221
server/src/cmd/dev/mod.rs (2)
84-84:nourish::register位于register_for_dev_mode的无条件区段(仅ambient_spawn受dev_mode_enabled门控),因此/nourish set在生产模式下同样对普通在线玩家开放,可直接改写饱食/水分并绕过自然消耗。此前评审已就/nourish缺少 dev 权限门控提出过同一问题,根因在此注册点。As per coding guidelines, "Dev harness 命令只能用于本地/dev 测试,显式绕过 worldview 修炼规则和 qi ledger 的入口不得复用于生产 gameplay 路径"。
Source: Coding guidelines
17-17: LGTM!server/src/lib.rs (1)
87-87: LGTM!server/src/nourishment/mod.rs (5)
8-152: LGTM!Also applies to: 154-189
216-238: LGTM!Also applies to: 240-379
381-425: LGTM!Also applies to: 428-522, 629-731
194-209: 🩺 Stability & Availability无需修改。
tick_combat_clock已在server/src/combat/mod.rs和server/src/network/mod.rs的生产启动路径中注册到Update,没有 debug/dev 门控,营养扫荡的顺序约束可生效。> Likely an incorrect or invalid review comment.
193-193: 🩺 Stability & Availability无需修改
add_event::<MovementEvent>()。
nourishment::register和movement::register不会注册同一 event,valence 未注册该 event,重复注册不会丢失同帧事件。> Likely an incorrect or invalid review comment.server/src/nourishment/tick.rs (6)
20-71: LGTM!
73-79: LGTM!Also applies to: 82-102
104-141: LGTM!
143-155: LGTM!
183-233: LGTM!Also applies to: 235-463, 465-511
81-81: 🎯 Functional Correctness无需修改:
u64::is_multiple_of与当前 Rust 配置兼容。
server/rust-toolchain.toml已固定channel = "stable"且Cargo.toml未声明更高的rust-version,该 API 在现代 Rust stable 上可用。server/src/main.rs (1)
14-16: LGTM!Also applies to: 131-131
server/src/cmd/dev/nourish.rs (4)
50-107: LGTM!
109-183: LGTM!
184-385: LGTM!
28-41: 🩺 Stability & Availability需要查阅具体依赖版本,暂时无法确认
f32::parse_arg与with_parser::<f32>()是否完全等价。server/src/cmd/registry_pin.rs (1)
22-22: LGTM!Also applies to: 99-101, 190-190
server/src/cmd/dev/revive.rs (1)
52-52: LGTM!server/src/coffin/mod.rs (1)
348-362: LGTM!server/src/combat/baomai_v3/skills.rs (1)
590-592: LGTM!server/src/combat/dugu_v2/skills.rs (1)
551-553: LGTM!server/src/combat/woliu_v2/mod.rs (1)
35-37: LGTM!server/src/network/mod.rs (1)
937-940: LGTM!server/src/npc/lifecycle.rs (1)
580-581: LGTM!server/src/cultivation/void/actions.rs (1)
277-283: LGTM!server/src/combat/woliu_v2/tests.rs (2)
4-6: LGTM!Also applies to: 132-132
1786-1828: LGTM!server/src/combat/woliu_v2/tick.rs (2)
223-231: 🎯 Functional Correctness确认所有可触发心脏浩劫 tick 的实体均带
Lifecycle组件。
HeartActiveBackfireItem把&'a Lifecycle设为非Option必需字段,Bevy 的查询语义是:缺少该组件的实体会被整体排除出迭代,而不是进入循环体后被lifecycle.state != Alive分支跳过。测试通过给spawn_actor补上Lifecycle::default()才使测试继续通过,说明这条约束是新增的隐性前提。如果未来有非玩家实体(傀儡/召唤物等)也能触发WoliuSkillId::Heart,但未被赋予Lifecycle,其浩劫结算会被静默永久跳过(不会有告警),而不是走等价的"非 Alive 跳过"分支。请确认 Woliu 战斗系统目前及可预见的调用方是否都保证挂载
Lifecycle(例如是否有 NPC/傀儡也能释放该技能)。Also applies to: 280-286
308-308: LGTM!server/src/cultivation/death_hooks.rs (4)
3-5: LGTM!Also applies to: 21-21, 50-77, 154-173, 184-235
593-594: LGTM!Also applies to: 609-673, 713-714, 734-734, 762-768, 780-831
137-225: 🗄️ Data Integrity & Integration补充:此 comment 不再作为可执行 issue 保留。
PlayerRevived的生产路径只在正式复活的持久化成功后发送;dev 路径在同一 tick 立即先状态复位再发送,当前代码没有可被延迟事件“命中新角色”的调用链依据。> Likely an incorrect or invalid review comment.
247-256: 🎯 Functional Correctness无需修改。 当前
PlayerTerminated相关守护逻辑没有匹配到现有生产事件,不存在可据此跳过合法死亡清算的证据。server/src/cultivation/epitaph.rs (1)
349-356: LGTM!server/src/inventory/mod.rs (2)
883-897: LGTM!
12058-12059: LGTM!Also applies to: 12099-12100, 12171-12172
server/src/inventory/tsy_loot_integration_test.rs (1)
444-456: LGTM!Also applies to: 468-500, 556-578, 588-612
server/src/cultivation/tribulation.rs (3)
269-320: LGTM!Also applies to: 757-761, 1178-1315, 2340-2392
7280-7515: LGTM!Also applies to: 9506-9529, 10460-10608
269-280: 🗄️ Data Integrity & Integration无需修改。 现有
JueBiTriggerEvent生产者在玩家路径上都会捕获当前Lifecycle.character_id(通过Some(...)或.map(...)),非玩家构造点也已显式传入预期角色 ID。
| let (Some(mut nourishment), Some(mut nourishment_activity)) = | ||
| (nourishment, nourishment_activity) | ||
| else { | ||
| tracing::warn!( | ||
| "[bong][combat] refusing revival for {entity:?}: Nourishment or NourishmentActivityWindow is missing" | ||
| ); | ||
| return false; | ||
| }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Nourishment 缺失时拒绝复活会让玩家永久卡在死亡屏,无自愈路径。
revive_lifecycle 后续本来就无条件执行 nourishment.reset_to_spawn() / nourishment_activity.reset()(Line 1991-1992),并且持久化用的是本地构造的 staged_nourishment = Nourishment::spawn_default(),与 ECS 上的旧值无关。也就是说这两个组件的现值对复活结果毫无影响,缺失时却把整条复活拒掉。
结合 Line 1345 的一次性 auto-confirm:若某个实体(老存档 hydrate 遗漏、attach 顺序问题)永久缺少 Nourishment,玩家将永远停在 AwaitingRevival——手动重试也每次命中同一 gate,本层没有任何补插组件的路径。
建议改为按缺失回填后继续,与 reset_for_new_character(Line 2465-2476)的 insert 兜底保持一致。
🛡️ 建议改为回填而非拒绝
- let (Some(mut nourishment), Some(mut nourishment_activity)) =
- (nourishment, nourishment_activity)
- else {
- tracing::warn!(
- "[bong][combat] refusing revival for {entity:?}: Nourishment or NourishmentActivityWindow is missing"
- );
- return false;
- };改为在提交成功后的运行时发布阶段(Line 1991 附近)按 Option 分支处理:
match nourishment {
Some(mut nourishment) => nourishment.reset_to_spawn(),
None => {
commands.entity(entity).insert(Nourishment::spawn_default());
}
}
match nourishment_activity {
Some(mut activity) => activity.reset(),
None => {
commands
.entity(entity)
.insert(NourishmentActivityWindow::default());
}
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/combat/lifecycle.rs` around lines 1774 - 1781, Update
revive_lifecycle to allow revival when Nourishment or NourishmentActivityWindow
is missing instead of returning false. At the successful runtime
publication/reset stage, reset present components and insert
Nourishment::spawn_default() or NourishmentActivityWindow::default() for missing
components, matching the fallback behavior in reset_for_new_character.
| fn publish_overworld_runtime( | ||
| entity: Entity, | ||
| commands: &mut Commands, | ||
| layer_id: Option<valence::prelude::Mut<'_, EntityLayerId>>, | ||
| visible_chunk_layer: Option<valence::prelude::Mut<'_, VisibleChunkLayer>>, | ||
| visible_entity_layers: Option<valence::prelude::Mut<'_, VisibleEntityLayers>>, | ||
| current_dimension: Option<valence::prelude::Mut<'_, CurrentDimension>>, | ||
| dimension_layers: Option<&DimensionLayers>, | ||
| ) { | ||
| let Some(layers) = dimension_layers else { | ||
| tracing::warn!( | ||
| "[bong][combat] refusing Overworld runtime publication for {entity:?}: DimensionLayers resource is missing" | ||
| ); | ||
| return; | ||
| }; | ||
| let overworld = layers.overworld; | ||
|
|
||
| match layer_id { | ||
| Some(mut layer_id) => { | ||
| layer_id.0 = overworld; | ||
| if let Some(mut visible_layers) = visible_entity_layers { | ||
| visible_layers.0.clear(); | ||
| visible_layers.0.insert(overworld); | ||
| } else { | ||
| let mut visible_layers = VisibleEntityLayers::default(); | ||
| visible_layers.0.insert(overworld); | ||
| commands.entity(entity).insert(visible_layers); | ||
| } | ||
| } | ||
| None => { | ||
| commands.entity(entity).insert(EntityLayerId(overworld)); | ||
| if let Some(mut visible_layers) = visible_entity_layers { | ||
| visible_layers.0.clear(); | ||
| visible_layers.0.insert(overworld); | ||
| } else { | ||
| let mut visible_layers = VisibleEntityLayers::default(); | ||
| visible_layers.0.insert(overworld); | ||
| commands.entity(entity).insert(visible_layers); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
dimension_layers: Option<…> 分支已不可达,且两个 layer_id 分支里的可见层处理完全重复。
两个调用点(Line 1983、2434)都在外层 gate 过非 None 后才传 Some(dimension_layers),Line 2010-2015 的 warn 分支永远不会执行。把参数改成 &DimensionLayers 可直接消掉这段死分支;同时 Some/None 两个分支中的 VisibleEntityLayers 处理逐字重复,可提到 match 外。
♻️ 建议改写
fn publish_overworld_runtime(
entity: Entity,
commands: &mut Commands,
layer_id: Option<valence::prelude::Mut<'_, EntityLayerId>>,
visible_chunk_layer: Option<valence::prelude::Mut<'_, VisibleChunkLayer>>,
visible_entity_layers: Option<valence::prelude::Mut<'_, VisibleEntityLayers>>,
current_dimension: Option<valence::prelude::Mut<'_, CurrentDimension>>,
- dimension_layers: Option<&DimensionLayers>,
+ layers: &DimensionLayers,
) {
- let Some(layers) = dimension_layers else {
- tracing::warn!(
- "[bong][combat] refusing Overworld runtime publication for {entity:?}: DimensionLayers resource is missing"
- );
- return;
- };
let overworld = layers.overworld;
- match layer_id {
- Some(mut layer_id) => {
- layer_id.0 = overworld;
- if let Some(mut visible_layers) = visible_entity_layers {
- visible_layers.0.clear();
- visible_layers.0.insert(overworld);
- } else {
- let mut visible_layers = VisibleEntityLayers::default();
- visible_layers.0.insert(overworld);
- commands.entity(entity).insert(visible_layers);
- }
- }
- None => {
- commands.entity(entity).insert(EntityLayerId(overworld));
- if let Some(mut visible_layers) = visible_entity_layers {
- visible_layers.0.clear();
- visible_layers.0.insert(overworld);
- } else {
- let mut visible_layers = VisibleEntityLayers::default();
- visible_layers.0.insert(overworld);
- commands.entity(entity).insert(visible_layers);
- }
- }
- }
+ match layer_id {
+ Some(mut layer_id) => layer_id.0 = overworld,
+ None => {
+ commands.entity(entity).insert(EntityLayerId(overworld));
+ }
+ }
+ match visible_entity_layers {
+ Some(mut visible_layers) => {
+ visible_layers.0.clear();
+ visible_layers.0.insert(overworld);
+ }
+ None => {
+ let mut visible_layers = VisibleEntityLayers::default();
+ visible_layers.0.insert(overworld);
+ commands.entity(entity).insert(visible_layers);
+ }
+ }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/combat/lifecycle.rs` around lines 2001 - 2041, Update
publish_overworld_runtime to accept &DimensionLayers directly, remove the
unreachable None guard and warning, and continue using layers.overworld. Extract
the duplicated VisibleEntityLayers update/insert logic outside the layer_id
match, while preserving the existing behavior for both present and absent
EntityLayerId components.
| #[derive(Debug, Default)] | ||
| struct RevivalObservation { | ||
| snapshots: Vec<(Entity, Nourishment, NourishmentActivityWindow)>, | ||
| } | ||
|
|
||
| impl valence::prelude::Resource for RevivalObservation {} |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
🧩 Analysis chain
🌐 Web query:
Bevy ECS Resource trait derive macro vs manual impl recommended
💡 Result:
In Bevy, the use of the #[derive(Resource)] macro is the standard, recommended approach for implementing the Resource trait [1][2]. Since Bevy 0.9, the Resource trait has been an opt-in trait, meaning it is no longer automatically implemented for all types that satisfy the necessary bounds [3]. Manually implementing the Resource trait is generally unnecessary and discouraged for standard use cases [3]. The derive macro is preferred for several reasons: - Safety and Clarity: It ensures that the type explicitly opts into being used as a singleton resource, preventing accidental usage or silent conflicts with other types [3]. - Compile-time Validation: The macro enforces that the type meets the required Send, Sync, and 'static bounds [4][5]. If a type does not meet these requirements, the derive macro will provide a clear diagnostic error [6]. - Future-proofing: Using the derive macro allows the Bevy developers to add future customization options to the trait without breaking existing code [3]. If you are working with a third-party type that does not implement Resource, the recommended practice is to wrap it in a tuple struct and derive Resource for that wrapper, rather than attempting to manually implement the trait for the foreign type (which would be restricted by Rust's orphan rules) [3]. You can also derive Deref and DerefMut on your wrapper struct to maintain ergonomic access to the underlying data [3].
Citations:
- 1: https://docs.rs/bevy/latest/bevy/prelude/derive.Resource.html
- 2: https://docs.rs/bevy_ecs/latest/bevy_ecs/resource/derive.Resource.html
- 3: Make
Resourcetrait opt-in, requiring#[derive(Resource)]bevyengine/bevy#5007 - 4: https://docs.rs/bevy/latest/bevy/ecs/prelude/trait.Resource.html
- 5: https://docs.rs/bevy_ecs/latest/bevy_ecs/resource/trait.Resource.html
- 6: https://github.com/bevyengine/bevy/blob/main/crates/bevy_ecs/src/resource.rs
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the local declaration and any project-wide pattern for Resource derives.
printf '--- server/src/combat/lifecycle.rs relevant lines ---\n'
sed -n '3008,3024p' server/src/combat/lifecycle.rs
printf '\n--- project-wide Resource trait implementations ---\n'
rg -n "impl\s+.*\bResource\s+for|derive\(Resource\)" . || true
printf '\n--- valence prelude Resource import/source references ---\n'
rg -n "pub use .*Resource|mod resource|pub trait Resource|Resource" -g '* Cargo.toml' -g '*.rs' . | head -80 || trueRepository: Kizunad/Bong
Length of output: 241
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the local declaration and any project-wide pattern for Resource implementations.
printf '%s\n' '--- server/src/combat/lifecycle.rs relevant lines ---'
sed -n '3008,3024p' server/src/combat/lifecycle.rs
printf '%s\n' ''
printf '%s\n' '--- project-wide Resource trait implementations ---'
rg -n "impl\s+.*\bResource\s+for|derive\(Resource\)" . || true
printf '%s\n' ''
printf '%s\n' '--- valence prelude Resource import/source references ---'
rg -n "prelude::Resource|Resource" -g '* Cargo.toml' -g '*.rs' . | head -80 || trueRepository: Kizunad/Bong
Length of output: 19326
改用 derive Resource,避免手写空资源 impl 的可选 refactor。
RevivalObservation 满足 Bevy Resource 要求,可以加 #[derive(Resource)];手动 impl valence::prelude::Resource for RevivalObservation {} 虽能编译,但项目内更多 Resource 已使用 derive 风格,保持统一可减少未来宏增强带来的不一致。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/combat/lifecycle.rs` around lines 3017 - 3022, Update
RevivalObservation to derive valence::prelude::Resource alongside its existing
derives, then remove the manual valence::prelude::Resource implementation.
Preserve the current fields and Default/Debug derives.
| crate::persistence::persist_player_cultivation_bundle_with_nourishment( | ||
| &settings, | ||
| "JoinAtBoundary", | ||
| &crate::cultivation::components::Cultivation { | ||
| realm: crate::cultivation::components::Realm::Awaken, | ||
| ..Default::default() | ||
| }, | ||
| &crate::cultivation::components::MeridianSystem::default(), | ||
| &crate::cultivation::components::QiColor::default(), | ||
| &crate::cultivation::components::Karma::default(), | ||
| &crate::cultivation::components::Contamination::default(), | ||
| &crate::cultivation::life_record::LifeRecord::new( | ||
| "player:join-at-boundary".to_string(), | ||
| ), | ||
| &crate::cultivation::color::PracticeLog::default(), | ||
| &crate::cultivation::insight::InsightQuota::default(), | ||
| &crate::cultivation::insight_apply::UnlockedPerceptions::default(), | ||
| &crate::cultivation::insight_apply::InsightModifiers::new(), | ||
| None, | ||
| &crate::cultivation::meridian::severed::MeridianSeveredPermanent::default(), | ||
| None, | ||
| None, | ||
| Some(&persisted_nourishment), | ||
| ) | ||
| .expect("persisted join nourishment should seed successfully"); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 查看该函数签名与所有调用点,评估收敛为结构体的成本
ast-grep run --lang rust --pattern 'pub fn persist_player_cultivation_bundle_with_nourishment($$$) { $$$ }' server/src/persistence/mod.rs
rg -nP -C3 'persist_player_cultivation_bundle(_with_nourishment)?\s*\(' server/srcRepository: Kizunad/Bong
Length of output: 9165
🏁 Script executed:
#!/bin/bash
# 精确查看 persistence 函数签名、参数列表长度,以及各调用点实参结构,用于判断是否真正存在不可被 Rust 类型系统捕获的相邻 Option 错位风险。
set -euo pipefail
echo "== function signatures =="
sed -n '6233,6735p' server/src/persistence/mod.rs | cat -n
echo "== call sites with call-start line number and arg block =="
python3 - <<'PY'
import re, pathlib
p=pathlib.Path('server/src/persistence/mod.rs')
text=p.read_text()
# locate tests/features in same file and persistence tests in other files from previous rg
targets=[
('server/src/cultivation/mod.rs', [1988,2040,2697,4089,428,593,768]),
('server/src/player/mod.rs', [428,593,768]),
('server/src/combat/lifecycle.rs', [2971]),
('server/src/nourishment/mod.rs', [529]),
('server/src/persistence/mod.rs', [10753,10891,10944]),
]
for fname, lines in targets:
if fname.startswith('server/src/combat/lifecycle.rs') or not pathlib.Path(fname).exists():
continue
txt=pathlib.Path(fname).read_text().splitlines()
print(f'-- {fname} --')
for i in lines:
line=txt[i-1]
if 'persist_player_cultivation_bundle_with_nourishment(' in line or 'persist_player_cultivation_bundle(' in line:
# accumulate until matching ) not inside
s=i-1
depth=0
start=line.find('(')
if start==-1:
print(f'no paren at {i}')
continue
collected=[]
while s < len(txt) and depth != 0 or (depth==0 and start!=-1):
collected.append(txt[s])
depth += txt[s].count('(') - txt[s].count(')')
s += 1
print(f'call at line {i}')
for j,ln in enumerate(collected, i):
print(f'{j}: {ln}')
PY
echo "== relevant call lines concise =="
rg -n --no-heading 'persist_player_cultivation_bundle( with_nourishment)?\s*\(' server/src | head -80
echo "== read-only Python parity check for adjacent Option params in call snippets where None/Some are literal =="
python3 - <<'PY'
import pathlib, re
for path in ['server/src/persistence/mod.rs','server/src/player/mod.rs','server/src/nourishment/mod.rs','server/src/cultivation/mod.rs','server/src/combat/lifecycle.rs']:
p=pathlib.Path(path)
if not p.exists():
continue
txt=p.read_text()
print(f'-- {path} --')
for match in re.finditer(r'persist_player_cultivation_bundle_with_nourishment\s*\(', txt):
start=match.start()
# find next 500 chars / until balanced )
i=txt.find('(', start)
depth=1
end=i+1
while end < len(txt):
op=txt[end]
if op == '(':
depth += 1
elif op == ')':
depth -= 1
if depth == 0:
break
end += 1
block=txt[i+1:end]
# count likely positional items by top-level comma (be safe only around call content)
args=[a.strip() for a in re.split(r',(?![^(]*\))', block)]
none_like=[(idx,a) for idx,a in enumerate(args) if re.match(r'^(None|Some\([^)]*\))$', a)]
if len(none_like) >= 3:
print(f'call around {txt[:start].count(chr(10)) + 1}: {len(args)} args')
for idx,a in none_like:
print(f'arg{x_idx}: {a}')
PYRepository: Kizunad/Bong
Length of output: 22020
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== precise call sites and adjacent params around each invocation =="
python3 - <<'PY'
import pathlib, re
files = [
'server/src/player/mod.rs',
'server/src/cultivation/mod.rs',
'server/src/combat/lifecycle.rs',
'server/src/nourishment/mod.rs',
'server/src/persistence/mod.rs',
]
for fname in files:
p = pathlib.Path('server/' + fname)
txt = p.read_text()
lines = txt.splitlines()
print(f'-- {fname} --')
for m in re.finditer(r'persist_player_cultivation_bundle_with_nourishment\s*\(', txt):
i = txt.find('(', m.start())
depth = 1
end = i + 1
while end < len(txt) and depth:
if txt[end] == '(':
depth += 1
elif txt[end] == ')':
depth -= 1
end += 1
block = txt[i+1:end]
args = [a.strip().splitlines() for a in re.split(r',(?![^()]*\))', block)]
print(f'call around line {txt[:m.start()].count(chr(10)) + 1}: {len(args)} args')
# inspect parameters known to be around the Option/None tail in persistence/mod.rs signature
param_names = [
'tutorial_state',
'meridian_severed',
'poison_toxicity',
'digestion_load',
'nourishment',
]
for idx, (arg, name) in enumerate(zip(args, param_names)):
text = '\n'.join(arg)
print(f'{name}: {text!r}')
PY
echo "== imports of Nourishment and field construction in target call =="
sed -n '520,556p' server/src/nourishment/mod.rs | cat -nRepository: Kizunad/Bong
Length of output: 761
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== precise call sites and adjacent params around each invocation =="
python3 - <<'PY'
import pathlib, re
files = [
'server/src/player/mod.rs',
'server/src/cultivation/mod.rs',
'server/src/combat/lifecycle.rs',
'server/src/nourishment/mod.rs',
'server/src/persistence/mod.rs',
]
for fname in files:
p = pathlib.Path(fname)
txt = p.read_text()
print(f'-- {fname} --')
for m in re.finditer(r'persist_player_cultivation_bundle_with_nourishment\s*\(', txt):
i = txt.find('(', m.start())
depth = 1
end = i + 1
while end < len(txt) and depth:
if txt[end] == '(':
depth += 1
elif txt[end] == ')':
depth -= 1
end += 1
block = txt[i+1:end]
args = [a.strip().splitlines() for a in re.split(r',(?![^()]*\))', block)]
print(f'call around line {txt[:m.start()].count(chr(10)) + 1}: {len(args)} args')
param_names = [
'tutorial_state',
'meridian_severed',
'poison_toxicity',
'digestion_load',
'nourishment',
]
for idx, (arg, name) in enumerate(zip(args, param_names)):
text = '\n'.join(arg)
print(f'{name}: {text!r}')
PY
echo "== imports of Nourishment and field construction in target call =="
sed -n '520,556p' server/src/nourishment/mod.rs | cat -nRepository: Kizunad/Bong
Length of output: 7500
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate all direct calls with line numbers =="
rg -n -C 12 'persist_player_cultivation_bundle_with_nourishment\s*\(' server/src
echo "== signature tail only =="
sed -n '6612,6724p' server/src/persistence/mod.rs | cat -nRepository: Kizunad/Bong
Length of output: 27806
将 persist_player_cultivation_bundle_with_nourishment 的参数收敛为命名结构体。
这个调用链已有多个 17+ 位置参数的调用点,尾部 Option/Some(...) 连续出现,错位后 Rust 不会报错却会写错持久化字段。引入 CultivationPersistBundle<'_> 并按字段传参,新增切片只需增字段,而不是继续堆参数。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/nourishment/mod.rs` around lines 529 - 553, 将
persist_player_cultivation_bundle_with_nourishment 的长参数列表收敛为命名结构体
CultivationPersistBundle<'_>,并更新该函数及其所有调用点。按字段构造 bundle,尤其明确尾部各 Option 和
nourishment 字段,确保现有持久化映射不变;后续新增数据仅通过新增结构体字段传递。
|
本地门禁状态:当前无红灯; 此前红灯为 assert_eq!(
victim_lifecycle.state,
crate::combat::components::LifecycleState::Terminated,
"NPC should skip NearDeath wait and go straight to Terminated"
);实际为 按安全隔离要求,本地未运行 |
There was a problem hiding this comment.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
server/src/combat/lifecycle.rs (1)
11414-11556: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value测试名与实际覆盖不一致:函数名声称 terminate,但实际发送
Reincarnate。
terminate_tribulation_failed_with_username_clears_sqlite_in_coffin发送的是RevivalActionKind::Reincarnate(依赖chance: 0.0使roll_rebirth必失后转入 terminate 分支),这本身是合理的路径构造,但名字读起来像直接发Terminateintent;同时该测试还额外承担了 precommit 失败 + 显式重试的原子性验证,已超出名字所述范围。建议重命名为failed_tribulation_reincarnate_retries_then_clears_sqlite_and_runtime_coffin之类,或把重试段拆成独立用例。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/combat/lifecycle.rs` around lines 11414 - 11556, Rename the test function containing the `RevivalActionKind::Reincarnate` intent and failpoint/retry assertions to reflect failed tribulation, reincarnation retry, and clearing durable/runtime coffin state, such as `failed_tribulation_reincarnate_retries_then_clears_sqlite_and_runtime_coffin`. Keep the existing test behavior unchanged; do not imply it directly sends a terminate intent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/plan-satiety-hydration-v1.md`:
- Around line 240-242: 在“单次 consume-plan 全自动到 merge”小节中逐项补充强制门禁:每个逻辑单元必须使用中文
atomic commit;agent commit 必须包含真实模型 ID 的“Model” trailer;PR review
只能由独立的“/review”触发;review 产生修改后必须重新等待 re-review。确保自动流程仅在所有门禁满足且复审完成后合并。
- Line 122: 补齐水囊灌装流程中 ClientRequestV1::WaterSkinFill 的网络契约:定义成功与失败 response
shape 及明确错误码,规定请求 ID
的幂等处理、重试行为和最终收敛规则;明确提交成功但响应丢失时客户端如何依据服务端权威库存恢复为满水囊状态,并增加覆盖该场景的重试测试。
In `@server/src/combat/lifecycle.rs`:
- Around line 4684-4693: 将断言中的 zone_tolerance 和 audit_tolerance
容差表达式提取为模块级具名常量,并为 16/8 系数添加注释,说明 spirit_qi 归一化存储及往返换算最多引入的 ulp
数量。更新相关断言以复用这些常量,保持现有容差计算与校验行为不变。
- Around line 2554-2564: 在处理 TerminationPlayerContext 的生命周期分支前,新增具名布尔条件(如
offline_simulation),明确检查 username、player_persistence 与 staged_qi_release
均为空;将该条件用于 identity-less 的 legacy 路径,并更新注释说明仅完全无身份且无正 qi 时才允许回退,其他部分身份继续进入
fail-closed 分支。
- Around line 2451-2656: Refactor terminate_lifecycle_with_death_context by
extracting the qi-release staging, player-bundle validation, and
persistence-path selection into a stage_termination_persistence(...) helper, and
extracting post-persistence runtime updates and event publication into
commit_termination_runtime(...). Centralize failure handling so every staging or
persistence error removes the staged biography entry exactly once, while
preserving the identity-less compatibility branch and existing persistence
behavior.
- Around line 2047-2115: Replace the 14-field tuple match in the revival staging
flow with per-field presence checks that collect each missing component’s name,
covering all staged cultivation lifecycle fields. If any required value is
absent, log the complete missing-field list and return false; otherwise preserve
the existing staged values and revival behavior.
- Around line 9535-10094: 将
create_new_character_precommit_failure_rolls_back_sqlite_and_ecs
拆分为多个按契约组织的测试,避免单个用例同时覆盖所有回滚行为;抽取共享的场景构造与失败触发 fixture,然后分别建立 ECS 回滚、SQLite
回滚、coffin 四件套回滚和 tribulation 运行时回滚用例。确保各用例仅保留对应断言,并复用同一 fixture 与 failpoint
设置逻辑。
- Around line 2685-2725: The reset_for_new_character function has too many
order-dependent parameters. Introduce NewCharacterRuntime and
NewCharacterResources structs grouping the runtime component options and
resource references, update reset_for_new_character to accept these structs, and
revise all call sites to construct them with named fields so parameter ordering
cannot cause silent mismatches.
- Around line 1287-1297: 在处理 CreateNewCharacter 的 deferred_creates 入队逻辑中,按
intent.entity 去重,避免同一实体连续多帧重复入队导致队列增长;保留已有的 Update 边界处理和其他 action 的 executable
流程不变。
In `@server/src/cultivation/mod.rs`:
- Around line 800-807: Separate deterministic validation failures in the
cultivation hydration flow from transient I/O failures before inserting
CultivationAttachRetry. For failures such as unknown race, lifecycle/termination
conflicts, identity mismatches, and bundle validation errors, avoid an
every-frame retry by recording a throttled next-attempt tick (or rejecting once
with an alert); retain immediate retry behavior for the I/O failures in the
existing load path. Update the retry-marker handling and the failure branches
around load_player_cultivation_bundle, load_player_lifecycle_slice, and
load_current_character_id accordingly.
In `@server/src/persistence/mod.rs`:
- Around line 8158-8229: 将 persist_termination_transition_inner 和
persist_life_record_death_insight 提交后的导出流程改为按当前 char_id 增量更新对应 snapshot,并仅增量重建
_index.json,避免调用 reconcile_public_deceased_exports 对全部记录执行 O(N) 写盘;保留
reconcile_public_deceased_exports 作为启动或显式兜底的全量路径,并在该全量路径中清理
deceased_public_dir() 下不再对应索引记录的旧 JSON 文件。
- Around line 8231-8279: Update atomic_replace_file so the parent-directory sync
after fs::rename is only performed on Unix via #[cfg(unix)], while preserving
file syncing and rename behavior on other platforms. Document the platform
limitation or provide an established Windows-compatible persistence mechanism;
ensure reconcile_public_deceased_exports can rebuild successfully on Windows
when directory fsync is unavailable.
- Around line 8502-8511: 删除未再被 Rust 调用的辅助函数 rollback_file
及其相关死代码,确保移除后不留下无效引用或不必要的导入。
In `@server/src/world/dimension.rs`:
- Around line 122-139: The PreserveUnrelatedLayers branch in
server/src/world/dimension.rs lines 122-139 must preserve unrelated layers when
previous_layer is None by removing only dimension layers, not clearing the set.
Replace the hardcoded layers.tsy removal at lines 131-131 with iteration over
all non-overworld layers derived from DimensionLayers, adding an all_layers() or
non_overworld_layers() helper if appropriate; both sites should be updated to
support future dimensions.
- Line 131: 更新 PreserveUnrelatedLayers 中对 visible_layers 的清理逻辑,不要仅硬编码移除
layers.tsy;遍历 DimensionLayers 的全部非 overworld 层并移除,或复用其新增的 all_layers()
接口,确保未来新增维度层时也不会残留可见性。
---
Outside diff comments:
In `@server/src/combat/lifecycle.rs`:
- Around line 11414-11556: Rename the test function containing the
`RevivalActionKind::Reincarnate` intent and failpoint/retry assertions to
reflect failed tribulation, reincarnation retry, and clearing durable/runtime
coffin state, such as
`failed_tribulation_reincarnate_retries_then_clears_sqlite_and_runtime_coffin`.
Keep the existing test behavior unchanged; do not imply it directly sends a
terminate intent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 9207ac9d-1f43-45ad-9ae3-700e0af01ac8
📒 Files selected for processing (22)
docs/plan-satiety-hydration-v1.mdserver/src/cmd/dev/mod.rsserver/src/cmd/dev/nourish.rsserver/src/cmd/mod.rsserver/src/coffin/mod.rsserver/src/combat/lifecycle.rsserver/src/combat/mod.rsserver/src/combat/tests.rsserver/src/cultivation/death_hooks.rsserver/src/cultivation/epitaph.rsserver/src/cultivation/mod.rsserver/src/inventory/mod.rsserver/src/network/audio_trigger.rsserver/src/network/inventory_snapshot_emit.rsserver/src/network/mod.rsserver/src/nourishment/mod.rsserver/src/nourishment/tick.rsserver/src/npc/lifecycle.rsserver/src/persistence/mod.rsserver/src/player/mod.rsserver/src/player/state.rsserver/src/world/dimension.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: e2e
🧰 Additional context used
📓 Path-based instructions (9)
server/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
server/**/*.rs: Server Rust 代码提交前必须运行cargo fmt --check、cargo clippy --all-targets -- -D warnings和cargo test。
Dev harness 命令只能用于本地/dev 测试,显式绕过 worldview 修炼规则和 qi ledger 的入口不得复用于生产 gameplay 路径。
Files:
server/src/cmd/mod.rsserver/src/cmd/dev/mod.rsserver/src/npc/lifecycle.rsserver/src/world/dimension.rsserver/src/network/inventory_snapshot_emit.rsserver/src/network/mod.rsserver/src/cultivation/epitaph.rsserver/src/coffin/mod.rsserver/src/network/audio_trigger.rsserver/src/inventory/mod.rsserver/src/combat/mod.rsserver/src/combat/tests.rsserver/src/player/mod.rsserver/src/nourishment/tick.rsserver/src/nourishment/mod.rsserver/src/player/state.rsserver/src/cultivation/death_hooks.rsserver/src/persistence/mod.rsserver/src/cmd/dev/nourish.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
server/src/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
server/src/**/*.rs: 所有真元/灵气流动必须通过qi_physics::ledger::QiTransfer { from, to, amount, reason },不得直接增减账户或区域数值。
测试灵气总量时必须引用SPIRIT_QI_TOTAL,不得硬编码100。
离屏战斗死亡必须调用release_dormant_qi_to_zone并通过ledger.transfer(ReleaseToZone)归还真元。
新增衰减、抽取或衰减率常数前必须检查并复用qi_physics;不存在时先扩展其 constants,禁止在业务 plan 中重复定义。
天道时代衰减必须使用qi_physics::tiandao::era_decay_step,并通过WorldQiBudget::apply_era_decay和assert_conservation追踪沉降槽。
不得使用 armor stand 或 invisible mob 作为碰撞箱/交互载体;实体必须使用 Marker+自定义渲染,交互通过 C2S 请求处理。
headless/CI 启服必须设置BONG_SKIP_SKIN_PREFETCH=1,避免缺少MINESKIN_API_KEY导致 panic。
Files:
server/src/cmd/mod.rsserver/src/cmd/dev/mod.rsserver/src/npc/lifecycle.rsserver/src/world/dimension.rsserver/src/network/inventory_snapshot_emit.rsserver/src/network/mod.rsserver/src/cultivation/epitaph.rsserver/src/coffin/mod.rsserver/src/network/audio_trigger.rsserver/src/inventory/mod.rsserver/src/combat/mod.rsserver/src/combat/tests.rsserver/src/player/mod.rsserver/src/nourishment/tick.rsserver/src/nourishment/mod.rsserver/src/player/state.rsserver/src/cultivation/death_hooks.rsserver/src/persistence/mod.rsserver/src/cmd/dev/nourish.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
server/src/**/*.{rs,json}
📄 CodeRabbit inference engine (CLAUDE.md)
server/src/**/*.{rs,json}: 新增 zone 前必须核对docs/worldview.md区域表和server/zones.json中已有 ID。
ItemCategory只能使用 Pill、Herb、Scroll、Misc、Weapon、Armor、Tool、Treasure、RecipeFragment、RecipeHint、BoneCoin、Container;炼丹材料使用Misc,不得使用Material。
Files:
server/src/cmd/mod.rsserver/src/cmd/dev/mod.rsserver/src/npc/lifecycle.rsserver/src/world/dimension.rsserver/src/network/inventory_snapshot_emit.rsserver/src/network/mod.rsserver/src/cultivation/epitaph.rsserver/src/coffin/mod.rsserver/src/network/audio_trigger.rsserver/src/inventory/mod.rsserver/src/combat/mod.rsserver/src/combat/tests.rsserver/src/player/mod.rsserver/src/nourishment/tick.rsserver/src/nourishment/mod.rsserver/src/player/state.rsserver/src/cultivation/death_hooks.rsserver/src/persistence/mod.rsserver/src/cmd/dev/nourish.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
**/*.{rs,ts,tsx,java,py}
📄 CodeRabbit inference engine (CLAUDE.md)
新增 skill/cast/主动能力必须同时提供独立 animation、particle/VFX、SFX、HUD 反馈和 hotbar/SkillBar PNG icon;仅实现 server 或 schema 不算完成。
Files:
server/src/cmd/mod.rsserver/src/cmd/dev/mod.rsserver/src/npc/lifecycle.rsserver/src/world/dimension.rsserver/src/network/inventory_snapshot_emit.rsserver/src/network/mod.rsserver/src/cultivation/epitaph.rsserver/src/coffin/mod.rsserver/src/network/audio_trigger.rsserver/src/inventory/mod.rsserver/src/combat/mod.rsserver/src/combat/tests.rsserver/src/player/mod.rsserver/src/nourishment/tick.rsserver/src/nourishment/mod.rsserver/src/player/state.rsserver/src/cultivation/death_hooks.rsserver/src/persistence/mod.rsserver/src/cmd/dev/nourish.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
**/*.{rs,ts,tsx,java}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{rs,ts,tsx,java}: 六境界必须使用“醒灵→引气→凝脉→固元→通灵→化虚”,不得使用练气、筑基、金丹、元婴等旧称。
命名不得使用末法禁词玄、陨、星、仙、太、古,除明确允许的俗世矿名例外。
Files:
server/src/cmd/mod.rsserver/src/cmd/dev/mod.rsserver/src/npc/lifecycle.rsserver/src/world/dimension.rsserver/src/network/inventory_snapshot_emit.rsserver/src/network/mod.rsserver/src/cultivation/epitaph.rsserver/src/coffin/mod.rsserver/src/network/audio_trigger.rsserver/src/inventory/mod.rsserver/src/combat/mod.rsserver/src/combat/tests.rsserver/src/player/mod.rsserver/src/nourishment/tick.rsserver/src/nourishment/mod.rsserver/src/player/state.rsserver/src/cultivation/death_hooks.rsserver/src/persistence/mod.rsserver/src/cmd/dev/nourish.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
**/*.{rs,ts,tsx,java,json,md}
📄 CodeRabbit inference engine (CLAUDE.md)
唯一真货币是骨币;矿物是交易筹码,灵石是燃料/衰变物,金银不是货币。
Files:
server/src/cmd/mod.rsserver/src/cmd/dev/mod.rsserver/src/npc/lifecycle.rsserver/src/world/dimension.rsserver/src/network/inventory_snapshot_emit.rsserver/src/network/mod.rsserver/src/cultivation/epitaph.rsserver/src/coffin/mod.rsserver/src/network/audio_trigger.rsserver/src/inventory/mod.rsserver/src/combat/mod.rsserver/src/combat/tests.rsserver/src/player/mod.rsserver/src/nourishment/tick.rsserver/src/nourishment/mod.rsserver/src/player/state.rsdocs/plan-satiety-hydration-v1.mdserver/src/cultivation/death_hooks.rsserver/src/persistence/mod.rsserver/src/cmd/dev/nourish.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
**/*
📄 CodeRabbit inference engine (CLAUDE.md)
**/*: 禁止git stash push后不执行对应git stash pop;不得留下孤儿 WIP stash。
每个逻辑单元使用中文 atomic commit;agent 产生的每个 commit 必须包含真实模型 ID 的Model:trailer。
未经明确确认不得执行 force push、hard reset、amend、交互式 rebase、批量删除/移动文件或依赖版本/生产配置修改;严禁--no-verify、--no-gpg-sign及关闭签名。
PR review 只能通过独立评论/review触发;不得等待 Codex,review 修改后必须重新等待 re-review。
Files:
server/src/cmd/mod.rsserver/src/cmd/dev/mod.rsserver/src/npc/lifecycle.rsserver/src/world/dimension.rsserver/src/network/inventory_snapshot_emit.rsserver/src/network/mod.rsserver/src/cultivation/epitaph.rsserver/src/coffin/mod.rsserver/src/network/audio_trigger.rsserver/src/inventory/mod.rsserver/src/combat/mod.rsserver/src/combat/tests.rsserver/src/player/mod.rsserver/src/nourishment/tick.rsserver/src/nourishment/mod.rsserver/src/player/state.rsdocs/plan-satiety-hydration-v1.mdserver/src/cultivation/death_hooks.rsserver/src/persistence/mod.rsserver/src/cmd/dev/nourish.rsserver/src/cultivation/mod.rsserver/src/combat/lifecycle.rs
docs/**/*.md
📄 CodeRabbit inference engine (docs/CLAUDE.md)
docs/**/*.md: 新 plan 头部必须写明接入面:进料、出料、复用的共享类型/event/schema、server/agent/client 跨仓库契约,以及对应的worldview.md锚点。
涉及真元、灵气、衰减、逸散、半衰、距离损耗、排斥或吸力的 plan,必须调用qi_physics;新物理常数和公式必须先扩展qi_physics,不得在功能 plan 中重复实现。
所有真元/灵气流动必须遵守守恒律并通过qi_physics::ledger::QiTransfer;释放使用qi_release_to_zone,吸收使用qi_excretion,不得凭空生成或销毁真元。
涉及玩家可感知行为的 plan,必须在对应机制阶段中内联可直接实现的粒子、音效、HUD、环境、动画和 narration 规格;不得将视听内容笼统推迟到独立阶段。纯 server 逻辑 plan 例外。
每份 plan 必须列出开放问题;实施前必须追加§N.1 决议,逐项给出结论、实施方案、边界条件,并以文件:行号和 plan 章节双锚点落地。
scope 大于或等于 4 个 PR 的 plan,必须在末尾包含§10 实施工作流,并按依赖顺序在一个 plan 内序列化多个 PR,不得拆成多个 plan。
涉及 NBT 建筑、worldgen layout 或复杂视觉资产的 TODO,必须完成三轮提交:(round 1/3)、(round 2/3)、(round 3/3);终轮提交必须包含拼写准确的<PROMISE>担保块。
§10 最末必须包含“单次 consume-plan 全自动到 merge”章节,明确用户提交/consume-plan后即可等待最终归档至docs/finished_plans/。一个 PR 只允许修改一个 plan;
/consume-plan只能追加 Finish Evidence 或执行允许的 plan 归档移动,不得自动修改其他 docs 文件、CLAUDE.md或 worldview 文档。
Files:
docs/plan-satiety-hydration-v1.md
docs/plan-*.md
📄 CodeRabbit inference engine (CLAUDE.md)
docs/plan-*.md: Active plan 每个阶段必须写出可核验的模块路径、类型/函数、测试、schema、Redis key 或跨仓库契约 symbol。
plan 归档前必须所有阶段标记为✅ YYYY-MM-DD并补充严格标题为## Finish Evidence的证据章节。
Files:
docs/plan-satiety-hydration-v1.md
🪛 LanguageTool
docs/plan-satiety-hydration-v1.md
[uncategorized] ~242-~242: 您的意思是“"不"齐”?
Context: ... 后,流程按上述五个 PR 的依赖顺序自动实施、验证、审查并合并;全部阶段完成后补齐 ## Finish Evidence,最终将本 plan 归档至 `do...
(BU)
🔇 Additional comments (49)
server/src/nourishment/mod.rs (2)
529-553: 🗄️ Data Integrity & Integration
persist_player_cultivation_bundle_with_nourishment长参数列表问题仍未解决。此前已提出应将该函数参数收敛为具名结构体(尾部多个
Option/Some(...)连续排列,错位后编译器无法捕获),当前调用点依然是原始的位置参数形式,未标记为已处理。
541-541: LGTM!server/src/nourishment/tick.rs (2)
10-10: LGTM!Also applies to: 164-164, 174-178
250-289: LGTM!server/src/cmd/dev/mod.rs (1)
17-17: LGTM!Also applies to: 66-66
server/src/cmd/dev/nourish.rs (1)
50-53: LGTM!Also applies to: 123-123
server/src/cmd/mod.rs (1)
73-73: LGTM!Also applies to: 86-91
server/src/persistence/mod.rs (2)
4297-4433: LGTM!Also applies to: 6512-6600, 6602-6682
10925-11210: LGTM!Also applies to: 12775-12921
server/src/player/mod.rs (2)
1252-1298: LGTM!Also applies to: 1489-1580, 1852-1918
137-140: 🩺 Stability & Availability无需修改:这两个系统已在
Update中注册。
attach_cultivation_to_joined_clients与handle_revival_action_intents都在server/src/cultivation/mod.rs/server/src/combat/lifecycle.rs的app.add_systems(Update, ...)中注册,.after()约束不会在不同 schedule 中被忽略。server/src/player/state.rs (2)
618-636: LGTM!Also applies to: 858-885, 890-924
1153-1194: LGTM!Also applies to: 2529-2566, 4395-4449
server/src/combat/lifecycle.rs (9)
167-199: 📐 Maintainability & Code Quality查询元组已嵌套四层,建议抽成具名
QueryData结构体。
NearDeathPersistenceQueryItem现在在near_death_tick与handle_revival_action_intents两处按同一形状解包 30+ 字段,新增/删除字段需同时改 alias 与两个调用点,且靠位置识别字段极易错位。
3548-3553: 📐 Maintainability & Code Quality测试内
RevivalObservation建议改用#[derive(Resource)]。与生产代码 Line 213-216 的
PendingPlayerRevivedCompletions同样是手写空impl Resource,项目内其余 Resource 多用 derive,统一风格可减少后续宏增强带来的不一致。
1889-1972: LGTM!Also applies to: 2199-2212
2019-2035: LGTM!Also applies to: 2216-2283
2727-2756: LGTM!Also applies to: 2884-2898, 3050-3057
4403-4423: LGTM!
6313-6413: LGTM!
213-225: 🩺 Stability & Availability确认生产调度已注册
PendingPlayerRevivedCompletions与emit_player_revived_completions。
handle_revival_action_intents现在写入待发布资源而非直接发送事件;如果调度层未init_resource,系统参数会在运行时 panic;如果未注册,后续已注册的on_player_revived将永远不会收到PlayerRevived,复活下游 hook 会静默失效。请检查生产调度注册是否按apply_deferred后、on_player_revived前的顺序完成。
2758-2786: 🗄️ Data Integrity & Integration旧
Cultivation按 0 提交不会漏还真元。
reset_for_new_character只被LifecycleState::Terminated的转世分支调用;正式终结路径先用stage_lifecycle_qi_release(...current qi...)把旧命真元打入ZoneRegistry/WorldQiAccount并 emitQiTransfer,成功后才发布PlayerTerminated;on_player_terminated随后移除Cultivation。当前路径不会再走一次CreateNewCharacter,也不会进入old_cultivation.map_or(0.0, ...)。> Likely an incorrect or invalid review comment.server/src/coffin/mod.rs (2)
251-265: LGTM!Also applies to: 342-342, 362-379
335-335: 🩺 Stability & Availability无需移除重复的
add_event::<SneakEvent>()。当前 Bevy 0.14.2 的实现已使同一事件类型的
add_event幂等:若Events<T>/事件维护系统已存在则不会重复插入,因此valence注册后再注册不会替换事件资源或重复注册event_update_system。server/src/world/dimension.rs (1)
88-121: LGTM!Also applies to: 141-153
server/src/cultivation/death_hooks.rs (3)
83-85: LGTM!Also applies to: 261-278
344-352: LGTM!Also applies to: 428-428, 458-458
852-948: LGTM!Also applies to: 1329-1433
server/src/cultivation/epitaph.rs (1)
984-987: LGTM!Also applies to: 1037-1040, 1064-1067, 1111-1114, 1155-1158, 1196-1199, 1237-1240, 1276-1279, 1323-1326, 1370-1373, 1415-1418, 1484-1487, 1561-1564, 1641-1644, 1795-1798, 1834-1837, 1885-1888, 1940-1943
server/src/cultivation/mod.rs (3)
568-583: LGTM!Also applies to: 605-614, 838-853, 945-952, 1134-1141, 1274-1314, 1316-1409, 1419-1485
2731-2875: LGTM!Also applies to: 2971-3520, 3800-4194
976-978: 🩺 Stability & Availability无需修改:零 qi 时 staging 返回
None,不会触发提交阶段的资源expect。
stage_lifecycle_qi_release对amount == 0.0直接返回Ok(None),因此staged_old_qi_release为Some的前提已经排除了零 qi 场景。server/src/npc/lifecycle.rs (1)
1032-1035: LGTM!Also applies to: 1265-1267
server/src/inventory/mod.rs (3)
883-899: 调度顺序符合预期。
apply_death_drop_on_revive挂在emit_player_revived_completions之后、apply_termination_drop_on_terminate挂在handle_revival_action_intents之后,与network/mod.rs、combat/mod.rs中对应的下游调度(inventory/skill resync、rebirth events 等)保持一致。
15159-15194: LGTM!
12247-12250: 🗄️ Data Integrity & Integration无需修改:
PlayerTerminated.settlement_committed已有消费路径。
death_hooks.rs中PlayerTerminated的处理器会读取ev.settlement_committed,因此这里设置默认值不构成未使用的契约字段问题。> Likely an incorrect or invalid review comment.server/src/network/audio_trigger.rs (2)
109-124: LGTM!
2543-2544: LGTM!Also applies to: 2569-2570, 2657-2658
server/src/network/inventory_snapshot_emit.rs (1)
1062-1129: LGTM! 该测试精确锁定了"复活库存重同步必须观察到死亡掉落之后的库存"这一跨系统调度契约,是本 PR 调度重构的一个很好的回归防护。server/src/network/mod.rs (3)
789-793: LGTM!
931-963: LGTM!
1006-1015: LGTM!server/src/combat/mod.rs (3)
91-115: LGTM! 用(With<Client>, With<Cultivation>, Without<Wounds>)替换Added<Client>单帧窗口,解决了修炼数据延迟挂载时错过战斗组件挂载时机的问题,且有对应回归测试覆盖。
264-264: LGTM!
343-368: 🩺 Stability & Availability超时自动复活的
PlayerRevived结算会自然延迟一 tick,这不是当前改动引入的问题。
auto_confirm_revival_decisions只是发射RevivalActionIntent,handle_revival_action_intents才消费并写入PendingPlayerRevivedCompletions;由于它在当前 resolve 顺序中被排在emit_player_revived_completions之后,超时自动复活对应的一次PlayerRevived及后续回调只能在下一次 resolve 被 drain,与主动提交复活的行为边界一致。server/src/combat/tests.rs (3)
30-91: LGTM! 这两条新测试准确覆盖了combat/mod.rs中战斗组件挂载重试逻辑的关键语义(等待 Cultivation、Terminated Lifecycle 在无 Cultivation 时保持不可见)。
149-152: LGTM! 为满足attach_combat_bundle_to_joined_clients新查询过滤器(需要With<Cultivation>)而统一补充Cultivation::default(),改动机械且一致。Also applies to: 184-187, 216-219, 239-242, 311-314, 382-385, 439-442, 477-480, 531-534, 609-612
337-343: LGTM! 放宽为区间断言,规避同进程内墙钟秒级截断导致的最多 1 秒偏差引起的测试抖动,同时仍校验了 deadline 折算方向正确。docs/plan-satiety-hydration-v1.md (1)
96-96: 🗄️ Data Integrity & Integration明确复活真元的来源金额与转移身份
PlayerRevived::FormalPenaltyApplied的审计released_qi和 plan 描述的“处罚后qi_current = 0+ “同额真元”回流”之间存在歧义:如果released_qi等于处罚前剩余真元,则和qi_current = 0一致;如果指化虚配额,则来源字段、from账户、释放条件/边界和released == accepted + overflow的 pin test 仍需要明确定义,避免复活路径凭空增发或漏记真元。
| | 陈酒 | +3 | +18 | 保留 | | ||
| | 陈醋 | +2 | +10 | 保留 | | ||
|
|
||
| **水囊实装**:`water_skin`(现为 `workbench_materials.toml:260`、`category = "misc"` 的材料)加「满水囊」`water_skin_filled` 真实物品,**沿用 `category = "misc"`;本 plan 不新增、不修改、也不依赖任何 `ItemCategory` 扩张**。满水囊 `hydration +55`,用后原子替换回空 `water_skin`;背包若无法容纳返回物,整笔消费失败,禁止先扣满水囊再丢空囊。此前过滤器单测曾借用 `water_skin_filled` 作为 `ItemCategory::Liquid` 内存 fixture,P0 已改成无生产语义的 `test_liquid`,避免预占并污染 P2 的正式 `Misc` 类别契约;P2 仍需从零接线 TOML 注册 + 消费事务 + icon,不存在“落地即接活”的捷径。灌装交互纳入 v1:client 准星检测发 `ClientRequestV1::WaterSkinFill { v, x, y, z, item_instance_id }`,server 复核存活/同维度/中心距离 ≤5/目标精确为 `BlockState::WATER`/实例仍是权威持有的空水囊后原子替换;灌装本身不恢复 hydration。新物品 icon 走 `/gen-image item`(跑不了则 `[BLOCKED: 需 /gen-image 生成 water_skin_filled.png]` + 占位接线)。 |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 检查水囊灌装请求、处理器、响应及库存同步链路。
rg -n -C 8 \
'ClientRequestV1|WaterSkinFill|item_instance_id|InventorySnapshot|water_skin_filled|water_skin' \
server/src
fd -e rs -e ts -e tsx . | xargs -r rg -n -C 6 \
'WaterSkinFill|item_instance_id|InventorySnapshot|water_skin_filled'Repository: Kizunad/Bong
Length of output: 50369
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- plan context ---\n'
sed -n '100,140p' docs/plan-satiety-hydration-v1.md
printf '\n--- exact symbol line counts ---\n'
rg -n --max-count 2 \
'WaterSkinFill|water_skin_filled|water_skin|InventorySnapshot|snapshot|请求 ID|幂等|超时|response|响应|错误码|error' \
$(git ls-files 'docs/plan-satiety-hydration-v1.md' 'server/src/' 'client/src/')
printf '\n--- focused server source for exact symbols ---\n'
python3 - <<'PY'
from pathlib import Path
patterns = ['WaterSkinFill', 'water_skin_filled', 'water_skin', 'InventorySnapshot', 'ClientRequestV1', 'response', 'response']
for f in Path('server/src').rglob('*'):
if not f.is_file() or f.suffix not in {'.rs'}:
continue
txt = f.read_text(errors='ignore')
hits = []
for p in patterns:
if p not in txt:
continue
lines = txt.splitlines()
for i, line in enumerate(lines, 1):
if p in line:
hits.append((i, line[:220]))
if hits:
print(f'\nFILE {f}')
last = hits[-1][0]
start=max(1,hits[0][0]-4)
end=min(len(lines), last+4)
print(f'range {start}-{end} with hits near first/last')
for n,line in enumerate(lines[start-1:end],start):
print(f'{n}\t{line}')
PYRepository: Kizunad/Bong
Length of output: 241
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- plan context ---'
sed -n '100,140p' docs/plan-satiety-hydration-v1.md
printf '%s\n' ''
printf '%s\n' '--- exact symbol line counts ---'
python3 - <<'PY'
from pathlib import Path
terms = ['WaterSkinFill', 'water_skin_filled', 'water_skin', 'InventorySnapshot', 'ClientRequestV1']
for root in [Path('server/src'), Path('client/src')]:
if not root.exists():
continue
for f in root.rglob('*'):
if not f.is_file() or f.suffix not in {'.rs', '.ts', '.tsx'}:
continue
try:
txt = f.read_text(errors='ignore')
except UnicodeDecodeError:
continue
hits = {}
for p in terms:
hits[p] = [i for i, line in enumerate(txt.splitlines(), 1) if p in line]
if any(hits.get(p) for p in terms):
print(f'\nFILE {f}')
first = min(next(iter(v)) for v in hits.values() if v)
last = max(next(iter(v)) for v in hits.values() if v)
print(f'matches: {hits}')
start=max(1, first-10)
end=min(len(txt.splitlines()), last+10)
for n,line in enumerate(txt.splitlines()[start-1:end], start):
print(f'{n}\t{line}')
PY
printf '%s\n' ''
printf '%s\n' '--- broader retry/error/idempotency related search ---'
rg -n --max-count 30 \
'请求 ID|request id|RequestId|幂等|idempot|超时|timeout|重试|retry|response|Response|error|错误码|InventorySnapshot|snapshot|filled' \
$(git ls-files 'docs/plan-satiety-hydration-v1.md' 'server/src/' 'client/src/')Repository: Kizunad/Bong
Length of output: 50369
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- plan context ---'
sed -n '100,140p' docs/plan-satiety-hydration-v1.md
printf '%s\n' ''
printf '%s\n' '--- WaterSkinFill/sent handlers files with exact term ---'
python3 - <<'PY'
from pathlib import Path
terms = ['WaterSkinFill']
for root in [Path('server/src'), Path('client/src')]:
if not root.exists():
continue
for f in root.rglob('*'):
if not f.is_file() or f.suffix not in {'.rs', '.ts', '.tsx'}:
continue
txt = f.read_text(errors='ignore')
if any(p in txt for p in terms):
lines = txt.splitlines()
print(f'\nFILE {f} ({len(lines)} lines)')
for i,line in enumerate(lines,1):
if any(p in line for p in terms):
start=max(1,i-8); end=min(len(lines),i+24)
for n,l in enumerate(lines[start-1:end],start):
print(f'{n}\t{l}')
PY
printf '%s\n' ''
printf '%s\n' '--- ClientRequestV1 request envelope and responses, request IDs, ack/success failure cases ---'
rg -n \
'enum ClientRequestV1|ClientRequestV1|water_skin|filled|request.*id|RequestId|request_id|ack_request_id|success|failed|error|timeout|retry|timeout|InventorySnapshot|inventory_snapshot|item_instance_id' \
client/src/main/java/com/bong/client/network/ClientRequestProtocol.java \
client/src/main/java/com/bong/client/network/ClientRequestSender.java \
server/src -g '*.rs' \
--max-count 200 | head -n 500Repository: Kizunad/Bong
Length of output: 50370
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- exact terms summary from tracked files ---'
python3 - <<'PY'
from pathlib import Path
terms = {
'WaterSkinFill': ['.rs','.ts','.tsx','.md'],
'water_skin_filled': ['.rs','.ts','.tsx','.json','.toml','.md'],
'water_skin': ['.rs','.ts','.tsx','.json','.toml','.md'],
'ClientRequestV1::WaterSkinFill': ['.rs','.ts','.tsx','.md'],
}
counts={}
for t, suffixes in terms.items():
counts[t]=[]
for root in [Path('server/src'), Path('client/src'), Path('docs')]:
if not root.exists(): continue
for f in root.rglob('*'):
if not f.is_file() or f.suffix not in suffixes: continue
try:
txt=f.read_text(errors='ignore')
except UnicodeDecodeError:
continue
if t in txt:
counts[t].append((f,txt.count(t)))
print(f'\nTERM {t}')
if not counts[t]:
print('NO MATCHES')
else:
for f,c in counts[t]:
print(f'{f}: {c}')
print('\n--- request_id sending handlers ---')
for f in [Path('client/src/main/java/com/bong/client/network/ClientRequestSender.java')]:
if not f.exists(): continue
txt=f.read_text()
lines=txt.splitlines()
for i,l in enumerate(lines,1):
if 'send(' in l or 'dispatch(' in l or 'request_id' in l:
start=max(1,i-8); end=min(len(lines),i+20)
print(f'\nFILE {f}')
for n,line in enumerate(lines[start-1:end],start):
print(f'{n}\t{line}')
PY
printf '%s\n' ''
printf '%s\n' '--- focused network protocol handlers around request_id/ack patterns ---'
sed -n '330,710p' client/src/main/java/com/bong/client/network/ClientRequestSender.java
printf '%s\n' ''
sed -n '1140,1175p' client/src/main/java/com/bong/client/network/ClientRequestProtocol.javaRepository: Kizunad/Bong
Length of output: 50369
补齐水囊灌装的网络响应与重试契约
当前 plan/代码尚未实现 ClientRequestV1::WaterSkinFill 的完成路径:缺少成功/失败 response 和错误码、重试的请求 ID 幂等语义及收敛规则、提交成功后丢失响应时客户端如何依据服务端权威库存收敛到满水囊状态。建议补齐 response shape、失败 case 清单与一条“服务端已提交但响应丢失的重试覆盖测试”。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/plan-satiety-hydration-v1.md` at line 122, 补齐水囊灌装流程中
ClientRequestV1::WaterSkinFill 的网络契约:定义成功与失败 response shape 及明确错误码,规定请求 ID
的幂等处理、重试行为和最终收敛规则;明确提交成功但响应丢失时客户端如何依据服务端权威库存恢复为满水囊状态,并增加覆盖该场景的重试测试。
| ### 单次 consume-plan 全自动到 merge | ||
|
|
||
| 用户提交一次 `/consume-plan` 后,流程按上述五个 PR 的依赖顺序自动实施、验证、审查并合并;全部阶段完成后补齐 `## Finish Evidence`,最终将本 plan 归档至 `docs/finished_plans/`。 |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
将自动合并流程的强制门禁写入计划
“单次 /consume-plan 全自动到 merge”没有明确要求自动流程阻断于以下仓库规则:每个逻辑单元使用中文 atomic commit;agent commit 必须包含 Model: <真实模型 ID> trailer;PR review 只能由独立 /review 触发;review 修改后必须重新等待 re-review。请将这些条件逐项写入本节,否则自动化可能合并不合规历史或未完成复审的 PR。
🧰 Tools
🪛 LanguageTool
[uncategorized] ~242-~242: 您的意思是“"不"齐”?
Context: ... 后,流程按上述五个 PR 的依赖顺序自动实施、验证、审查并合并;全部阶段完成后补齐 ## Finish Evidence,最终将本 plan 归档至 `do...
(BU)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/plan-satiety-hydration-v1.md` around lines 240 - 242, 在“单次 consume-plan
全自动到 merge”小节中逐项补充强制门禁:每个逻辑单元必须使用中文 atomic commit;agent commit 必须包含真实模型 ID
的“Model” trailer;PR review 只能由独立的“/review”触发;review 产生修改后必须重新等待
re-review。确保自动流程仅在所有门禁满足且复审完成后合并。
Source: Coding guidelines
| pub fn reconcile_public_deceased_exports(settings: &PersistenceSettings) -> io::Result<()> { | ||
| let _guard = deceased_export_lock() | ||
| .lock() | ||
| .map_err(|_| io::Error::other("deceased public export lock poisoned"))?; | ||
| fs::create_dir_all(settings.deceased_public_dir())?; | ||
|
|
||
| let snapshot_stem = sanitize_deceased_snapshot_stem(char_id); | ||
| let snapshot_path = settings | ||
| .deceased_public_dir() | ||
| .join(format!("{snapshot_stem}.json")); | ||
| let index_path = settings.deceased_public_dir().join("_index.json"); | ||
| let previous_snapshot = fs::read(&snapshot_path).ok(); | ||
| let previous_index = fs::read(&index_path).ok(); | ||
| fs::write(&snapshot_path, snapshot_json.as_bytes())?; | ||
|
|
||
| let relative_snapshot_path = format!("deceased/{snapshot_stem}.json"); | ||
| let mut entries = read_deceased_index(&index_path)?; | ||
| entries.retain(|entry| entry.char_id != char_id); | ||
| entries.push(DeceasedIndexEntry { | ||
| char_id: char_id.to_string(), | ||
| died_at_tick, | ||
| path: relative_snapshot_path.clone(), | ||
| termination_category: termination_category.to_string(), | ||
| }); | ||
| entries.sort_by(|left, right| { | ||
| left.died_at_tick | ||
| .cmp(&right.died_at_tick) | ||
| .then_with(|| left.char_id.cmp(&right.char_id)) | ||
| }); | ||
| let connection = open_persistence_connection(settings)?; | ||
| let mut statement = connection | ||
| .prepare( | ||
| " | ||
| SELECT char_id, snapshot_json, public_path, died_at_tick | ||
| FROM deceased_snapshots | ||
| WHERE public_path IS NOT NULL | ||
| ORDER BY died_at_tick ASC, char_id ASC | ||
| ", | ||
| ) | ||
| .map_err(io::Error::other)?; | ||
| let rows = statement | ||
| .query_map([], |row| { | ||
| Ok(( | ||
| row.get::<_, String>(0)?, | ||
| row.get::<_, String>(1)?, | ||
| row.get::<_, String>(2)?, | ||
| row.get::<_, i64>(3)?, | ||
| )) | ||
| }) | ||
| .map_err(io::Error::other)? | ||
| .collect::<Result<Vec<_>, _>>() | ||
| .map_err(io::Error::other)?; | ||
|
|
||
| let mut entries = Vec::with_capacity(rows.len()); | ||
| for (char_id, snapshot_json, public_path, died_at_tick) in rows { | ||
| let died_at_tick = sql_to_tick(died_at_tick)?; | ||
| let snapshot: DeceasedSnapshot = serde_json::from_str(&snapshot_json) | ||
| .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?; | ||
| let expected_path = format!( | ||
| "deceased/{}.json", | ||
| sanitize_deceased_snapshot_stem(char_id.as_str()) | ||
| ); | ||
| if public_path != expected_path { | ||
| return Err(io::Error::new( | ||
| io::ErrorKind::InvalidData, | ||
| format!( | ||
| "invalid deceased public path for {char_id}: expected {expected_path}, got {public_path}" | ||
| ), | ||
| )); | ||
| } | ||
| atomic_replace_file( | ||
| settings | ||
| .deceased_public_dir() | ||
| .join(format!( | ||
| "{}.json", | ||
| sanitize_deceased_snapshot_stem(char_id.as_str()) | ||
| )) | ||
| .as_path(), | ||
| snapshot_json.as_bytes(), | ||
| )?; | ||
| entries.push(DeceasedIndexEntry { | ||
| char_id, | ||
| died_at_tick, | ||
| path: public_path, | ||
| termination_category: snapshot.termination_category, | ||
| }); | ||
| } | ||
|
|
||
| let index_json = serde_json::to_string_pretty(&entries) | ||
| .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?; | ||
| fs::write(&index_path, index_json.as_bytes())?; | ||
|
|
||
| Ok(StagedDeceasedExport { | ||
| snapshot_path, | ||
| index_path, | ||
| previous_snapshot, | ||
| previous_index, | ||
| relative_snapshot_path, | ||
| _guard: guard, | ||
| }) | ||
| atomic_replace_file( | ||
| settings.deceased_public_dir().join("_index.json").as_path(), | ||
| index_json.as_bytes(), | ||
| ) | ||
| } |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 确认 reconcile 的调用点,以及是否存在把 public_path 置空/删除 deceased_snapshots 行的路径
rg -nP --type=rust -C3 'reconcile_public_deceased_exports\s*\('
rg -nP --type=rust -C3 'public_path\s*=\s*NULL|DELETE\s+FROM\s+deceased_snapshots'
rg -nP --type=rust -C3 'deceased_public_dir\s*\(\)'Repository: Kizunad/Bong
Length of output: 150
🏁 Script executed:
#!/bin/bash
set -u
echo "== candidate files =="
git ls-files | rg '(^|/)persistence\.rs$|server/src/.+\.rs$' || true
echo "== call sites / relevant symbols =="
rg -n --glob '*.rs' -C 4 'reconcile_public_deceased_exports|persist_termination_transition_inner|persist_life_record_death_insight|public_path|deceased_public_dir|deceased_snapshots|sanitize_deceased_snapshot_stem|_index\.json' .Repository: Kizunad/Bong
Length of output: 50368
🏁 Script executed:
#!/bin/bash
set -u
echo "== atomic_replace_file =="
rg -n --glob '*.rs' -C 4 'fn atomic_replace_file|atomic_replace_file\s*\(' server/src/persistence/mod.rs server/src || true
echo "== deceased path cleanup / public_path updates / deletes around persistence/mod.rs =="
sed -n '1250,1285p' server/src/persistence/mod.rs
sed -n '4190,4435p' server/src/persistence/mod.rs
sed -n '8070,8130p' server/src/persistence/mod.rs
sed -n '8158,8230p' server/src/persistence/mod.rs
echo "== exact SQL references to deceased_snapshots =="
rg -n --glob '*.rs' 'deceased_snapshots|public_path' server/src | head -n 200Repository: Kizunad/Bong
Length of output: 21320
🏁 Script executed:
#!/bin/bash
set -u
echo "== persistence settings defaults =="
rg -n --glob '*.rs' -C 3 'DEFAULT_DECEASED_PUBLIC_DIR|DEFAULT_DATABASE_PATH|PersistenceSettings::default|PersistenceSettings::default' server/src/main.rs server/src | head -n 200
echo "== atomic_replace_file implementation =="
sed -n '8231,8285p' server/src/persistence/mod.rs
echo "== static path cleanup/deletion references =="
rg -n --glob '*.rs' -C 2 'readdir|remove_file|PathBuf|deceased|public_path|index\.json|fs::read_dir|drop|DELETE FROM|UPDATE deceased_snapshots SET public_path' server/src | head -n 300Repository: Kizunad/Bong
Length of output: 35946
热路径不要全量 reconcile deceased public exports。
reconcile_public_deceased_exports 会对 public_path IS NOT NULL 的每条记录写一份 snapshot + 重写 _index.json,并在 persist_termination_transition_inner / persist_life_record_death_insight 的 commit 后执行;随着已故离线角色增多,这是 O(N) 的磁盘写 + fsync。热路径应只更新/重建当前 char_id 的 "deceased/{stem}.json" 与 _index.json,启动时的兜底 reconcile 保留在全量路径即可。
另外该函数不删除 deceased_public_dir() 下不再属于索引的旧 *.json;后续若引入 public_path 置空或删除 deceased_snapshots 的记录,需要把清理陈旧文件纳入同一导出路径。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/persistence/mod.rs` around lines 8158 - 8229, 将
persist_termination_transition_inner 和 persist_life_record_death_insight
提交后的导出流程改为按当前 char_id 增量更新对应 snapshot,并仅增量重建 _index.json,避免调用
reconcile_public_deceased_exports 对全部记录执行 O(N) 写盘;保留
reconcile_public_deceased_exports 作为启动或显式兜底的全量路径,并在该全量路径中清理
deceased_public_dir() 下不再对应索引记录的旧 JSON 文件。
| fn atomic_replace_file(path: &Path, contents: &[u8]) -> io::Result<()> { | ||
| let parent = path.parent().ok_or_else(|| { | ||
| io::Error::new( | ||
| io::ErrorKind::InvalidInput, | ||
| "atomic replacement path must have a parent directory", | ||
| ) | ||
| })?; | ||
| fs::create_dir_all(parent)?; | ||
| let file_name = path.file_name().ok_or_else(|| { | ||
| io::Error::new( | ||
| io::ErrorKind::InvalidInput, | ||
| "atomic replacement path must name a file", | ||
| ) | ||
| })?; | ||
|
|
||
| fn rollback_file(path: &Path, previous: Option<&[u8]>) { | ||
| match previous { | ||
| Some(contents) => { | ||
| let _ = fs::write(path, contents); | ||
| } | ||
| None => { | ||
| let _ = fs::remove_file(path); | ||
| for attempt in 0..32_u32 { | ||
| let temporary = parent.join(format!( | ||
| ".{}.{}.{}.tmp", | ||
| file_name.to_string_lossy(), | ||
| std::process::id(), | ||
| attempt | ||
| )); | ||
| let mut options = OpenOptions::new(); | ||
| options.write(true).create_new(true); | ||
| #[cfg(unix)] | ||
| options.mode(0o600); | ||
| let mut temporary_file = match options.open(&temporary) { | ||
| Ok(file) => file, | ||
| Err(error) if error.kind() == io::ErrorKind::AlreadyExists => continue, | ||
| Err(error) => return Err(error), | ||
| }; | ||
| let result = (|| -> io::Result<()> { | ||
| temporary_file.write_all(contents)?; | ||
| temporary_file.sync_all()?; | ||
| fs::rename(&temporary, path)?; | ||
| File::open(parent)?.sync_all() | ||
| })(); | ||
| drop(temporary_file); | ||
| if result.is_err() { | ||
| let _ = fs::remove_file(&temporary); | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| Err(io::Error::new( | ||
| io::ErrorKind::AlreadyExists, | ||
| "could not allocate a deceased export temporary file", | ||
| )) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Does std::fs::rename on Windows overwrite an existing destination file, and can File::open be used on a directory on Windows?
💡 Result:
On Windows, the behavior of std::fs::rename and File::open is as follows: std::fs::rename on Windows: Yes, std::fs::rename generally overwrites an existing destination file [1][2]. The function is implemented using Windows API functions such as MoveFileExW or SetFileInformationByHandle [1][2]. On modern versions of Windows, it replaces the destination if it exists, provided the destination is not a directory [1][2]. There are platform-specific constraints: on older Windows versions or specific filesystems where advanced renaming flags are unavailable, the destination must not be a directory [1][2]. Additionally, modern Rust versions attempt to use POSIX-like renaming semantics (via FILE_RENAME_POSIX_SEMANTICS) when supported, which can improve behavior regarding atomic operations and open file handles [3][4]. File::open on a Directory on Windows: By default, standard File::open calls on Windows do not succeed for directories; they will typically return an error (such as Permission Denied or Access Denied) because the underlying CreateFileW system call requires specific flags to open a directory [5][6]. To successfully open a directory as a file handle on Windows using Rust, you must use OpenOptions and set the custom flag FILE_FLAG_BACKUP_SEMANTICS [5][6]. This flag is explicitly required by the Windows API to obtain a handle to a directory [5][6]. This is why internal Rust library functions that need to stat or inspect directories use this specific flag [6].
Citations:
- 1: https://doc.rust-lang.org/stable/std/fs/fn.rename.html
- 2: https://doc.rust-lang.org/std/fs/fn.rename.html
- 3: Win: Use POSIX rename semantics for
std::fs::renameif available rust-lang/rust#131072 - 4:
std::fs::renamesometimes fails on Windows due to missingFILE_RENAME_POSIX_SEMANTICSrust-lang/rust#123985 - 5: File::open() on directories does not return Err(), leads to breakage with BufReader rust-lang/rust#64144
- 6: https://github.com/rust-lang/rust/blob/3802025f400af7817ba4874587e6a2df95abd65d/library/std/src/sys/windows/fs.rs
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate file and relevant symbols =="
wc -l server/src/persistence/mod.rs || true
rg -n "atomic_replace_file|sanitize_deceased_snapshot_stem_replaces_windows_invalid_separators|deceased" server/src/persistence/mod.rs
echo
echo "== relevant function section =="
sed -n '8200,8290p' server/src/persistence/mod.rs
echo
echo "== Windows-related or fsync parent context in persistence =="
rg -n "windows|rename\\(|sync_all\\(|OpenOptions|fs::create_dir_all|sanitize_deceased" server/src/persistence/mod.rs | sed -n '1,200p'Repository: Kizunad/Bong
Length of output: 17162
🌐 Web query:
Rust std::fs::rename Windows existing destination overwrite documentation std::fs::rename Windows
💡 Result:
The Rust standard library function std::fs::rename on Windows is designed to replace the destination file or directory if it already exists, effectively performing an overwrite [1][2][3]. However, the specific behavior regarding whether an overwrite succeeds depends on the version of Windows and the underlying filesystem's support for modern Windows APIs [1][3]. Key behaviors include: 1. Modern Windows (Windows 10, version 1607 and later): The standard library attempts to use FileRenameInfoEx (via SetFileInformationByHandle) with POSIX semantics [1][4][5]. When supported, this matches Unix behavior, requiring that if the source is a directory, the destination must also be an (empty) directory; if the source is not a directory, the destination must not be a directory [1][3]. 2. Legacy/Fallback Behavior: If FileRenameInfoEx is not supported or not available, the implementation falls back to MoveFileExW [1][4][3]. In this mode, the source can be anything, but the destination must not be a directory [1][3]. Consequently, attempting to rename a file over an existing, non-empty directory will typically fail with an access-denied error [6]. 3. Atomicity: Recent updates (specifically those leveraging FILE_RENAME_FLAG_POSIX_SEMANTICS) have improved the atomicity and reliability of this operation on Windows, helping to resolve issues where files could not be replaced while still in use by other processes [4][5]. In summary, while std::fs::rename is intended to overwrite the destination, it is subject to platform-specific constraints that may cause it to fail depending on whether the source and destination are files or directories, and the capabilities of the host system [1][3].
Citations:
- 1: https://doc.rust-lang.org/stable/std/fs/fn.rename.html
- 2: https://doc.rust-lang.org/nightly/std/fs/fn.rename.html
- 3: https://doc.rust-lang.org/beta/std/fs/fn.rename.html
- 4: Win: Use POSIX rename semantics for
std::fs::renameif available rust-lang/rust#131072 - 5:
std::fs::renamesometimes fails on Windows due to missingFILE_RENAME_POSIX_SEMANTICSrust-lang/rust#123985 - 6: fs::rename fails when dest exists only on windows rust-lang/rust#31301
不要在非 Unix 平台上用 File::open(parent) 做 sync_all。
reconcile_public_deceased_exports 会写入 existing 快照和 _index.json;在 Windows 上直接打开目录会失败,导致每次重建 deceased projection 都返回错误。如只支持 Unix,把目录 fsync 换成 #[cfg(unix)] 并说明平台前提;如需兼容 Windows,应实现跨平台目录持久化方案。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/persistence/mod.rs` around lines 8231 - 8279, Update
atomic_replace_file so the parent-directory sync after fs::rename is only
performed on Unix via #[cfg(unix)], while preserving file syncing and rename
behavior on other platforms. Document the platform limitation or provide an
established Windows-compatible persistence mechanism; ensure
reconcile_public_deceased_exports can rebuild successfully on Windows when
directory fsync is unavailable.
| fn rollback_file(path: &Path, previous: Option<&[u8]>) { | ||
| match previous { | ||
| Some(contents) => { | ||
| let _ = fs::write(path, contents); | ||
| } | ||
| None => { | ||
| let _ = fs::remove_file(path); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -nP --type=rust -C3 '\brollback_file\s*\('Repository: Kizunad/Bong
Length of output: 150
rollback_file 已无 Rust 调用者。
该辅助函数在事务前 staged export 与文件回滚路径被移除后成为死代码,可直接清理。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/persistence/mod.rs` around lines 8502 - 8511, 删除未再被 Rust 调用的辅助函数
rollback_file 及其相关死代码,确保移除后不留下无效引用或不必要的导入。
| if let Some(mut visible_layers) = visible_entity_layers { | ||
| match visibility_policy { | ||
| OverworldVisibilityPolicy::ReplaceAll => visible_layers.0.clear(), | ||
| OverworldVisibilityPolicy::PreserveUnrelatedLayers => { | ||
| if let Some(previous_layer) = previous_layer { | ||
| visible_layers.0.remove(&previous_layer); | ||
| } else { | ||
| visible_layers.0.clear(); | ||
| } | ||
| visible_layers.0.remove(&layers.tsy); | ||
| } | ||
| } | ||
| visible_layers.0.insert(overworld); | ||
| } else { | ||
| let mut visible_layers = VisibleEntityLayers::default(); | ||
| visible_layers.0.insert(overworld); | ||
| commands.entity(entity).insert(visible_layers); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
PreserveUnrelatedLayers 的可见层计算既不健壮也不可扩展。 两处问题共享同一根因:该分支手工枚举"要移除哪些层",而不是依据 DimensionLayers 推导出"除 overworld 外的全部维度层"。
server/src/world/dimension.rs#L122-L139:删除previous_layer为None时的visible_layers.0.clear(),改为仅移除维度层、保留其余无关层,使行为与策略名一致。server/src/world/dimension.rs#L131-L131:把硬编码的visible_layers.0.remove(&layers.tsy)替换为遍历DimensionLayers中所有非 overworld 层移除(可为DimensionLayers新增all_layers()/non_overworld_layers()辅助方法),避免新增第三维度时静默漏移除。
📍 Affects 1 file
server/src/world/dimension.rs#L122-L139(this comment)server/src/world/dimension.rs#L131-L131
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/world/dimension.rs` around lines 122 - 139, The
PreserveUnrelatedLayers branch in server/src/world/dimension.rs lines 122-139
must preserve unrelated layers when previous_layer is None by removing only
dimension layers, not clearing the set. Replace the hardcoded layers.tsy removal
at lines 131-131 with iteration over all non-overworld layers derived from
DimensionLayers, adding an all_layers() or non_overworld_layers() helper if
appropriate; both sites should be updated to support future dimensions.
| } else { | ||
| visible_layers.0.clear(); | ||
| } | ||
| visible_layers.0.remove(&layers.tsy); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
硬编码 layers.tsy 会在新增维度时静默漏移除。
PreserveUnrelatedLayers 目前靠"显式移除 tsy"来保证离开 TSY 时不残留可见性;一旦 DimensionLayers 增加第三个维度层,该维度层会被当作"无关层"留在可见集里,造成跨维度实体可见。建议改为遍历 DimensionLayers 的全部非 overworld 层移除,或给 DimensionLayers 提供 all_layers() 供此处使用。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server/src/world/dimension.rs` at line 131, 更新 PreserveUnrelatedLayers 中对
visible_layers 的清理逻辑,不要仅硬编码移除 layers.tsy;遍历 DimensionLayers 的全部非 overworld
层并移除,或复用其新增的 all_layers() 接口,确保未来新增维度层时也不会残留可见性。
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
server/src/combat/lifecycle.rs (1)
11414-11556: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value测试名与实际覆盖不一致:函数名声称 terminate,但实际发送
Reincarnate。
terminate_tribulation_failed_with_username_clears_sqlite_in_coffin发送的是RevivalActionKind::Reincarnate(依赖chance: 0.0使roll_rebirth必失后转入 terminate 分支),这本身是合理的路径构造,但名字读起来像直接发Terminateintent;同时该测试还额外承担了 precommit 失败 + 显式重试的原子性验证,已超出名字所述范围。建议重命名为failed_tribulation_reincarnate_retries_then_clears_sqlite_and_runtime_coffin之类,或把重试段拆成独立用例。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/combat/lifecycle.rs` around lines 11414 - 11556, Rename the test function containing the `RevivalActionKind::Reincarnate` intent and failpoint/retry assertions to reflect failed tribulation, reincarnation retry, and clearing durable/runtime coffin state, such as `failed_tribulation_reincarnate_retries_then_clears_sqlite_and_runtime_coffin`. Keep the existing test behavior unchanged; do not imply it directly sends a terminate intent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/plan-satiety-hydration-v1.md`:
- Around line 240-242: 在“单次 consume-plan 全自动到 merge”小节中逐项补充强制门禁:每个逻辑单元必须使用中文
atomic commit;agent commit 必须包含真实模型 ID 的“Model” trailer;PR review
只能由独立的“/review”触发;review 产生修改后必须重新等待 re-review。确保自动流程仅在所有门禁满足且复审完成后合并。
- Line 122: 补齐水囊灌装流程中 ClientRequestV1::WaterSkinFill 的网络契约:定义成功与失败 response
shape 及明确错误码,规定请求 ID
的幂等处理、重试行为和最终收敛规则;明确提交成功但响应丢失时客户端如何依据服务端权威库存恢复为满水囊状态,并增加覆盖该场景的重试测试。
In `@server/src/combat/lifecycle.rs`:
- Around line 4684-4693: 将断言中的 zone_tolerance 和 audit_tolerance
容差表达式提取为模块级具名常量,并为 16/8 系数添加注释,说明 spirit_qi 归一化存储及往返换算最多引入的 ulp
数量。更新相关断言以复用这些常量,保持现有容差计算与校验行为不变。
- Around line 2554-2564: 在处理 TerminationPlayerContext 的生命周期分支前,新增具名布尔条件(如
offline_simulation),明确检查 username、player_persistence 与 staged_qi_release
均为空;将该条件用于 identity-less 的 legacy 路径,并更新注释说明仅完全无身份且无正 qi 时才允许回退,其他部分身份继续进入
fail-closed 分支。
- Around line 2451-2656: Refactor terminate_lifecycle_with_death_context by
extracting the qi-release staging, player-bundle validation, and
persistence-path selection into a stage_termination_persistence(...) helper, and
extracting post-persistence runtime updates and event publication into
commit_termination_runtime(...). Centralize failure handling so every staging or
persistence error removes the staged biography entry exactly once, while
preserving the identity-less compatibility branch and existing persistence
behavior.
- Around line 2047-2115: Replace the 14-field tuple match in the revival staging
flow with per-field presence checks that collect each missing component’s name,
covering all staged cultivation lifecycle fields. If any required value is
absent, log the complete missing-field list and return false; otherwise preserve
the existing staged values and revival behavior.
- Around line 9535-10094: 将
create_new_character_precommit_failure_rolls_back_sqlite_and_ecs
拆分为多个按契约组织的测试,避免单个用例同时覆盖所有回滚行为;抽取共享的场景构造与失败触发 fixture,然后分别建立 ECS 回滚、SQLite
回滚、coffin 四件套回滚和 tribulation 运行时回滚用例。确保各用例仅保留对应断言,并复用同一 fixture 与 failpoint
设置逻辑。
- Around line 2685-2725: The reset_for_new_character function has too many
order-dependent parameters. Introduce NewCharacterRuntime and
NewCharacterResources structs grouping the runtime component options and
resource references, update reset_for_new_character to accept these structs, and
revise all call sites to construct them with named fields so parameter ordering
cannot cause silent mismatches.
- Around line 1287-1297: 在处理 CreateNewCharacter 的 deferred_creates 入队逻辑中,按
intent.entity 去重,避免同一实体连续多帧重复入队导致队列增长;保留已有的 Update 边界处理和其他 action 的 executable
流程不变。
In `@server/src/cultivation/mod.rs`:
- Around line 800-807: Separate deterministic validation failures in the
cultivation hydration flow from transient I/O failures before inserting
CultivationAttachRetry. For failures such as unknown race, lifecycle/termination
conflicts, identity mismatches, and bundle validation errors, avoid an
every-frame retry by recording a throttled next-attempt tick (or rejecting once
with an alert); retain immediate retry behavior for the I/O failures in the
existing load path. Update the retry-marker handling and the failure branches
around load_player_cultivation_bundle, load_player_lifecycle_slice, and
load_current_character_id accordingly.
In `@server/src/persistence/mod.rs`:
- Around line 8158-8229: 将 persist_termination_transition_inner 和
persist_life_record_death_insight 提交后的导出流程改为按当前 char_id 增量更新对应 snapshot,并仅增量重建
_index.json,避免调用 reconcile_public_deceased_exports 对全部记录执行 O(N) 写盘;保留
reconcile_public_deceased_exports 作为启动或显式兜底的全量路径,并在该全量路径中清理
deceased_public_dir() 下不再对应索引记录的旧 JSON 文件。
- Around line 8231-8279: Update atomic_replace_file so the parent-directory sync
after fs::rename is only performed on Unix via #[cfg(unix)], while preserving
file syncing and rename behavior on other platforms. Document the platform
limitation or provide an established Windows-compatible persistence mechanism;
ensure reconcile_public_deceased_exports can rebuild successfully on Windows
when directory fsync is unavailable.
- Around line 8502-8511: 删除未再被 Rust 调用的辅助函数 rollback_file
及其相关死代码,确保移除后不留下无效引用或不必要的导入。
In `@server/src/world/dimension.rs`:
- Around line 122-139: The PreserveUnrelatedLayers branch in
server/src/world/dimension.rs lines 122-139 must preserve unrelated layers when
previous_layer is None by removing only dimension layers, not clearing the set.
Replace the hardcoded layers.tsy removal at lines 131-131 with iteration over
all non-overworld layers derived from DimensionLayers, adding an all_layers() or
non_overworld_layers() helper if appropriate; both sites should be updated to
support future dimensions.
- Line 131: 更新 PreserveUnrelatedLayers 中对 visible_layers 的清理逻辑,不要仅硬编码移除
layers.tsy;遍历 DimensionLayers 的全部非 overworld 层并移除,或复用其新增的 all_layers()
接口,确保未来新增维度层时也不会残留可见性。
---
Outside diff comments:
In `@server/src/combat/lifecycle.rs`:
- Around line 11414-11556: Rename the test function containing the
`RevivalActionKind::Reincarnate` intent and failpoint/retry assertions to
reflect failed tribulation, reincarnation retry, and clearing durable/runtime
coffin state, such as
`failed_tribulation_reincarnate_retries_then_clears_sqlite_and_runtime_coffin`.
Keep the existing test behavior unchanged; do not imply it directly sends a
terminate intent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 9207ac9d-1f43-45ad-9ae3-700e0af01ac8
📒 Files selected for processing (22)
docs/plan-satiety-hydration-v1.mdserver/src/cmd/dev/mod.rsserver/src/cmd/dev/nourish.rsserver/src/cmd/mod.rsserver/src/coffin/mod.rsserver/src/combat/lifecycle.rsserver/src/combat/mod.rsserver/src/combat/tests.rsserver/src/cultivation/death_hooks.rsserver/src/cultivation/epitaph.rsserver/src/cultivation/mod.rsserver/src/inventory/mod.rsserver/src/network/audio_trigger.rsserver/src/network/inventory_snapshot_emit.rsserver/src/network/mod.rsserver/src/nourishment/mod.rsserver/src/nourishment/tick.rsserver/src/npc/lifecycle.rsserver/src/persistence/mod.rsserver/src/player/mod.rsserver/src/player/state.rsserver/src/world/dimension.rs
📜 Review details
🔇 Additional comments (49)
server/src/nourishment/mod.rs (2)
529-553: 🗄️ Data Integrity & Integration
persist_player_cultivation_bundle_with_nourishment长参数列表问题仍未解决。此前已提出应将该函数参数收敛为具名结构体(尾部多个
Option/Some(...)连续排列,错位后编译器无法捕获),当前调用点依然是原始的位置参数形式,未标记为已处理。
541-541: LGTM!server/src/nourishment/tick.rs (2)
10-10: LGTM!Also applies to: 164-164, 174-178
250-289: LGTM!server/src/cmd/dev/mod.rs (1)
17-17: LGTM!Also applies to: 66-66
server/src/cmd/dev/nourish.rs (1)
50-53: LGTM!Also applies to: 123-123
server/src/cmd/mod.rs (1)
73-73: LGTM!Also applies to: 86-91
server/src/persistence/mod.rs (2)
4297-4433: LGTM!Also applies to: 6512-6600, 6602-6682
10925-11210: LGTM!Also applies to: 12775-12921
server/src/player/mod.rs (2)
1252-1298: LGTM!Also applies to: 1489-1580, 1852-1918
137-140: 🩺 Stability & Availability无需修改:这两个系统已在
Update中注册。
attach_cultivation_to_joined_clients与handle_revival_action_intents都在server/src/cultivation/mod.rs/server/src/combat/lifecycle.rs的app.add_systems(Update, ...)中注册,.after()约束不会在不同 schedule 中被忽略。server/src/player/state.rs (2)
618-636: LGTM!Also applies to: 858-885, 890-924
1153-1194: LGTM!Also applies to: 2529-2566, 4395-4449
server/src/combat/lifecycle.rs (9)
167-199: 📐 Maintainability & Code Quality查询元组已嵌套四层,建议抽成具名
QueryData结构体。
NearDeathPersistenceQueryItem现在在near_death_tick与handle_revival_action_intents两处按同一形状解包 30+ 字段,新增/删除字段需同时改 alias 与两个调用点,且靠位置识别字段极易错位。
3548-3553: 📐 Maintainability & Code Quality测试内
RevivalObservation建议改用#[derive(Resource)]。与生产代码 Line 213-216 的
PendingPlayerRevivedCompletions同样是手写空impl Resource,项目内其余 Resource 多用 derive,统一风格可减少后续宏增强带来的不一致。
1889-1972: LGTM!Also applies to: 2199-2212
2019-2035: LGTM!Also applies to: 2216-2283
2727-2756: LGTM!Also applies to: 2884-2898, 3050-3057
4403-4423: LGTM!
6313-6413: LGTM!
213-225: 🩺 Stability & Availability确认生产调度已注册
PendingPlayerRevivedCompletions与emit_player_revived_completions。
handle_revival_action_intents现在写入待发布资源而非直接发送事件;如果调度层未init_resource,系统参数会在运行时 panic;如果未注册,后续已注册的on_player_revived将永远不会收到PlayerRevived,复活下游 hook 会静默失效。请检查生产调度注册是否按apply_deferred后、on_player_revived前的顺序完成。
2758-2786: 🗄️ Data Integrity & Integration旧
Cultivation按 0 提交不会漏还真元。
reset_for_new_character只被LifecycleState::Terminated的转世分支调用;正式终结路径先用stage_lifecycle_qi_release(...current qi...)把旧命真元打入ZoneRegistry/WorldQiAccount并 emitQiTransfer,成功后才发布PlayerTerminated;on_player_terminated随后移除Cultivation。当前路径不会再走一次CreateNewCharacter,也不会进入old_cultivation.map_or(0.0, ...)。> Likely an incorrect or invalid review comment.server/src/coffin/mod.rs (2)
251-265: LGTM!Also applies to: 342-342, 362-379
335-335: 🩺 Stability & Availability无需移除重复的
add_event::<SneakEvent>()。当前 Bevy 0.14.2 的实现已使同一事件类型的
add_event幂等:若Events<T>/事件维护系统已存在则不会重复插入,因此valence注册后再注册不会替换事件资源或重复注册event_update_system。server/src/world/dimension.rs (1)
88-121: LGTM!Also applies to: 141-153
server/src/cultivation/death_hooks.rs (3)
83-85: LGTM!Also applies to: 261-278
344-352: LGTM!Also applies to: 428-428, 458-458
852-948: LGTM!Also applies to: 1329-1433
server/src/cultivation/epitaph.rs (1)
984-987: LGTM!Also applies to: 1037-1040, 1064-1067, 1111-1114, 1155-1158, 1196-1199, 1237-1240, 1276-1279, 1323-1326, 1370-1373, 1415-1418, 1484-1487, 1561-1564, 1641-1644, 1795-1798, 1834-1837, 1885-1888, 1940-1943
server/src/cultivation/mod.rs (3)
568-583: LGTM!Also applies to: 605-614, 838-853, 945-952, 1134-1141, 1274-1314, 1316-1409, 1419-1485
2731-2875: LGTM!Also applies to: 2971-3520, 3800-4194
976-978: 🩺 Stability & Availability无需修改:零 qi 时 staging 返回
None,不会触发提交阶段的资源expect。
stage_lifecycle_qi_release对amount == 0.0直接返回Ok(None),因此staged_old_qi_release为Some的前提已经排除了零 qi 场景。server/src/npc/lifecycle.rs (1)
1032-1035: LGTM!Also applies to: 1265-1267
server/src/inventory/mod.rs (3)
883-899: 调度顺序符合预期。
apply_death_drop_on_revive挂在emit_player_revived_completions之后、apply_termination_drop_on_terminate挂在handle_revival_action_intents之后,与network/mod.rs、combat/mod.rs中对应的下游调度(inventory/skill resync、rebirth events 等)保持一致。
15159-15194: LGTM!
12247-12250: 🗄️ Data Integrity & Integration无需修改:
PlayerTerminated.settlement_committed已有消费路径。
death_hooks.rs中PlayerTerminated的处理器会读取ev.settlement_committed,因此这里设置默认值不构成未使用的契约字段问题。> Likely an incorrect or invalid review comment.server/src/network/audio_trigger.rs (2)
109-124: LGTM!
2543-2544: LGTM!Also applies to: 2569-2570, 2657-2658
server/src/network/inventory_snapshot_emit.rs (1)
1062-1129: LGTM! 该测试精确锁定了"复活库存重同步必须观察到死亡掉落之后的库存"这一跨系统调度契约,是本 PR 调度重构的一个很好的回归防护。server/src/network/mod.rs (3)
789-793: LGTM!
931-963: LGTM!
1006-1015: LGTM!server/src/combat/mod.rs (3)
91-115: LGTM! 用(With<Client>, With<Cultivation>, Without<Wounds>)替换Added<Client>单帧窗口,解决了修炼数据延迟挂载时错过战斗组件挂载时机的问题,且有对应回归测试覆盖。
264-264: LGTM!
343-368: 🩺 Stability & Availability超时自动复活的
PlayerRevived结算会自然延迟一 tick,这不是当前改动引入的问题。
auto_confirm_revival_decisions只是发射RevivalActionIntent,handle_revival_action_intents才消费并写入PendingPlayerRevivedCompletions;由于它在当前 resolve 顺序中被排在emit_player_revived_completions之后,超时自动复活对应的一次PlayerRevived及后续回调只能在下一次 resolve 被 drain,与主动提交复活的行为边界一致。server/src/combat/tests.rs (3)
30-91: LGTM! 这两条新测试准确覆盖了combat/mod.rs中战斗组件挂载重试逻辑的关键语义(等待 Cultivation、Terminated Lifecycle 在无 Cultivation 时保持不可见)。
149-152: LGTM! 为满足attach_combat_bundle_to_joined_clients新查询过滤器(需要With<Cultivation>)而统一补充Cultivation::default(),改动机械且一致。Also applies to: 184-187, 216-219, 239-242, 311-314, 382-385, 439-442, 477-480, 531-534, 609-612
337-343: LGTM! 放宽为区间断言,规避同进程内墙钟秒级截断导致的最多 1 秒偏差引起的测试抖动,同时仍校验了 deadline 折算方向正确。docs/plan-satiety-hydration-v1.md (1)
96-96: 🗄️ Data Integrity & Integration明确复活真元的来源金额与转移身份
PlayerRevived::FormalPenaltyApplied的审计released_qi和 plan 描述的“处罚后qi_current = 0+ “同额真元”回流”之间存在歧义:如果released_qi等于处罚前剩余真元,则和qi_current = 0一致;如果指化虚配额,则来源字段、from账户、释放条件/边界和released == accepted + overflow的 pin test 仍需要明确定义,避免复活路径凭空增发或漏记真元。
🛑 Comments failed to post (8)
server/src/combat/lifecycle.rs (7)
1287-1297: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value
延迟队列在实体消失后会残留 intent,建议按实体存活性剔除。
deferred_creates是Local<Vec<..>>,若玩家在同一 Update 内断线/实体被 despawn,下一帧lifecycle_q.get_mut失败只是continue,intent 被丢弃——行为正确但静默。更值得注意的是同一实体连续多帧重复发送CreateNewCharacter会不断入队,仅靠LifecycleState::Terminated门禁去重;建议入队时按intent.entity去重,避免队列在异常客户端下线性增长。♻️ 建议按实体去重
if intent.action == RevivalActionKind::CreateNewCharacter { - deferred_creates.push(intent.clone()); + if !deferred_creates + .iter() + .any(|pending| pending.entity == intent.entity) + { + deferred_creates.push(intent.clone()); + } } else {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.// A fresh CreateNewCharacter must cross an Update boundary so all consumers of a // same-frame termination settle the old life before this entity publishes a new one. let mut executable = std::mem::take(&mut *deferred_creates); for intent in intents.read() { if intent.action == RevivalActionKind::CreateNewCharacter { if !deferred_creates .iter() .any(|pending| pending.entity == intent.entity) { deferred_creates.push(intent.clone()); } } else { executable.push(intent.clone()); } } for intent in executable {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/combat/lifecycle.rs` around lines 1287 - 1297, 在处理 CreateNewCharacter 的 deferred_creates 入队逻辑中,按 intent.entity 去重,避免同一实体连续多帧重复入队导致队列增长;保留已有的 Update 边界处理和其他 action 的 executable 流程不变。
2047-2115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
14 元组
match用于全量存在性校验,建议改为逐项ok_or收集缺失字段名。当前写法把 14 个
Option装进元组再整体匹配,任一缺失只得到一条"bundle is missing"日志,排障时无法知道缺哪个组件;而测试revival_rejects_each_newly_required_cultivation_sibling_individually恰恰在逐项验证。建议改为按字段let Some(x) = .. else { missing.push("x") }或小 helper,日志带上缺失字段列表。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/combat/lifecycle.rs` around lines 2047 - 2115, Replace the 14-field tuple match in the revival staging flow with per-field presence checks that collect each missing component’s name, covering all staged cultivation lifecycle fields. If any required value is absent, log the complete missing-field list and return false; otherwise preserve the existing staged values and revival behavior.
2451-2656: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
terminate_lifecycle_with_death_context已接近 250 行、承担 5 类职责,建议拆分。单个函数内混合了:生平记录 staging、qi 释放 staging、玩家 bundle 完整性校验(含 identity-less 兼容分支)、三条持久化路径分发、以及提交后的 runtime/事件发布。任一分支的失败回滚都要手动
life_record.biography.pop(),极易在后续修改时漏掉一处。建议抽出stage_termination_persistence(...) -> Result<TerminationPersistenceBundle, _>与commit_termination_runtime(...)两个子函数,让回滚点集中在一处。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/combat/lifecycle.rs` around lines 2451 - 2656, Refactor terminate_lifecycle_with_death_context by extracting the qi-release staging, player-bundle validation, and persistence-path selection into a stage_termination_persistence(...) helper, and extracting post-persistence runtime updates and event publication into commit_termination_runtime(...). Centralize failure handling so every staging or persistence error removes the staged biography entry exactly once, while preserving the identity-less compatibility branch and existing persistence behavior.
2554-2564: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
identity-less 回退分支的判据建议收紧为显式条件。
_ if username.is_none() && player_persistence.is_none() && staged_qi_release.is_none()依赖"前一个 arm 未命中"这一隐式前提;一旦将来给TerminationPlayerContext增加字段,部分身份仍会落到_ =>的 fail-closed 分支(这是期望行为),但读者很难从代码看出这一点。建议把该判据提升为在match之前计算的具名布尔(如let offline_simulation = ...),并在注释里明确"仅当完全无身份且无正 qi 时走 legacy 路径"。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/combat/lifecycle.rs` around lines 2554 - 2564, 在处理 TerminationPlayerContext 的生命周期分支前,新增具名布尔条件(如 offline_simulation),明确检查 username、player_persistence 与 staged_qi_release 均为空;将该条件用于 identity-less 的 legacy 路径,并更新注释说明仅完全无身份且无正 qi 时才允许回退,其他部分身份继续进入 fail-closed 分支。
2685-2725: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
reset_for_new_character已有 30+ 位置参数,建议改为入参结构体。签名跨越 40 行、包含 8 个
Option<Mut<'_, _>>与 10 个资源引用,调用点(Line 1526-1564)只能靠顺序对齐;任何插入/重排都是无声的类型兼容错误风险(多个Option<&mut ZoneRegistry>/Option<&mut WorldQiAccount>之外,还有多组同类型Option<Mut<_>>)。建议拆成NewCharacterRuntime { .. }+NewCharacterResources { .. }两个结构体传入。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/combat/lifecycle.rs` around lines 2685 - 2725, The reset_for_new_character function has too many order-dependent parameters. Introduce NewCharacterRuntime and NewCharacterResources structs grouping the runtime component options and resource references, update reset_for_new_character to accept these structs, and revise all call sites to construct them with named fields so parameter ordering cannot cause silent mismatches.
4684-4693: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
容差表达式内联在断言中,建议提取为具名常量并加注释说明推导。
f64::EPSILON * QI_ZONE_UNIT_CAPACITY * 16.0与amount.abs().max(1.0) * f64::EPSILON * 8.0两个魔法系数(16/8)没有说明来源,后续调整 zone 存储精度时无从判断是否仍成立。建议提到模块级const并注明"zone 以 spirit_qi 归一化存储,往返换算引入至多 N 个 ulp"。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/combat/lifecycle.rs` around lines 4684 - 4693, 将断言中的 zone_tolerance 和 audit_tolerance 容差表达式提取为模块级具名常量,并为 16/8 系数添加注释,说明 spirit_qi 归一化存储及往返换算最多引入的 ulp 数量。更新相关断言以复用这些常量,保持现有容差计算与校验行为不变。
9535-10094: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff
单个测试 560 行、80+ 断言,建议按契约拆分为多个用例。
create_new_character_precommit_failure_rolls_back_sqlite_and_ecs同时验证 lifecycle/life_record/player_state/craft/nourishment/tutorial/11 个 tribulation 组件/两个队列/coffin 四件套/SQLite 六张表的回滚。一旦失败,定位到具体契约要靠人工读断言顺序;且 fixture 构建段(Line 9541-9783)与断言段严重不对称。建议抽出共享 fixture 构造函数,再拆成 "ECS 回滚"、"SQLite 回滚"、"coffin 四件套回滚"、"tribulation 运行时回滚" 四个用例。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/combat/lifecycle.rs` around lines 9535 - 10094, 将 create_new_character_precommit_failure_rolls_back_sqlite_and_ecs 拆分为多个按契约组织的测试,避免单个用例同时覆盖所有回滚行为;抽取共享的场景构造与失败触发 fixture,然后分别建立 ECS 回滚、SQLite 回滚、coffin 四件套回滚和 tribulation 运行时回滚用例。确保各用例仅保留对应断言,并复用同一 fixture 与 failpoint 设置逻辑。server/src/cultivation/mod.rs (1)
800-807: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
确定性校验失败也挂
CultivationAttachRetry,会形成每帧重读 SQLite 的无限重试。
CultivationAttachRetry只在成功 hydration(L1276)时移除,因此 marker 存在期间该实体每个Update都会重跑load_player_cultivation_bundle+load_player_lifecycle_slice+load_current_character_id三次 SQLite 查询。对未知 race、life_record 与 lifecycle 终结状态冲突、身份不匹配、bundle 校验失败(L710/719/734/761/771/849)这类确定性损坏,重试永远不会自愈,等于在 20 tps 的请求线程上对每个受影响在线玩家做持续磁盘 IO,且日志按帧刷屏。建议区分两类失败:IO/瞬态失败(L656/675/694)保留逐帧重试或加退避;确定性校验失败改为带节流的重试(记录上次尝试 tick,间隔 N tick 再试)或一次性拒绝 + 告警,避免热路径打满。
♻️ 一种带节流的 marker 形态
-#[derive(Debug, Component)] -pub(crate) struct CultivationAttachRetry; +#[derive(Debug, Component)] +pub(crate) struct CultivationAttachRetry { + /// 下一次允许重试的 combat clock tick,避免确定性损坏数据每帧重读 SQLite。 + pub(crate) next_attempt_tick: u64, +}随后在过滤/循环入口按
CombatClock比较next_attempt_tick,确定性失败时写入一个较大的间隔,IO 失败时写入下一帧。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@server/src/cultivation/mod.rs` around lines 800 - 807, Separate deterministic validation failures in the cultivation hydration flow from transient I/O failures before inserting CultivationAttachRetry. For failures such as unknown race, lifecycle/termination conflicts, identity mismatches, and bundle validation errors, avoid an every-frame retry by recording a throttled next-attempt tick (or rejecting once with an alert); retain immediate retry behavior for the I/O failures in the existing load path. Update the retry-marker handling and the failure branches around load_player_cultivation_bundle, load_player_lifecycle_slice, and load_current_character_id accordingly.
Central reviewDecision: The read-only review job did not produce a verifiable artifact. No code verdict was inferred. |
Model: grok-4.5 Co-Authored-By: Claude <noreply@anthropic.com>
Model: grok-4.5 Co-Authored-By: Claude <noreply@anthropic.com>
Model: grok-4.5 Co-Authored-By: Claude <noreply@anthropic.com>
Model: grok-4.5 Co-Authored-By: Claude <noreply@anthropic.com>
显式保证 CombatClock 先于移动状态推进,并让真实 join 水合在全局边界 claim 前完成;补充 tick 200 加速、扣减与不重放回归。 Model: grok-4.5 Co-Authored-By: Claude <noreply@anthropic.com>
Model: cc-default Co-Authored-By: Claude <noreply@anthropic.com>
Model: cc-default Co-Authored-By: Claude <noreply@anthropic.com>
Model: cc-default Co-Authored-By: Claude <noreply@anthropic.com>
将正式复活的玩家状态、真元回流与运行时发布收束到同一事务边界,并锁定跨 tick 活动窗口和异步事件的角色生命身份。 Model: cc-default Co-Authored-By: Claude <noreply@anthropic.com>
Model: gpt-5.6-sol-xhigh Co-Authored-By: Claude <noreply@anthropic.com>
终结角色 join hydration 在完整 bundle 解码前保持 fail-closed,并通过显式 retry 延后身份轮换。补齐所有切片解码边界与亚 epsilon 正额真元修复后原子重试回归。 Model: gpt-5.6-sol-xhigh Co-Authored-By: Claude <noreply@anthropic.com>
将战斗组件水合收紧为修炼 bundle 成功后的可重试下游步骤,并让新角色事务在旧 Cultivation 缺失时 fail-closed。补齐生产链、延迟重试与持久身份不变回归。 Model: gpt-5.6-sol-xhigh Co-Authored-By: Claude <noreply@anthropic.com>
仅在修炼水合明确等待时拒绝创建新角色,保留正式终结已完成结算后的延迟重建路径。 Model: gpt-5.6-sol-xhigh Co-Authored-By: Claude <noreply@anthropic.com>
以当前角色的持久化生命周期补充终结判定,损坏生平卷时拒载整份修炼存档并保留旧真元,修复后再原子转生。 Model: gpt-5.6-sol-xhigh Co-Authored-By: Claude <noreply@anthropic.com>
Model: gpt-5.6-sol-xhigh Co-Authored-By: Claude <noreply@anthropic.com>
Model: gpt-5.6-sol-xhigh Co-Authored-By: Claude <noreply@anthropic.com>
Model: cc-sonnet-max Co-Authored-By: Claude <noreply@anthropic.com>
Model: claude-sonnet-5 Co-Authored-By: Claude <noreply@anthropic.com>
Model: claude-sonnet-5 Co-Authored-By: Claude <noreply@anthropic.com>
Model: claude-sonnet-5 Co-Authored-By: Claude <noreply@anthropic.com>
Model: claude-sonnet-5 Co-Authored-By: Claude <noreply@anthropic.com>
Model: claude-sonnet-5 Co-Authored-By: Claude <noreply@anthropic.com>
Model: claude-sonnet-5 Co-Authored-By: Claude <noreply@anthropic.com>
Model: claude-sonnet-5 Co-Authored-By: Claude <noreply@anthropic.com>
a15b8de to
4d0f808
Compare
Central reviewDecision: The read-only review job did not produce a verifiable artifact. No code verdict was inferred. |
1 similar comment
Central reviewDecision: The read-only review job did not produce a verifiable artifact. No code verdict was inferred. |
|
Closing as stale: 4 of 5 review rounds died on infrastructure, and the refactor program (master plan v1 + #1902 adjudication) has since redefined the server systems this PR touches - a July pre-refactor implementation base cannot merge cleanly into the post-refactor architecture. Branch auto/plan-satiety-hydration-v1-pr1 (head 4d0f808, 34 commits) is preserved for salvage. Satiety-hydration implementation re-enters via the wave table on the refactored baseline; the merged plan skeleton #1229 remains the design reference. |
自动消费
docs/plan-satiety-hydration-v1.md的 PR-1(P0 server 底盘)。实施摘要
CombatClock全局 200-tick sweep:同一边界全服在线玩家统一结算,exactly-once,跳过边界不追债,重复/回退时钟不重放。MovementEvent按真实水平路程聚合,严格> 0.05才算移动;dash 优先。边界 tick 先采样再结算,断线/重启不继承活动状态。tick_combat_clock → accepted movement/dash → activity sample → global sweep → revival intent。/nourish set satiety|hydration <value>与/nourish showdev-only typed literal 命令树契约。Reincarnategate 拒绝与 SQLite open 失败原子性矩阵,锁定 ECS、SQLite 和PlayerRevived均不发生半提交。本地门禁(HEAD
28e85e089e6ace11371fa22577961a9bda844516)cargo test nourishment --lib:27 passed,0 failed。cargo test combat::lifecycle --lib:55 passed,0 failed。cargo test --lib:11,914 passed,0 failed,2 ignored。cargo clippy --all-targets -- -D warnings:通过。rustfmt --edition 2021 --check:通过。git diff --check:通过。28e85e089修复。评审意见处理
shelflife/sweep.rs先例恢复全局CombatClockboundary;删除 per-player cursor、20-tick nourishment movement lease 和 activity persistence。PlayerRevived先于 reset 的建议:BevyEventWriter::send只写入事件队列,不会同步调用 reader;事件应公告已完成的复活状态。生产成功链与两条失败原子性测试现已锁定该契约。PR 边界
本 PR 仅完成五 PR 序列中的 PR-1,不提前写 Finish Evidence 或归档整个 plan。PR-2 至 PR-5 串行继续。
有阻断意见请加前缀
BLOCKING:或选 Request changes。主导实现/测试模型:
grok-4.5(Sonnet 路由)预提交审查:Sonnet finder/skeptic + 单次范围受限 Opus 裁决;固定 HEAD 复验使用 Sonnet 路由。
🤖 Generated by /consume-plan