Files
ft/CodexOutput/upserver/阶段4C1普通钓鱼消费确认方案-待评审-2026-09-24.md

143 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 4C1普通钓鱼消费确认方案(待独立评审)
日期:2026-09-24。作者:主会话。前置:R1已关闭且关闭复验通过。本文申请限定设计评审,**尚未批准编码/真实消费**。
## 1. 目标和范围选择
在现有Editor KV专用模式、102专用存档、单账号和重启式重登限制下,完成一次普通收费抛竿的“扣体力→原动作保存确认→允许拉杆→正常退出→新进程恢复”。首轮到拉杆按钮可用即停止操作,不点击拉杆、不结算鱼获/领取任务奖励。它验证消费前置保存,不宣称完整捕鱼事务、4C整体或G4完成。
路线比较:全B/C热换号先行会扩大改动;全局接通SyncDataImmidiate会同时开放未处理的转盘且保留旧体力副本冲突;选择仅普通钓鱼增加可等待确认支路。保留现有Owner、Bridge、Write/Flush/Ticket、整档序列化、扣费及任务算法,不增加通用交易框架或另一套缓存。
本期支持条件:`IsGeneralFish()==true`、`!IsFree`、`!IsRetry`、`!IsDuel`、`!IsPackItemFish()`,对象/配置/会话有效,费用来自原TbEnergyDef.DataList[magnification].Mag且大于0,当前体力足够。免费、重试、礼包鱼、特殊鱼、转盘、广告和发奖均不获启用;不自动选鱼、改配置、补充体力或修改102档来满足条件。条件不符则在扣费前终止该测试run。
## 2. 源码依据和写入矩阵
引用行号以同目录受审清单为准;实施前须重核hash。
| 触发链 | 现有行为、可能写入 | 本期处理 |
|---|---|---|
| FishingUpdateSys.Start→CastLine→FishingBehaviorB.CastLineBehavior→CastBait→BaitMove→WaitForFishBite→ConsumeBait | 进入捕鱼对象后Start会自动抛竿;动画/协程推进,收费调用async void后立即WaitForFishBiteED | 实际UI进入捕鱼即可能开始动作,不通过CLI直接调用消费伪装UI验收;只在KV模式捕获一次动作,收费分支等待确认后继续一次;普通PF分支保持 |
| ConsumeBaitOnPlayfab | 扣前复制PlayerDataDic,仅改energy别名,旧newData仍带扣前体力 | KV支路完全不使用该副本/旧立即接口;复用现有整档序列化 |
| PlayerData.ReduceEnergy→SetEnergy→SavePlayerData | 修改energy、energyRM,发布PlayerData,写NewPlayerDataDic(原调用remote=false) | 原扣费算法执行一次;最终另取现有序列化产生的明确remote票据,不能把localOnly当确认 |
| 从满体力降到阈值下→RecoveryEnergy(0) | 设置_lastEnergyRecoveryTime,再SavePlayerData,启动CoRecoveryEnergy | 不重写恢复算法;同步写收进本动作;后续定时写不是本动作票据 |
| CoRecoveryEnergy | 每秒等待、到期回血,可能多次NewPlayerDataDic写 | 保持运行;若取代待确认的本动作最终票据则停止run,不暂停计时伪造稳定结果 |
| ConsumeEnergy→AchievementDataManager.UpdateData | 更新成就内存,独立保存调用已注释 | 必须在事件发布之后作最后一次整档序列化,包含新的ad |
| ConsumeEnergy→BattlePassData.UpdateTaskData | 活动有效且任务变化时SaveBattleTask→BattlePassRecordTask | 同步原写入与原票据纳入确认;不领奖 |
| ConsumeEnergy→MiniBattlePassModel.UpdateTaskData | 更新Exp→SaveBattlePassRecord→provider→MiniBPKeyNew | 同上;ConditionTypeEvent是struct,各订阅收到副本,不改倍率算法 |
| ConsumeEnergy→LuckyTaskData.UpdateTaskData | 有任务变化时SaveTaskData;两个实现分别写EventLuckGameRecord、EventLuckMagicPlayfabData | 同步原写入纳入;红点/任务计算保持 |
| ConsumeEnergy→TriggerPackData.UpdateTriggerPackData | 配置匹配时ConditionTypeAdd/AddTriggerPack,可写FixedTimeShopDic、TriggerPackDataList并排入礼包UI | 原同步写受许可与捕获约束;不购买/领取礼包 |
| ConsumeEnergy→FishingEventData.OnEventDataChange | 配置匹配时SetEvent;可初始化活动、写FixedTimeEventDic/模块数据,甚至启动其他加载 | 本期真实run要求当前配置没有以ConsumeEnergy触发该SetEvent链的记录,否则扣费前拒绝;不试图盘点/开放所有活动激活 |
| PlayerData通知→PlayDataEventSubscriber→ActivityManager | 重新检查活动,可Open/Close;已查等级/钓鱼次数条件,本动作不变更二者,但时间条件可能变化 | 真运行前记录活动状态与相关条件;同步副作用受捕获约束,观察到活动切换/异步初始化则该run不计通过,先关闭再另立差异审查 |
| HomeBtnRecharge的ConditionTypeEvent订阅 | 仅RechargeAmount过滤 | 不接收ConsumeEnergy |
以上是直接订阅和明确转发链,不冒称枚举了反射或动态注册的全部游戏事件。真实运行的日志、写入key和状态矩阵是验收条件;发现新增调用链必须停止本期扩展,不能临时扩大白名单。
## 3. 运行许可和动作生命周期
1. 保留Version=1许可的equipment语义;增加Version=2且Operation必须为固定枚举`equipment`或`fishing-consume-once`,未知版本/操作拒绝。Version=1中出现试图授权消费的字段必须拒绝,不能靠未识别字段启用。Version=2设备/账号/API/备份摘要/AllowedKeys检查全部沿用,所有允许键仍必须出现在现有备份中。
2. Editor新增固定PrepareFishingPermit入口,沿用PreparePermit的校验过程,操作固定为fishing-consume-once;不接受URL、任意代码或任意方法名。每个Unity进程仍只领取一次运行许可,不删除claimed、不允许域重载绕过。
3. KV模式进入CastLineBehavior之前检查操作许可;旧equipment许可不能尝试抛竿。消费许可在本进程只能占用一个动作(成功/失败/不支持都不能自动恢复配额)。在同一组件的CastLine首次入口捕获原handle、组件引用、递增的本地动作版本;重复入口直接返回,不重复动画/扣费,记录duplicate-suppressed。
4. 鱼种可能在抛竿流程中确定,因此在真正ConsumeBait之前再次检验本节及§1所有条件,固定鱼/地图/鱼竿/倍率/费用;CastLineBehavior、CastBait的每个await后及Tween完成回调、BaitMove/WaitForFishBite继续处检查原对象存活、active/enabled、动作版本一致、原handle仍可用。不得仅在最终网络await后检查,放任旧动画回调重新推进。普通PF模式不执行新限制。
5. OnDisable/OnDestroy只撤销本对象动作继续资格,不撤销已准入网络写;若消费已开始或等待中,走现有Fault/Close。退出/401已经撤销会话时不再重复退出、不清标记。生命周期回调不能重置进程配额。
6. ConsumeBait只准入一次;WaitForFishBite、断网弹窗重复回调、第二次动画事件不能再次扣费。KV支路不调用旧NoInternet→DoConsumeBait的自动重试;网络不可用时在修改内存前终止run,不能仅凭internetReachability宣称网络提交结果。
## 4. 消费方法和保存顺序
在PlayerData内增加仅`UNITY_EDITOR && !SERVICE_HOST`可用的窄方法,由FishingBehaviorB的可等待KV支路调用。费用计算在原位置复用,参数必须是本次已验证的正费用和固定动作;不接受外部传来的序列化字符串。
同步段顺序固定,段内不await:
1. 复查原handle、动作和体力;开始现有WriteBatch捕获,禁止与装备批次/另一消费嵌套。
2. 调用**一次**现有ReduceEnergy(cost,false)。保存现有energyRM、阈值和恢复时间逻辑,不直接改私有energy来绕过副作用。
3. 发布**一次**原ConditionTypeEvent(ConsumeEnergy,cost)。本期只接受普通鱼,因此不省略任务事件。
4. 直接调用已有私有SerializeAndSavePlayerData(true),取得NewPlayerDataDic最后一次原票据;不再次Publish PlayerData、不复制19槽序列化、不写energy别名。最终整档包含扣费、累计消耗、恢复时间和刚更新的成就。
5. 关闭同步捕获,固定本次记录集合。异常立即Fault,不自动回滚内存,不再补偿保存、不补发事件。
等待段:对原handle发起一次Flush观察,随后验证§5的原票据。最多30秒观察(取消仅结束等待,不取消底层已准入写);超时按不确定结果停止run,原Owner/Registry继续排水。每个await后检查动作有效性,成功后仅调用一次WaitForFishBiteED,再记录`fishing-consume-ready`,记录费用、前后体力、原动作身份和最终原票据,不记录凭据或整档正文。
`PlayFabMgr.SyncDataImmidiate`保持原KV拒绝;本期不把它改成通用消费适配器。转盘不会因为本期修订得到可用接口。
## 5. 多次写同一key的明确合同
需要完整评审的新增规则:R1装备仍逐张票据要求Confirmed,不修改原ConfirmEquipAsync含义。消费由于同一同步调用本身会重复序列化状态,采用**本动作每个key的最终原写入**确认,不能要求被本动作最终状态正常替代的中间票据也Confirmed。
最小实现:现有WriteBatch保留Edits列表原行为,另记录每笔key、remote标志、原EditResult、值摘要和顺序;Write在原Accepted之后同时记录。不是延迟写缓冲,不拦截或重排底层Update,不创建新Owner或网络队列。
- 对每个key确定同步段最后一次Accepted记录;NewPlayerDataDic最后一笔必须等于§4返回的primary且有原remote票据。
- 每个key最终记录若为localOnly,本run拒绝成功(主档已由最后序列化转为remote);不偷偷提升其他模块的localOnly语义或补写。不能用早先remote值代替后来localOnly值。
- 每个key的最终原票据必须Confirmed。较早remote票据只允许Confirmed,或能由**同一批次、同一Owner/key、更后remote票据**解释的Superseded;最后票据必须Confirmed。Unknown/Blocked/StorageError不因后面有票据而被掩盖。
- 异步晚到的新写不追加进已封存集合;它若使本动作最终票据Superseded,则停止run,不寻找替代票据。不同key的晚写也不声称属于已确认动作;若来自本次消费引发的异步活动链,该真实run不通过。
- Flush计数/无异常不是业务成功凭据;TransportError、Unknown、Blocked保持原信息。部分key成功、部分失败视为run失败,不宣称原子提交或做整档回滚。
- 对底层本来就已Confirmed的票据,不要求服务器永远保持旧值;最终真实对账若出现其他业务改值,要另行归因并判断是否仍满足本期闭环,不能只用票据掩盖后续覆盖。
## 6. 失败与体验边界
| 情形 | 本期行为 |
|---|---|
| 无许可/错误操作/不支持鱼种/体力不足/配置缺失 | 首次消费内存修改前拒绝;无扣费、无ConsumeEnergy、无拉杆继续 |
| 原动作重复/生命周期失效 | 不再次消费;等待中的结果不能继续已销毁对象;已发生的保存保留原结果 |
| Accepted且Pending | 不开放拉杆,不报成功;没有重试按钮触发二次扣费 |
| Confirmed且动作有效 | 仅开放一次拉杆;不自动收鱼、不发奖 |
| 明确拒绝/StorageError/Unknown/401/超时/最终票据Superseded | 现有Fault撤销业务准入并正常关闭;保留原票据/错误,不重扣、不退款补写、不重登重放 |
| 已确认扣费但本地对象失效,或多键部分提交 | 可能已扣体力但未继续玩法;如实记录,专用账号保留结果供对账。普通用户补偿/恢复方案不在本期启用范围 |
这是专用测试模式的停止策略,不是消费事务的完整恢复承诺。因而本期通过不能开放普通用户、连续多次钓鱼、鱼获奖励或最终包。不得把Unknown改成false来调用旧重连逻辑。
## 7. 精确实施白名单
以下在独立审查通过、主会话裁决后才生效:
| 文件(Client/Assets/下) | 允许变化 |
|---|---|
| Scripts/GampPlay/FishingB/FishingBehaviorB.cs | CastLineBehavior/ConsumeBait KV分支、窄async Task消费方法、原动作字段、OnDisable/OnDestroy撤销;CastBait/BaitMove/WaitForFishBite仅增加KV动作继续/重复检查。旧PF消费/动画/奖励代码不重写 |
| Scripts/DataCenter/DataCenter.PlayerData.cs | 一新增Editor消费方法复用ReduceEnergy、事件、SerializeAndSavePlayerData;不改原扣费、恢复、序列化或奖励公式 |
| Scripts/Services/UserService.KVGameplayLoop.cs | 许可v2验证、一次动作占用/确认、WriteBatch附加记录、Write采样接线、固定验证入口。R1装备确认语义保持 |
| Editor/KVGameplayLoopSetup.cs | 固定PrepareFishingPermit及`fishing-consume`验证case,沿用限定反射入口;无任意执行扩展 |
| Scripts/Services/UserService.KVFishingValidation.cs及meta(新增) | 固定Editor合成验证host;如采用partial,原class只增加partial修饰。固定.invalid后端,不转发真实网络,不含运行许可 |
| CodexOutput/upserver/KVFishingConsumeTests/(新增) | 离线宿主/用例/证据与CLI脚本,避免生成.cs混入其他csproj glob;不新增包依赖 |
只读依据但不允许修改:PlayFabMgr、Schema、Context/Owner/Coordinator/Lease/Registry/Guard、FishingData、各任务和活动订阅者、转盘、场景/prefab、SDK及服务器。若通过测试必须动这些文件,停在具体差异方案评审,不借“修复”扩展白名单。
## 8. 验收矩阵与检查点
| 编号 | 必验内容 |
|---|---|
| C1-01 | 旧代码先证明“不等待便继续”和“旧newData/新energy冲突”;保留红测。不得只测试新代码的镜像 |
| C1-02 | 真实PlayerData与原ReduceEnergy:恰好一次扣费/累计消耗/事件,最终19槽整档Schema有效,其他槽保持 |
| C1-03 | 满体力跨阈值会产生恢复时间写;低于阈值、恢复定时在等待期再次写两个分支;不暂停原计时 |
| C1-04 | 主档重复写、任务多key、localOnly最终写、中间票据Superseded与最终票据Superseded区分;原票据身份不可替换 |
| C1-05 | 注入延迟:未确认时WaitForFishBiteED调用数0;确认后1;重复Cast/Consume/动画/断网回调不会二次扣费 |
| C1-06 | 写后丢响应、401、取消/超时、StorageError(含可能本地提交)、部分确认、退出/Disable/Destroy;无重扣、无发奖、无跨对象继续 |
| C1-07 | 许可v1装备不能消费,v2合法消费仅一次,未知版本/操作拒绝;免费/重试/特殊鱼/转盘无新增放行 |
| C1-08 | 配置及任务真实绑定验证:上述直接订阅的同步保存进入本动作;发现活动异步激活或新增key不得记通过 |
| C1-09 | 4B2全量、B3A三条件全量、R1固定装备链/许可/退出,Unity真实M01/B2-20;默认PF与SERVICE_HOST编译行为保持 |
| C1-10 | 102一次真实UI抛竿到可拉杆,记录原最终票据Confirmed与服务器逐键对账;不点击拉杆/领取,正常退出;新进程新缓存恢复体力/累计消耗/关联任务 |
检查点0:审查报告/hash/逐项裁决和源码快照,更新日志。检查点1:红测、最小实现、合成失败矩阵。检查点2:Unity固定真实对象与既有回归,确认没有新增活动分支。检查点3:§9真实102准备/运行/恢复与证据;阶段内每个检查点更新入口、TODO、开发日志。检查点4:限定代码自审/受审hash/独立实施复核,主会话裁决后关闭4C1。
测试不得实例化假的通用公开网络工厂:固定验证host使用现有合成Bridge,与真实PlayerData/实际消费入口相连;捕获的继续次数来自实际控制流或编译真实方法的宿主,不靠复制一份理想伪实现。新增host应恢复静态状态且排水完成才能结束,沿用R1固定测试约束。
## 9. 真实数据和操作边界
已有101/102授权沿用,无需再询问账号。只用102现有原档,不重放import-102.claimed、不初始化101。正式地址固定http://s101.local:18080/,Unity CLI Loop,不用UnitySkills,暂不打包。
实施验证后先只读备份102及101,记录实际服务时间、config摘要、体力、MaxEnergy、倍率/费用、恢复到期时间、目标鱼种及当前活动。历史403体力仅为旧证据,不能替代当时真实值;不写死本次费用。
允许键仍限备份中的原45键;消费实际预计键集合为NewPlayerDataDic、BattlePassRecordTask、MiniBPKeyNew、EventLuckGameRecord、EventLuckMagicPlayfabData、FixedTimeShopDic、TriggerPackDataList及正常初始化原已批准键。实施运行前输出“预计写键→当前存在→关联订阅→允许变化字段”矩阵;主档精确核对Energy/energyRM/恢复时间/ad及其他槽,任务键按原事件费用计算。任何新key、新激活链或无法解释的变化拒绝验收,不能添加空键绕过许可。
收费前只读确认无ConsumeEnergy触发旧FishingEventData.SetEvent的配置;若存在就停止,提交具体活动分支的最小差异方案。活动的等级/次数不因本动作变化;如果观察到时间边界导致Open/Close或异步初始化,本run也不记通过,不冻结服务器时间或重写业务回调过关。
实际UI普通抛竿一次,确认后观察拉杆可用即停止输入,用独立CLI请求发起正常QuitStage,另取日志/状态(不依赖跨Stop的await返回)。未确认时不能靠手动Stop伪造成功。Registry未Ready则保留进程/排水任务。已关闭后归档permit;下一轮启动新的Unity进程、验证新缓存不存在,再只读恢复比较,不执行第二次消费。期间自然体力恢复允许按原时间公式解释;未解释的差异不放行。
默认保留测试结果;失败时不回滚整档、重扣或自动补偿。若需人工恢复,另列最新服务器态和精确恢复范围,不能拿旧45键备份直接覆盖运行后状态。
## 10. 作者自审及交接
- 目标、一次动作、成功终点与不支持分支已明确;不把“可拉杆”表述成完整钓鱼奖励闭环。
- 避免旧体力双表示;同键替代和不同键部分提交已区分,不作原子性承诺。
- 直接事件链有明确矩阵;任意活动激活明确未覆盖,设真实run拒绝条件,不冒称全游戏调用图完整。
- 白名单不包含底层/转盘/活动算法;R1语义有回归约束。没有为了测试顺利允许删claimed或伪造存档。
- §5消费最终状态确认规则、§6失败可能扣费但不继续、§9活动拒绝边界需评审者特别裁决。若评审认为此范围不足以称4C1,应退回分期名称/验收范围,不在编码时临时扩大。
评审提示词见《其他AI审查提示词-4C1普通钓鱼消费确认-2026-09-24.md》。本方案可交审,**没有代码已实现、测试已通过或真实消费已运行的声明**。