123 lines
16 KiB
Markdown
123 lines
16 KiB
Markdown
# 阶段4C2扣费前拦截诊断方案独立审查
|
||
|
||
2026-09-24。独立评审agent输出。审查对象:《阶段4C2扣费前拦截诊断最小方案-待评审-2026-09-24.md》(sha256 2877E083…F249)。本审查为只读审查,除本报告外未写任何文件,未修改任何共享文档/TODO/开发日志/生产代码,未运行Unity,未连接正式服务器或测试账号。
|
||
|
||
## 一、审查范围与方法
|
||
|
||
### 1.1 读过并核对的文件
|
||
|
||
- 方案与记录:《阶段4C2扣费前拦截诊断最小方案-待评审-2026-09-24.md》、《阶段4C2首次真实尝试与扣费前拦截记录-2026-09-24.md》、《其他AI审查提示词-4C2扣费前拦截诊断-2026-09-24.md》、《接手入口.md》(第1~60行当前状态与硬约束部分及全文关键词检索)。
|
||
- 生产源码:`Client/Assets/Scripts/GampPlay/FishingB/FishingBehaviorB.cs`(第1~100行、第195~210行、第795~810行)、`Client/Assets/Scripts/GampPlay/FishingB/FishingData.cs`(全文)、`Client/Assets/Scripts/Tables/Item.cs`(ID/SubType声明)、`Client/Assets/Scripts/Tables/FishData.cs`(ID声明)、`Client/Assets/Scripts/Services/UserService.KVGameplayLoop.cs`(第180~424行)、`Client/Assets/Scripts/Services/UserService.KVFishingReward.cs`(PrepareFishingReward相关行)、`Client/Assets/Scripts/Services/UserService.KVFishingValidation.cs`(全文,Editor固定测试)。
|
||
- 测试与证据:`KVFishingRewardTests/ConsumeControlSlice.cs.txt`、`ConsumeControlTests.cs.txt`、`ConsumeControlTests.csproj`、`KVGameplayLoopTests/runs/42620_639258392922105429/events.jsonl`、`KVFishingRewardTests/real-attempt-final-diff.json`、`real-after-attempt-errors.json`、`real-login-reconciliation.json`、`real-final-idle-state.json`、`real-precast-conditions-retry.json`、两份102备份(094234Z/094501Z)。
|
||
- 独立验证:用powershell Get-FileHash逐项计算18项SHA256(大写hex)与字节数;用ConvertFrom-Json对两份102备份做45键逐值比对;全库检索固定错误串"Only one ordinary paid fishing action"确认无逻辑依赖该文本。
|
||
|
||
### 1.2 hash核对结果表(基目录 C:\projects\ft,Get-FileHash SHA256 大写hex + 字节数)
|
||
|
||
| # | 文件 | 期望hash(清单) | 实算hash | 期望/实算字节 | 结论 |
|
||
|---|---|---|---|---|---|
|
||
| 1 | Client/Assets/Scripts/GampPlay/FishingB/FishingBehaviorB.cs | 0A76743E…9BE8 | 一致 | 99010/99010 | 一致 |
|
||
| 2 | Client/Assets/Scripts/GampPlay/FishingB/FishingData.cs | 8C7DC3D3…0A4 | 一致 | 8230/8230 | 一致 |
|
||
| 3 | Client/Assets/Scripts/Services/UserService.KVGameplayLoop.cs | 930D1FF0…B4A | 一致 | 40556/40556 | 一致 |
|
||
| 4 | Client/Assets/Scripts/Services/UserService.KVFishingReward.cs | 3104B335…D101 | 一致 | 20005/20005 | 一致 |
|
||
| 5 | CodexOutput/upserver/阶段4C2扣费前拦截诊断最小方案-待评审-2026-09-24.md | 2877E083…F249 | 一致 | 3020/3020 | 一致 |
|
||
| 6 | CodexOutput/upserver/阶段4C2首次真实尝试与扣费前拦截记录-2026-09-24.md | 75613397…3EB6 | 一致 | 4464/4464 | 一致 |
|
||
| 7 | CodexOutput/upserver/其他AI审查提示词-4C2扣费前拦截诊断-2026-09-24.md | EADC12D4…994F | 一致 | 896/896 | 一致 |
|
||
| 8 | CodexOutput/upserver/接手入口.md | 79A0D1AC…2F58 | 一致 | 57695/57695 | 一致 |
|
||
| 9 | CodexOutput/upserver/KVFishingRewardTests/real-after-attempt-errors.json | 657BF9AD…61B4 | 一致 | 3530/3530 | 一致 |
|
||
| 10 | CodexOutput/upserver/KVFishingRewardTests/real-attempt-final-diff.json | 0BA88ED4…3203 | 一致 | 397/397 | 一致 |
|
||
| 11 | CodexOutput/upserver/KVFishingRewardTests/real-login-reconciliation.json | 2B116C7F…7E33 | 一致 | 905/905 | 一致 |
|
||
| 12 | CodexOutput/upserver/KVFishingRewardTests/real-final-idle-state.json | 0636C241…9456 | 一致 | 830/830 | 一致 |
|
||
| 13 | CodexOutput/upserver/KVFishingRewardTests/real-precast-conditions-retry.json | AF2A05F8…B3F1 | 一致 | 1236/1236 | 一致 |
|
||
| 14 | CodexOutput/upserver/KVGameplayLoopTests/runs/42620_639258392922105429/events.jsonl | F6DC9FF1…0B03 | 一致 | 1872/1872 | 一致 |
|
||
| 15 | CodexOutput/upserver/KVGameplayLoopTests/backups/20260924T094234Z/102.json | A9391829…0A23 | 一致 | 14351/14351 | 一致 |
|
||
| 16 | CodexOutput/upserver/KVGameplayLoopTests/backups/20260924T094501Z/102.json | 05CB8DF2…AACE | 一致 | 14351/14351 | 一致 |
|
||
| 17 | CodexOutput/upserver/其他AI开发接手说明-4C2扣费前拦截-2026-09-24.md | 1E632D50…90F5 | 一致 | 7360/7360 | 一致 |
|
||
| 18 | CodexOutput/upserver/其他AI开发接手提示词-4C2-2026-09-24.md | 981CB6C5…B2AC | 一致 | 1189/1189 | 一致 |
|
||
|
||
汇总:18/18 一致(hash与字节数均一致),0项不一致。清单自述的"handoffUpdate:原16项复验一致"与本轮18项全对结果相容。
|
||
|
||
## 二、对方案末尾四个问题的逐项回答
|
||
|
||
### 问题1:现有证据是否只能定位到合并门,尚不能确定具体谓词?
|
||
|
||
结论:同意。证据只能证明命中 FishingBehaviorB.cs:49~51 的合并if,不能区分七个析取条件中的哪一项为真。
|
||
|
||
证据:
|
||
|
||
- 拦截点与消息唯一:`events.jsonl` 第11行(09:44:05.1516474Z)`kind=fault, message="Only one ordinary paid fishing action is permitted."`;该字符串在生产代码中仅出现一处——FishingBehaviorB.cs:51 的 `throw ...Stop("Only one ordinary paid fishing action is permitted.")`(全库grep确认,无其他产生点)。
|
||
- 栈帧与当前源码逐行对齐:`real-after-attempt-errors.json` 第12行栈显示 `ConsumeBaitOnKVAsync d__4:MoveNext (at FishingBehaviorB.cs:51)` ← `Stop (at UserService.KVGameplayLoop.cs:280)` ← `Fault (at UserService.KVGameplayLoop.cs:287)` ← `ConsumeBait (at FishingBehaviorB.cs:801)`。当前源码实算:51行=throw+Stop、KVGameplayLoop.cs:280=Stop定义、:287=Fault内Debug.LogError、FishingBehaviorB.cs:801=ConsumeBait的KV分支调用,全部吻合(FishingBehaviorB.cs hash已对上清单,无事后改动)。
|
||
- 早于扣费/奖励:门在49~51行,`PrepareFishingReward` 在62行、`ConsumeEnergyForKVLoopAsync` 在63行;异常在51行抛出后直达70行catch,62~68行未执行。旁证:events.jsonl中无 `fishing-reward-preflight`(仅由 UserService.KVFishingReward.cs:144 在 PrepareFishingReward 内审计)、无 `fishing-consume-confirmed`(仅由 UserService.KVGameplayLoop.cs:241 在 ConfirmFishingAsync 内审计)、09:40:42之后再无 write-attempt/accepted。
|
||
- 无法回溯谓词输入:该消息为七个条件共用,`real-after-attempt-errors.json` 与 `real-final-idle-state.json` 均只含同一句合并消息(另3条为无关的PlayFab域名解析旧错误);Fault 后 `Ready=false`(KVGameplayLoop.cs:283),`FishingCurrent` 恒false(:190-191),运行已closed(events.jsonl:12),拒绝瞬间的 fishingData 快照无处可取。
|
||
- 45键全同成立:`real-attempt-final-diff.json` 记 `changedAfterAttempt=[]`、`keyCount=45`、`paidConsumption=false`、`fishReward=false`;本审查另用 ConvertFrom-Json 独立逐值比对两份备份,data 下45键全部相同、后备份无新增键,唯一差异是顶层 `at` 时间戳(这解释了两备份字节同为14351而SHA256不同),"逐值相同"表述准确。
|
||
|
||
### 问题2:六个字段是否足够唯一解释七条件,且不需新增状态?
|
||
|
||
结论:足够。七个谓词都是这六个字段值的纯函数,读出六个值即可逐一判定七条件的真值;不需要新增状态。
|
||
|
||
证据(源码定义与对应关系):
|
||
|
||
- `fishItem==null`(条件1)⟺ `fishItem?.ID` 为null标记。fishItem 为公共字段:FishingData.cs:32。
|
||
- `IsGeneralFish()`(条件2取反)= `fishItem.SubType == 1`:FishingData.cs:172-175。即 `fishItem?.SubType==1` 与该谓词完全等价(由于 `||` 短路,此谓词仅在 fishItem 非null时才被求值)。
|
||
- `IsFree`(条件3):公共bool字段,FishingData.cs:111。
|
||
- `IsRetry`(条件4):getter `return RetryCount>0`,FishingData.cs:113-116。
|
||
- `IsDuel`(条件5):公共bool字段,FishingData.cs:112。
|
||
- `IsPackItemFish()`(条件6)= `fishItem.SubType==21 || fishItem.SubType==22`:FishingData.cs:188-191;其中21=BarginPack(:184-187)、22=PiggyBank(:180-183)。即 `fishItem?.SubType∈{21,22}` 与该谓词完全等价。
|
||
- `curFishData==null`(条件7)⟺ `curFishData?.ID` 为null标记。curFishData 为公共字段:FishingData.cs:27。
|
||
|
||
字段类型与getter无副作用(问题3一并回答):`Item.ID`、`Item.SubType` 均为 `public int { get; private set; }` 自动属性(Tables/Item.cs:54、:71);`FishData.ID` 同(Tables/FishData.cs:156);`IsFree/IsDuel` 为简单字段。所以六字段全部是只读当前对象的简单成员读取,无新增状态。
|
||
|
||
说明两点(非缺陷):(a) `fishItem?.ID` 对七条件的判定并非必需(SubType已覆盖条件2/6),但能唯一定位物品行便于后续对照表配置,保留合理;(b) 当 SubType=21/22 时条件2与条件6同时为真、IsFree等亦可与其它条件并存——诊断文字报告的是状态全集,能推出每个谓词的真值,但不标识"哪个析取项最先短路",这是合并门的固有属性,不影响定位。
|
||
|
||
### 问题3:仅改变拒绝消息是否保持时序/副作用边界,是否有null/日志异常风险?
|
||
|
||
结论:保持。存在一个必须在实现中显式处理的null文本陷阱(见P2-1),其余无风险。
|
||
|
||
证据:
|
||
|
||
- 时序:改动只替换51行throw中Stop(...)的字符串字面量为插值字符串。C#求值顺序为"先求实参、再调Stop",插值发生在谓词判定之后、Stop/Fault之前,全程同步、无await、无事件、无随机/选鱼/存档调用;49~50行谓词与throw/Stop结构不动,时序不变。
|
||
- 副作用:全部读取为自动属性/纯字段/纯getter(见问题2);`fishingData` 自身在49行已被解引用,故到51行时必非null(若为null,消息将是NRE文本而非当前消息,已被实际运行排除)。
|
||
- 日志通道:消息经 `Stop`→`Fault`(KVGameplayLoop.cs:280、281-290)单通道写入 events.jsonl(:286 Audit、:294单行JSON追加)与Console(:287 LogError)及Error属性(real-final-idle-state.json可见);70行catch再调Fault时被 `Interlocked.Exchange(ref failed,1)`(:284)去重,不会产生第二个fault事件。插值内容只有int/bool与固定"null"文本,无引号/换行,不会破坏events.jsonl的JSON单行结构;全库无任何代码按该消息文本做逻辑分支或断言匹配(既有提取测试断言只查 Faults/Calls 计数),改文字不破坏既有行为。
|
||
- null安全:`fishItem?.ID`、`fishItem?.SubType`、`curFishData?.ID` 用 `?.` 安全;`IsFree/IsRetry/IsDuel` 在非null的fishingData上直读安全。唯一陷阱:C#插值中 `int?` 为null时输出空串而非"null"(`$"{fishItem?.ID}"` 在fishItem为null时打印空),必须显式处理才能兑现方案"null明确写null"的承诺(实现例:`fishItem==null?"null":fishItem.ID.ToString()` 或 `(object)fishItem?.ID ?? "null"`)。若不处理,条件1/条件7与"空显示"将混淆,直接削弱本方案唯一目的。
|
||
|
||
### 问题4:编译及既有入口验证是否足够,是否批准这一个文件、这一处修改?
|
||
|
||
结论:验收组合足够、批准范围正确,但验收第3项需按P2-2补一步(提取测试shim同步),故整体为有条件批准。
|
||
|
||
证据:
|
||
|
||
- "静态diff只有错误文字变化":对纯文字改动是恰当且接近充分的验收;建议明确diff判据为——49~50行谓词与全文件其余部分字节级不变,仅51行字符串字面量变化(见P3-1)。
|
||
- "Editor和SERVICE_HOST编译":该分支位于 `#if UNITY_EDITOR && !SERVICE_HOST`(FishingBehaviorB.cs:27),Editor编译直接覆盖改动;SERVICE_HOST编译不含该分支,作为全局回归无害,两者都应做。
|
||
- "利用既有消费入口提取测试核对拒绝仍先于扣费":该测试真实存在且恰好覆盖此门——`KVFishingRewardTests/ConsumeControlTests.cs.txt` 第10~18行对 free/retry/duel/pack/special/missing-fish/missing-item 等逐项断言 `player.Calls==0 && Continues==0 && Faults>0`("pre-consumption gate"),即七条件每一项单独触发时扣费调用次数为0;断言不匹配消息文本,改文字不破坏断言。但注意:该测试编译的 `ConsumeControlSlice.cs.txt` 是手工提取副本,其shim `FishingData.fishItem` 声明为 `object` 且无 ID/SubType 成员(ConsumeControlTests.cs.txt:38)——改动后重新提取的slice引用 `fishItem?.ID/SubType` 将无法编译,必须同步给shim补一个带 `int ID; int SubType;` 的Item类型并重跑(属既有测试维护,不违反"不新建框架")。
|
||
- 批准范围:仅 FishingBehaviorB.cs 一处、仅原拒绝分支错误文字、不放宽准入、不新增日志点/框架/第二个Fault——与源码结构相容,可实施。
|
||
|
||
## 三、问题列表
|
||
|
||
P1(阻塞):无。
|
||
|
||
P2(应修,实施时必须处理):
|
||
|
||
1. null文本插值陷阱:`$"{fishItem?.ID}"`/`$"{curFishData?.ID}"` 在对象为null时输出空串而非"null",无法兑现方案"null明确写null"。实现必须显式判null输出字面"null"(如 `fishItem==null?"null":fishItem.ID.ToString()` 或 `(object)fishItem?.ID ?? "null"`),并在静态diff中可核对该处理存在。
|
||
2. 提取测试需同步:`ConsumeControlSlice.cs.txt` 须从改后源码重新提取,且 `ConsumeControlTests.cs.txt:38` 的shim `FishingData.fishItem`(现为 `object`)需补带 `ID/SubType` 的Item类型,否则验收第3项(既有消费入口提取测试)无法编译执行。此项属既有测试维护,不是新框架。
|
||
|
||
P3(建议):
|
||
|
||
1. 验收"静态diff只有错误文字变化"建议写成可执行判据:diff仅涉及 FishingBehaviorB.cs 第51行字符串字面量,第49~50行谓词与文件其余部分字节级不变,无其他文件变更。
|
||
2. 在方案或实施记录中注明诊断语义:六字段报告的是状态全集,可推出七条件各自真值,但不标识"哪个析取项最先短路"(SubType=21/22时条件2与6必然同真),避免后续读者误期待单一原因。
|
||
3. 精确性备注:该门位于第48行 `ClaimFishingConsumption`(内存标记Consuming=true,非扣费、无审计事件)之后;"扣费前拦截"表述成立,但如后续文档复述顺序时可注明这一内存claim先于门发生。
|
||
4. 两份102备份SHA256不同但45键逐值全同(仅顶层 `at` 时间戳不同、字节数相同),建议在证据文档注明,避免后人把hash差异误读为数据变化。
|
||
|
||
## 四、最终结论
|
||
|
||
有条件批准实施。条件(全部满足后方可编码合入):
|
||
|
||
1. 仅修改 `Client/Assets/Scripts/GampPlay/FishingB/FishingBehaviorB.cs` 第51行Stop(...)的字符串实参:保留第49~50行合并if谓词与throw/Stop调用结构字节级不变;错误文字含六字段(fishItem?.ID、fishItem?.SubType、curFishData?.ID、IsFree、IsRetry、IsDuel),null显式输出"null"(按P2-1方式实现,禁止裸 `?.`+插值依赖)。
|
||
2. 不新增任何日志点/框架/文件/await/事件/第二个Fault;成功分支与其余门(offline/activity/bad-mag/context-change)文字不变。
|
||
3. 验收按方案四项执行,其中提取测试按P2-2同步 `ConsumeControlSlice.cs.txt` 与shim并重跑通过(10类前置门断言含 `player.Calls==0`);静态diff按P3-1判据核对;Editor与SERVICE_HOST双编译通过。
|
||
4. 编码完成后由主会话核hash、逐条裁决,再决定后续运行;本诊断不构成根因修复、不构成4C2验收、更不授权新的真实运行。
|
||
|
||
## 五、边界声明
|
||
|
||
- 本审查基于2026-09-24冻结的hash清单(18/18一致)与当刻工作区状态,只做只读核对;未运行Unity、未连接正式服务器或测试账号、未执行任何写操作类命令(Get-FileHash/ConvertFrom-Json/diff/sha256sum/wc均为只读)。
|
||
- 本审查不等于4C2关闭:4C2实施复核、真实收鱼奖励闭环、新进程恢复均未完成;4C2/完整4C/G4维持未关闭。
|
||
- 本审查不构成对任何新的真实运行的授权;新的真实运行仍需按记录要求先核对新进程、备份与实际操作范围,并向用户明确一次尝试。
|