Files
ft/CodexOutput/upserver/阶段4B2检查点2配置隔离与鱼数据派生差异补审方案-2026-09-23.md

88 lines
12 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.
# 阶段4B2检查点2差异补审:D4配置隔离、D5鱼数据派生
日期:2026-09-23。状态:**待独立差异审查,尚未批准D4/D5编码**。D1/D2/D3及修订2已有裁决保持,不重审已通过内容。主会话继续实施至28/31模块完成局部验证,剩余M03、M18、M31及聚合事务/服务编排尚未完成。本文不是阶段验收,也不是正式启用批准。
## 1. 为什么需要本次差异审查
用户要求“方案完整评审后编码”,修订2又要求:真实模块在临时容器中验证后恢复共享环境;无法隔离时B2-20记阻塞;不得以反射复制真实模块字段替代适配器。
| 差异 | 当前合同与已核实源码 | 需要裁决的内容 |
|---|---|---|
| D4 / M18 | 正式版2要求PartnerInfoManager替换为空新实例;该类的静态`_tables`初始化从GContext取表,静态readonly `BuilderCapacity`随之计算。第一次构造临时实例可能永久留下临时表图。若该类型早已初始化,又会继续使用旧表,不服从当前固定快照。 | 是否允许扩展一个文件的白名单,去掉静态表缓存,并将公开容量字段改成只读属性。此项有明确公开成员形态变化,不伪装成单纯新增internal方法。 |
| D5 / M03 | 正式版2列明严格鱼记录解码、Reset全部集合、Replace、禁止调用旧地图派生链。源码`SetFishAllData`除赋值还过滤鱼配置/地图归属、修正MaxG、限制Level、补TotalCard,并同步mapFishData/fishData/fishCardData。合同未明确这些变换在纯入口的取舍及三套索引的一致性。 | 冻结纯配置快照后保留哪些变换、哪些旧静默忽略改为显式拒绝,以及三个集合的物化规则。不得默认为“只拷贝地图原值即等价”。 |
源码事实均为只读检查,尚未修改M03、M18或PartnerInfoManager,也没有为了探测而在Unity中触发PartnerInfoManager的类型初始化。不能把潜在静态污染说成已实测恢复失败。
## 2. D4推荐方案:配置由实例持有,容量不再静态缓存
新增唯一生产文件白名单:`Client/Assets/Scripts/EventPartnerGather/PartnerInfoManager.cs`。仅以下修改获审后可实施:
1. 私有`_tables`从静态缓存改为实例只读引用,在现有公开构造路径的字段初始化阶段解析当前容器表;不新增网络、订阅或PlayerPrefs调用。每个实例持有其创建时的表,随该实例释放,不留静态表根。
2. `public static readonly int BuilderCapacity`改为`public static int BuilderCapacity { get; }`形式的计算属性(表达式getter),从当前容器只读表取得`InvitationCount[1]`,不写缓存。**源代码读取表达式保持;字段→属性并非二进制兼容。** 全量Unity编译、所有引用盘点,以及预编译程序集依赖核查是强制验收项,不能仅凭编辑器能跑忽略公开面变化。
3. 实例的`BuildPartners`仍为公开readonly数组,构造长度取同一实例表的`InvitationCount[1]`,与静态属性分开读取;本类`AddBuildPartner`的容量判断和诊断改读`BuildPartners.Length`,避免实例受后来容器切换影响。其它现有调用方继续读取同名静态属性,本轮不改调用方正文。
4. `_friendService`、`_userService`仍为实例依赖;受控宿主只注册受控依赖,不调用`Init`、缓存读写、推荐、匹配或网络。若构造链触发额外服务构造,必须在实例化前拒绝或补齐已审替身,不容许借用真实账号服务。
5. M18 Reset在未启动的受控对象上新建两个伙伴管理对象,GatherData=null、派生引用及标志清空;不得对活跃玩法对象执行此操作。无需改`EventPartnerGatherRobotInvitationSystem.cs`:其默认构造无初始化动作,不调用其Init/CheckAndFire。
源代码目前的有效外部引用:EventPartnerGatherManager三处、EventPartnerGatherRobotInvitationSystem两处,均为数值读取;Mining面板仅注释。没有在源码中发现ref/out或反射取字段调用。这个盘点不自动排除预编译依赖,实施前仍须检查。
不推荐的替代方案:
- 反射改私有静态表并写readonly容量:违反既定反射边界,跨运行时不可依赖。
- 不执行M18真实对象,只用假模块宣布通过:违反B2-20。
- 保留静态表,测试完只恢复GContext:不能证明原全局环境恢复;冷启动和已初始化两种情况都有问题。
- 在正式Root里预热该类型:触碰真实配置/服务环境,且不能解决已缓存旧表与本epoch快照不一致。
若审核者认为字段→属性不可接受,应明确退回D4并给出可验证且不扩大为全局反射的替代,不将当前代码默认为可实施。
## 3. D5推荐方案:先比较原值,再做可证明的纯派生
白名单不扩展:仅既有PlayerKVSaveSchema/PlayerKVDecodedSave、DataCenter.PlayerFishData、PlayerFishDataRod及新验证宿主/测试。不得修改FishData/FishCard生成表、原PF方法或真实存档。
### 3.1 原始解码与配置快照
- 沿用正式版2的6个整数字典、RodGSAData完整嵌套集合、Luck三表示和Map动态键严格语法。Map后缀必须规范正十进制且存在于固定地图表。
- 每个Map旧对象/5槽/9槽先独立严格解码,**先按当前五个消费字段比较重叠记录**;原值冲突直接ConflictingRepresentations,不能先截断、修正后把冲突掩盖。优先新格式整张地图替换。9槽的2/3/4/8兼容槽仍保留在只读原始记录中、不参与当前五字段派生。
- 在epoch建立时复制必要表字段及数组:鱼ID、AreaID、Quality、BaseWeight、PointExtraParam、WeightRandom、FishCardID;鱼卡ID、MaxLevel、CardsRequiredList、WeightMagList。只读纯结构不可持有cfg.Tables或可变配置列表。所有派生只使用这份快照,无Resolve、时钟、Unity对象调用。
- 选中记录不存在鱼配置、AreaID与Map键不符,建议返回UnsupportedSchema;不复刻旧方法的静默丢弃。所需卡配置或下标缺失也返回UnsupportedSchema,不能在Apply中才发生异常。此项严格化必须被明确批准,并加入P2-α真实档兼容盘点。
### 3.2 具体数值变换(按旧步骤顺序)
1. 五个保存字段复制为工作值,Level/MaxG/TotalCard/TotalNum/NewFish原值保留在只读原始层。
2. Quality≤5:计算单精度`base = BaseWeight * PointExtraParam * (1 + WeightRandom)`。原Level>0时,先要求对应鱼卡和`WeightMagList[原Level-1]`存在,再乘`1 + weightMag`;乘法各步及乘100结果均须有限。不先把Level截小来绕过原先缺失的权重下标。
3. 基准重量按旧Mathf.Round的最近整数/中点到偶数规则对`base*100`取整,再以单精度除100;仅当原MaxG大于该结果时取较小值。纯实现与Unity Mathf.Round在0、正负中点、相邻float、普通小数及大值上做对照;不以双精度重排乘法。非有限配置/中间结果返回UnsupportedSchema。
4. 原Level>0:要求鱼卡MaxLevel≥1;应用`Level=min(原Level,MaxLevel)`,再要求`CardsRequiredList[Level-1]`存在,`TotalCard=max(原TotalCard,该阈值)`。Quality>5跳过重量修正,但仍执行本步骤。Level≤0沿用原逻辑,不凭本次补审额外定义负数禁令。TotalNum/NewFish直接保留。
5. 负的有限重量、负卡数等未触发上述既有补足规则的值,仍按声明类型保留,不加未经审查的经济规则。所有保存数值的token/位宽/有限性校验保持。
这些步骤是建议保留的内存派生,不创建持久化意图,不写回修正值,不表示真实旧档可直接启用。
### 3.3 集合物化、冲突及幸运值
- 解码输出同时持有只读原始地图、规范化地图、鱼索引和卡片数量索引。规范化地图与索引在纯阶段完成,不留表引用到Apply。不同已选地图中同一鱼ID无法同时满足归属校验;同一卡ID得到不同TotalCard时返回UnsupportedSchema,不依赖字典遍历顺序决定数量。
- Replace新建每个Map内字典及FishWeightData。`fishData`指向本模块本次Map中的同一FishWeightData实例,保持旧模块内部的索引一致性;不跨账号、快照或模块共享。`fishCardData`从已校验卡索引复制。Reset把三类索引及全部其余保存集合清为新空对象。
- RodGSAData六字段:GloveID、RodToSkin、GloveIds、SkinIds、GloveNewIds、SkinNewIds(一个整数、一字典、四列表)。各集合深复制;缺配件null,不调用默认建档方法。
- Luck表示全部严格解析且共有字段一致,优先`ld > LDD > LuckDataDic`整字典替换;BotCount存在时必须是有限float且可无损落入Int32。建议Replace只替换LuckDataDic,InitFishLuck/InitBotLuck/BotCount仍保持Reset的0:它们原本由后续`PlayerFishData.Init→InitLuck`建立,本4B2只处理Load链,不运行玩法Init。此处请审核者明确裁决,避免自行套用表默认幸运值。
- 其它Rod/Map字典仅按原值复制;地图/杆派生开关false、选择0,不调用InitRod、红点、GEvent或解锁提示。
## 4. 必须补的验证
| 编号 | 必须证明的结果 |
|---|---|
| D4-01 | 无活跃Root且编辑器冷类型状态时,临时容器A构造M18;恢复后无静态临时表/账号依赖残留。测试不反射改字段、不触碰真实Root。 |
| D4-02 | 先后两个隔离容器A/B,使用不同合法容量/配置替身,旧A实例继续用A容量,新B实例和静态属性用B;各次恢复原container/aggregator。本项提请允许仅在内存复制当前配置JSON并改变InvitationCount测试容量,构造后按epoch冻结;不得改配置文件或共享Root,最终B2-20仍使用未改值的当前表快照。 |
| D4-03 | M18同对象A→缺键B→A;PartnerInfoManager/RobotInvitationSystem引用每次换新,BuildPartners/Cache空,旧引用不参与新epoch;不Init、不读写Prefs/SDK/网络。 |
| D4-04 | 全项目引用及预编译依赖核查;固定PF配置时原数值行为一致;公开字段→属性影响写进最小diff和回归说明。 |
| D5-01 | 五/九槽/旧JSON全部消费字段,9槽兼容槽保留,原值冲突不能被归一化消除;未知map/非规范后缀拒绝。 |
| D5-02 | Quality两侧、Level≤0/>0、等级封顶、卡数下限、最大重量上下界、float舍入边界;所需配置缺失和下标越界全部在Decode失败。 |
| D5-03 | 未知鱼/错地图归属/同卡冲突拒绝;互换JSON属性/地图表示遍历顺序不能改变判定及输出。 |
| D5-04 | 三套实际索引一致;A→缺键B后空,A→B不同地图/鱼不会残留;模块内部允许对应索引同实例,跨输入/账号不共享。 |
| D5-05 | RodGSA各集合深复制;ld/LDD/顶层多表示、坏新不回退、非整数/越界BotCount、缺键空态和不执行InitLuck。 |
| D45-06 | 全部通过后才能继续31模块聚合Decode/Reset/Replace、检查点3/4。任一Reset失败仍Poisoned;完整B2-20仍须真实31模块和服务guard/零副作用证据。 |
## 5. 当前交付与审查边界
28个模块已做严格解码及真实对象局部验证,最终15个固定用例连续执行成功,共737条断言(含重复覆盖D3的联合用例,不能当作737个独立验收项);涉及临时容器的4个用例全部restored=true,其余不替换共享容器。fullB220全部false。离线55组/1058断言、原4B1全23组/279断言通过。Unity最终0错误/271警告,M05原PF方法的CS4014在此前日志已存在;新增KV代码无编译警告。
**该审查只裁决D4/D5方案,不是对28模块实现的独立实施复核。** 新方案通过后,主会话先核对本包hash、逐条裁决,再将批准内容合入新的正式合同,保留正式版2及原受审包不变;然后实施M03/M18并继续M31/聚合事务及检查点3/4。审核者只写独立报告,不动共享入口/TODO/日志/实现。
4B2未关闭、4B3未启动、G4未通过;P2-α真实档兼容/迁移、独立审查与用户产品确认仍是正式启用硬门。正式地址固定http://s101.local:18080/;仅Unity CLI Loop,不使用UnitySkills,不进入PlayMode,暂不打包、不访问正式服。