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

12 KiB
Raw Blame History

阶段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,暂不打包、不访问正式服。