diff --git a/biz/service/level_play.go b/biz/service/level_play.go index b0254d8..c8ef7da 100644 --- a/biz/service/level_play.go +++ b/biz/service/level_play.go @@ -156,6 +156,11 @@ func (s *levelPlay) Choose(ctx context.Context, parentUid, childId, levelId, nod return node, nil, nil } + // v2.1:终局节点剧本先于结算构建(只读查询失败则未结算,无副作用) + finalNode, err := nodeOf(ctx, levelRec, nextRec) + if err != nil { + return nil, nil, err + } // 终局:路径流水在锁内结算后记录,避免状态读到本次到达导致重复结算误判 settle, err := common.WithLock(ctx, fmt.Sprintf("child:%d:level:%d", childId, levelId), 10*time.Second, 3, 200*time.Millisecond, func() (*FinalSettle, error) { return s.settle(ctx, child, levelRec, nextRec, nodeId, optionId) @@ -163,11 +168,6 @@ func (s *levelPlay) Choose(ctx context.Context, parentUid, childId, levelId, nod if err != nil { return nil, nil, err } - // v2.1:终局节点剧本随结算返回(前端先演绎结局台词再跳结算页) - finalNode, err := nodeOf(ctx, levelRec, nextRec) - if err != nil { - return nil, nil, err - } settle.FinalNode = finalNode return nil, settle, nil } diff --git a/docs/superpowers/plans/2026-08-13-story-theater-v2.md b/docs/superpowers/plans/2026-08-13-story-theater-v2.md index 5bd86f4..05a9602 100644 --- a/docs/superpowers/plans/2026-08-13-story-theater-v2.md +++ b/docs/superpowers/plans/2026-08-13-story-theater-v2.md @@ -1146,18 +1146,30 @@ git commit -m "docs: 剧情演绎文档补充 + 全量剧本生成导入" - [ ] **Step 2: Choose 终局分支返回终局 Node** -`biz/service/level_play.go` Choose 中 `WithLock` 调用之后、`return nil, settle, nil` 之前插入: +`biz/service/level_play.go` Choose 终局分支改为**先 nodeOf 再结算**(内容表只读查询失败则未结算,无副作用;与决策分支同顺序): ```go - // v2.1:终局节点剧本随结算返回(前端先演绎结局台词再跳结算页) + // v2.1:终局节点剧本先于结算构建(只读查询失败则未结算,无副作用) finalNode, err := nodeOf(ctx, levelRec, nextRec) if err != nil { return nil, nil, err } + // 终局:路径流水在锁内结算后记录,避免状态读到本次到达导致重复结算误判 + settle, err := common.WithLock(ctx, fmt.Sprintf("child:%d:level:%d", childId, levelId), 10*time.Second, 3, 200*time.Millisecond, func() (*FinalSettle, error) { + return s.settle(ctx, child, levelRec, nextRec, nodeId, optionId) + }) + if err != nil { + return nil, nil, err + } settle.FinalNode = finalNode + return nil, settle, nil ``` -(`nodeOf` 与决策分支同函数、同签名,构建选项 + 元素 + buildNode;只读查询,锁外执行安全。) +(`nodeOf` 与决策分支同函数、同签名,构建选项 + 元素 + buildNode;只读查询,锁外执行安全。审查补充:nodeOf 与结算无数据依赖,先构建可避免「已结算但返回 err」的异常路径。) + +> 审查记录(aea79d8 双审查 APPROVE): +> - SHOULD-FIX(已修):原实现「锁内结算 → 锁外 nodeOf」,nodeOf 失败时已结算却返回 err(logRoute 多写一条终局流水 → FailStreak 误算)。改为 nodeOf 先于 WithLock,失败则未结算。 +> - NIT(不修):终局节点无选项,nodeOf 内 NodeOption 查询返回空集,成本极低;FinalNode 透出链路无新增单测,Step 5 既有测试通过即可 - [ ] **Step 3: dto FinalSettle 加 FinalNode**