Files
ft/CodexOutput/upserver/阶段4B2字段与模块加载合同-正式版3-2026-09-23.md

36 KiB
Raw Blame History

阶段4B2字段与模块加载合同(正式版3:合并D1–D5)

日期:2026-09-23。D4/D5依据配置与派生裁决批准。正式版2与原受审资料保留。状态:已审设计合同,供检查点2继续实施;不代表已实现或验收。正文合并修订2附件与D1/D2/D3差异修订,依据差异裁决生效。D3依据值类型空态裁决生效。后续实施首读本正式版2;原受审附件及其hash保留供历史核对,不再作为D1/D2的当前规则。其余合同沿用修订2方案及原实施批准。本清单限定于当前PlayerData.LoadPlayerData/LoadPlayerDataNew同步消费链和登录标记,不等于全项目玩法存档盘点。其余按需玩法读取由4B3/4C另审,受控入口不启动那些流程。

1. 统一规则

  • 所有适配器为本次epoch产生独立的加载状态;不与原快照、上次账号、静态默认字典或其他模块共享可变集合。
  • D<K,V>为JSON对象解码到相应字典,L<T>为JSON数组,T为现有保存DTO的序列化字段形状。必须先检查token类型、重复属性、null、数值溢出与有限浮点数;禁止靠Json.NET字符串/浮点到整数的宽松转换判合法。键转为整数/枚举后重复也拒绝。
  • 原保存DTO有构造副作用时用新文件中的同形状纯数据结构解码,不能在Decode里Resolve服务;应用时仅传已解码字段。不得反射复制真实模块全部字段,配置/服务/订阅不属于存档。
  • 已存在的可选键坏值也使整个加载Malformed。缺失才生成下面列明的空态。JSON文字null不是缺失;集合可以为空但不能为null,活动对象缺失由Missing状态表达,不向旧loader传"null"。
  • 未识别顶层键保留原快照、计数为Unconsumed,不送给总入口或参与主档判定。注释掉的MonthCardData、ChallengeData、TargetEventPackData、PokerScoreData、EventPartnerData、EventBreak/EventCan不算当前消费键。
  • 配置引用只验证该加载步骤会解引用的表项;0作为尚未激活/未选择的明确哨兵保留,不统一要求所有活动ID都为正。活动已失效但载荷可解析时保持Dormant,不启动活动、不自动清档/写回。
  • 数字紧凑串按InvariantCulture且无千位分隔解析;时间兼容按O(RoundtripKind)优先,之后仅允许本次捕获的CurrentCulture与InvariantCulture的TryParse;若两者成功却得到不同日期,返回UnsupportedSchema,不猜测。newData中日期含分隔逗号导致槽数不是19同样拒绝。既有文化存档的不兼容必须明确报错,未来迁移另审,不改PF解析规则。

2. 主档格式:完整核心字段,不再使用“任一特征键命中”

支持两种已知布局:

  1. 现代格式:顶层NewPlayerDataDic为D<string,string>,其newData恰好19个逗号槽;每槽必须可解析。只有NewPlayerDataDic而没有完整newData,不推断为新档。
  2. 完整旧格式:下表的19个顶层键全部存在且合法。不支持用部分旧键加部分新槽拼出主档。NewPlayerDataDic内的旧散键只作一致性校验,不单独授权完整档;缺核心时返回MissingCore,要求显式迁移,不造默认资产。

两格式同时完整存在时逐字段比较规范值,任意不同返回ConflictingRepresentations。现代格式存在但损坏,不回落旧格式。现存非完整旧格式只要包含与现代格式重叠的字段,也逐字段检查一致;不能让残余冲突值被静默覆盖。

槽 顶层旧键 NewPlayerDataDic旧散键 类型/主档规则
0 Lv lv Int32,>=1
1 Gold gold UInt64
2 vipAllScore vipS Int32,>=0
3 Energy energy Int32,>=0,坏/负值拒绝而非改0
4 Diamond pearl Int32,>=0;源码PlayerData.Pearl常量实际是Diamond
5 Magnification mag Int32,>=0
6 EquipRodID rodID Int32,必须在固定TbRodData存在,不以默认杆掩盖坏档
7 LastEnergyRecoveryTime EnergyTime 时间,按§1解析规则
8 RewardNewMap rewardNewMap Int32原始位集;保持有符号位表示,不按普通计数限制
9 EnergyRM energyRM Int32,>=0
10 GoldRM goldRM UInt64
11 DiamondTrue diamond Int32,>=0;不能错认成Diamond
12 CurrentMapId cMapID Int32;0为原入口哨兵,非0须存在于TbMapData
13 LastMapId lMapID 同上
14 FishingFrequency FF Int32,>=0
15 AllFishingFrequency AFF Int32,>=0
16 HeistGameCount HGC Int32,>=0
17 BombGameCount BGC Int32,>=0
18 FishHuntGameCount FHGC Int32,>=0

这是一项比原PF加载更严格的KV准入提案,不声称所有历史PF档都兼容;不使用正式账号猜测规则。本阶段只用合成完整档/残档验证。凡不符合支持布局的真实档将来必须走单独迁移,不自动建档。

3. 全部当前消费键登记及优先级

表中的模块编号也用于产物诊断。主档19键及NewPlayerDataDic由M01/M02消费;其余顶层键如下,均可缺失但不可损坏。

模块 顶层键/模式 格式合同
M01 玩家附加 NewRodList、SeasonId、IsOpenAppraise、InfiniteBuildingData L、Int32、存在标记(任意非null字符串,仅存在性转true)、原始字符串(加载期不解释建筑载荷)
M03 鱼/杆/地图 RodData、RodLevelData、FishSwimToMap、FishSwimToMapCount、MapProgress、HonorLevelData D<Int32,Int32>;整型负哨兵按原类型保留,不误把所有键看成表ID
M03 鱼/杆/地图 InitRodAccessoriesData、Map_5_{mapId}、Map_{mapId}、LuckDataDic RodGSAData形状;D<Int32,string>每项鱼紧凑串;旧D<Int32,FishWeightData>;D<string,float>
M04 商店 ShopPackBuyCount、ShopAllPackBuyCount、ShopGiftBuyCount D<string,Int32>、D<Int32,Int32>、D<Int32,Int32>
M04 商店 Voucher、ChainPackDataNew、VIPAdPackData、TurntableAdData、BuyData、AdvertAllCount Int32、DailySignEvent、AdvertData、DailySignEvent、L、D<Int32,Int32>
M04 商店 DiamondShopRefreshCount、DiamondShopRefreshTime、DiamondShopItemRecords、BlackFridayCouponDate Int32、时间、D<Int32,ItemShopRecord>、优惠券紧凑格式
M05 活动基础 LimitedTimeEventDic、FixedTimeEventDic、ETDropAfterDropDic、RankTargetCount、WheelLevelCount D<Int32,LimitedTimeEvent>、D<Int32,FixedTimeEvent>、后三者D<Int32,Int32>
M05 活动基础 TurntableRoll、WheelOperateCount、AllWheelOperateCount、WheelLevelFreeCount、PVPToken Int32
M06 增益 BuffDataList、TargetBuffDataList、FishDailyBuff、GlobalBuffDataList D<Int32,FishBuffTimeData>、L、FishBuffTimeData、D<Int32,FishBuffTimeData>
M07 营地 IsFix、IsOpenSkyscraper、CampDataDic bool、精确0/1字符串、D<string,Int32>;不能调用SetIsFix默认isSave=true
M08 成就 AchievementData D<ConditionType,AchievementData>
M09 钓鱼箱 FishingBoxData 原FishingBoxData的序列化形状,实际加载FishingBoxOpenNum;FishingNewBoxs按§5重置
M10 引导 GuideSaveData、GuideSaveDataNew D<string,GuideSaveData>;新格式`group
M11 战令 BattlePassRecordTask、BattlePassRecord、BattleTask 新组合紧凑格式;旧BattlePassRecord/旧BattleTask JSON形状
M12 签到 MonthlySignEvent MonthlySignEvent形状;dailySignEvent不允许null
M13 触发礼包 TriggerPackDataList、GiftVIPDData、FixedTimeShopDic L、D<string,GiftVIPDData>、D<Int32,FixedTimeEvent>
M14 公会加载态 ClubSignin、LureDonateData、IsOpenClubRed 时间或空字符串(无签到)、LureDonateData形状、存在标记(非null字符串→false)
M15 挑战 FishingChallengeData 现有InitChallengeData(string)的DTO形状,仅数据解码,不启动挑战
M16 宝箱链礼包 OfferChainsChestData TOfferChainsData
M17 Disco礼包 OfferDiscoPartyManager TOfferDiscoPartyData
M18 伙伴 EventPartnerGatherData EventPartnerGatherData,不能调用带Save的ClearData
M19 消块 EventCubeMeltData EventCubeMeltData
M20 活动礼包 EventPackData 现有LoadEventPackData的数据形状;配置派生仅在Apply前解码计算
M21 储蓄罐 PiggyBankPackData PiggyBankPackData.Data;旧兼容redirectID=0规范为4055001并检查配置存在,不写回
M22 砍价礼包 GeneralEventPackData GeneralEventPackData;redirectID须存在且PackType=3才可交BargainPackData,否则UnsupportedSchema,不能静默丢弃
M23 足迹 FootPrint UInt64位集,完整值由新增私有ulong受控字段保存;现有Int32枚举仅提供低32位视图,不代表全部64位;不调用UploadData,不拒未声明高位
M24 靶场 EventShootingRangeData EventShootingRangeData.RawData
M25 药水 epdk EventPotionData
M26 弹珠 epbdk EventPinballData
M27 刮刮卡 estdk EventScratchTicketData
M28 节日礼包 ThanksGivingPackData、ThanksGivingPackMigrationData 各自4个Int32逗号槽:EventId,RedirectId,TokenProgress,ChainProgress
M29 Pinata EventPinataPackData EventPinataPackData;仅SetData对应数据赋值,不GetFreshPinataPackData
M30 挖矿加载壳 无 InitServerData当前忽略字典,仅在_dataModel为空时new;KV必须替换成新未启动模型,不能复用旧实例
M31 登录附属 MMTest、hwGoogle、hwFacebook、Apple、EAccoutUrl、TapJoy_Diamond MMTest规则见主方案;渠道精确0/1;D<string,string>;Int32余额。额外当前非游客渠道名只允许来自测试身份枚举,不能任意字符串入意图

3.1 NewPlayerDataDic的附属键

键 解析及覆盖语义
vipL Int32,Int32记录以`
ld 恰好3个有限float,以`
ad `ConditionType:value1
bd Int32,Int32记录以;连接;替换FishingBoxOpenNum,空串为空字典
ct、rt Int32:Int32记录以,连接;分别替换ETDropAfterDropDic、RankTargetCount,空串为空字典
LDD 旧JSON格式同LuckDataDic;与ld共存时先分别严格解码,规范值必须一致,否则冲突
AD 旧JSON为D<ConditionType,string>,每个字符串恰好Id|Target|day三槽,按Int32/UInt64/Int32、InvariantCulture严格解析;不同于顶层AchievementData的对象值。键须为已定义ConditionType,归一化后重复键拒绝。与ad/顶层共存时全部先独立严格解码,共有字段冲突则ConflictingRepresentations;一致按ad > AD > 顶层AchievementData选择并整字典替换,不合并旧账号条目

NewPlayerDataDic中的其余旧散键为§2的19项,全部校验。其它内部键保留为Unconsumed,不能参与初始化。

同义表示同时出现时不依赖Dictionary迭代顺序:核心先作一致性检查;其余 Map_5_x与Map_x、GuideSaveDataNew与GuideSaveData、BattlePassRecordTask与旧战令、ld/LDD/LuckDataDic、ad/AD/AchievementData、bd/FishingBoxData、ct/ETDropAfterDropDic、rt/RankTargetCount全部先分别严格解码,共有字段不同则Conflict;一致时选新格式,缺新则选旧。新引导替换整个字典,不把未出现在新格式的旧组静默合并。

3.2 紧凑格式与动态键边界

  • Map动态后缀必须正整数且在固定TbMapData内;同一数值的非规范十进制后缀拒绝。只匹配Map_5_数字与Map_数字,其它前缀属于Unconsumed。鱼记录支持源码现存5/9槽两种:5槽Level/MaxG/TotalCard/TotalNum/NewFish;9槽采用0/1/5/6/7这五个位置,其余兼容槽必须保留但不作为当前字段。所有被消费槽严格解析。
  • BattlePassRecordTask恰好两个#分段(即一个分隔符);第一段按现存BattlePassRecord 7个|字段解析(ID、Level、Exp、BuyVipToastIndex、IsUnlockVip、两份逗号ID列表),第二段按BattleTask 5个|字段(两份key:value逗号字典、loginDays逗号列表、两个截止ticks)。空集合串允许,记录字段数量不符拒绝;ticks须在DateTime范围内。以受审源码SerializeObject顺序为准,不能改存档格式。
  • BlackFridayCouponDate支持7槽旧格式或9槽新格式,6个日期分量必须组成合法DateTime;旧格式RedirectId=0/IsBought=false,新格式末位只允许0/1;8槽或多余槽拒绝。
  • ThanksGiving两种键都严格4槽;不能复用原Data.Deserialize的“错误时打印原值并返回零对象”容错。
  • 所有分隔集合末尾空记录按当前保存器的Join格式:空整体表示空集合,非空整体中的空记录/重复key为Malformed。不得日志输出被拒绝的原字符串。

4. 无副作用加载路径及写点裁决

修订2不再调用旧LoadPlayerData/LoadPlayerDataNew总入口,以下旧路径仅为提取数据规则的依据。

旧同步链/风险点 KV路径明确处置
CompareDatas的PF×PlayerPrefs合并与写回 完全不调用;只有4B1不可变快照和独立解码状态
InitData MMTest缺失/旧格式两处写+SetMMTestValue写 内存规范化及本epoch意图;不运行GEvent统计
Agg InitData补渠道、AvatarToPF写EAccoutUrl 受控仅纯标记合并;不运行真实SDK和Login返回后的头像/邀请链
Camp.SetIsFix(isSave:false) 原路径本次抽查安全,但KV直接赋私有字段,禁止误调用默认true
PlayerData.energy相关setter、体力恢复、SavePlayerData 直接给energy/_lastEnergyRecoveryTime等赋值,不调用SetEnergy/恢复/保存或事件发布
PlayerFishData InitData中的GEvent、地图派生调用 纯decode后整组替换;不统计、不发解锁提示
FishingEventData Init/SetEvent/目标/排名恢复 不调用;只替换数据和置空运行派生对象;LimitedTime按冻结时钟做纯过滤
Potion/Pinball/Scratch GetDataFromServer→Init 不调用;只解析并替换原始数据,运行配置状态未激活
Pinata.LoadData→GetFreshPinataPackData 不调用刷新;只替换纯数据,奖励、红点不启动
Bargain.LoadData→AddGiftFaceUIForce 不调用旧函数;只替换_data
Partner.ClearData→SaveData、Cube.ClearData、活动默认建档 不借这些函数Reset;新增纯空态替换
Shop.Init、BattlePass刷新任务/领奖、Signin刷新 不调用;加载不等于功能激活
Digging.InitServerData之后的模型InitServerData异步服务 本阶段只new空模型;禁止调用异步业务初始化
所有旧LoadData带ELog/Debug输出原JSON、TryParse吞错 新KV纯入口只用结构化诊断;不能借用其日志和容错路径
PlayFabMgr定时/暂停/销毁/普通/立即写 主方案guard前置拦截;违反计数导致受控加载失败

因此无需声称未经读取的所有玩法保存调用都被4B2重构。受控路径只允许运行附件的纯适配器;未来新调用/活动激活属于范围变化,先补方案再审。

5. 每模块空态、替换职责和精确文件清单

以下路径相对Client/Assets/Scripts/。每行都要实现/测试 DecodeForKVLoad(纯)、ResetForKVLoad、ReplaceFromKVDecoded;同一类可合并方法但不能遗漏状态。固定配置/注入服务引用保留;旧账号的已加载数据和从它派生的缓存必须清除。无玩法运行时的订阅/计时器要求为零,非零拒绝准入,不在此模块中擅自扩展玩法退出。

ID 文件(白名单) 缺键/撤销时的明确空态与替换集合
M01 DataCenter/DataCenter.PlayerData.cs NewPlayerDataDic/vipScoreDic/newRodList新空集合;19核心字段置为不可发布空态(数值0、时间MinValue),合法Apply后全部由核心档替换;seasonId=0、IsOpenAppraise=false、InfiniteBuildingData=null;vip/vipLastScore/PriceLv=0、priceLevel=null,地图缓存0。空态仅用于未就绪,不允许作为新档进入玩法
M02 DataCenter/DataCenter.SmallGame.cs 三个游戏次数=0;加载派生的对手/预选缓存空、当前对手null;不触发取对手或网络
M03 DataCenter/DataCenter.PlayerFishData.cs;DataCenter/PlayerFishDataRod.cs mapFishData/mapProgress/fishData/fishCardData/rodData/rodLevelData/fishSwimToMap/fishSwimToMapCount/LuckDataDic/_honorlevel全新空集合;rodAccessoriesData=null;InitBotLuck/InitFishLuck/BotCount=0;所有地图/杆加载派生开关false、选择0。缺配件不调用默认建档方法
M04 DataCenter/DataCenter.PlayerShopData.cs;UI/Shop/BlackFridayCouponData.cs 购买/广告/钻石商店字典和buyData空,Voucher=0,ChainPackData/VIPAdPackData/TurntableAdData=null,刷新计数0/时间MinValue;CouponData原对象中账号字段归零/MinValue/false;SKU及配置引用不替换,不触发IAP查询
M05 DataCenter/DataCenter.FishingEventData.cs;DataCenter/FishingEvent_PVPData.cs 限时/固定/目标/排名/WheelLevelCount全新空集合;TurntableCoupon取固定表InitWheelTicket,WheelOperateCount/AllWheelOperateCount/freecount/PVPToken=0;目标队列、rankInit/rankTarget、wheelInit/wheelLevel和活动实例引用null;解锁集合空。缺活动是未激活,不启动Init
M06 DataCenter/BuffDataCenter.cs map/target/global buffs和showFishBuffIds空,fishDailyBuff=null
M07 Camp/CampDataMM.cs CampDataDic空,_isFix=false、_isOpenSkyscraper="0";_id=101、PreID/AreaIndex/AreaStep/AreaId/AreaStepCount/TimelineId=0;当前场景/StepInfo/AreaList派生引用清空,不加载建筑/资源
M08 DataCenter/AchievementDataManager.cs _achievementDatas整字典替换;空态空字典,不继续合并旧条目
M09 DataCenter/FishingBoxData.cs FishingNewBoxs/FishingBoxOpenNum新空字典,不领取、生成箱子或发布奖励
M10 DataCenter/DataCenter.GuideData.cs GuideSaveDataDic空、curSaveData/curDefine/curGroupDefine/popupPanelNode=null;配置指引定义保留,不启动引导
M11 DataCenter/BattlePassData.cs BattlePassRecord新构造态(ID/Exp/BuyVipToastIndex/IsUnlockVip/InflationRate=0,Level=1,三份奖励列表空),BattleTask新零态且集合显式空;不Refresh/Save/领奖
M12 DataCenter/DataCenter.SignupData.cs _monthlySignEvent=null;存在时替换,不读取会刷新日期/保存的dailySignEvent getter
M13 DataCenter/DataCenter.TriggerPackData.cs triggerPackList/giftVIPDData/fixedTimeShopDic新空;不Init、不注册条件事件
M14 Services/ClubService.cs signTime=null、lureDonateData新零态、IsOpenClubRed=true;只处理三项加载态,不登录公会/取网络成员信息
M15 DataCenter/FishingChallengeManager.cs 原始挑战存档对象null,挑战运行态未激活;有数据直接替换,不调用无参InitChallengeData
M16 DataCenter/OfferChainsChestManager.cs OfferChainsData=null;ValidIds空、活动派生引用null,不进场、不改FirstOpen
M17 DataCenter/OfferDiscoPartyManager.cs OfferDiscoPartyData=null、ValidIds空、活动派生引用null;FreeTime回构造值1
M18 DataCenter/EventPartnerGatherManager.cs GatherData=null、_isEventDataChanged=false、当前伙伴运行引用null;PartnerInfoManager/RobotInvitationSystem替换为空的新实例,不调用含Save的ClearData
M19 EventCubeMelt/Data/EventCubeMeltManager.cs EventData=null、_isInit=false、活动运行引用null;不调用Init/ClearData
M20 DataCenter/EventPackData.cs _data是单个Data结构,空态四字段currentEventID/purchaseCount/VIPLvlWhenEventActivate/redirectID全0;存在时整体替换,活动有效且redirectID=0时沿用4054001的旧兼容映射并校验表,不TriggerEventPack、不发布红点
M21 DataCenter/PiggyBankPackData.cs 原Data struct保持;Reset currentEventID/purchaseCount/VIPLvlWhenEventActivate/savingProgress/redirectID=0,doNeedTrigger/isTriggered=false,inflation=0,_kvLoadPresent=false(internal只读KVLoadPresent)。Missing保持该状态;Present八字段全赋后标记true;4055001映射仅用于存在载荷,不UpdateData
M22 DataCenter/BargainPackData.cs 原GeneralEventPackData struct保持;Reset十一整数、两bool、inflation清零/false,selections全新空List,_kvLoadPresent=false(internal只读KVLoadPresent)。Present通过PackType=3验证后完整替换、集合深复制、标记true,不经会触发FaceUI的LoadData;GeneralEventPackData.cs不改
M23 Services/FootPrintService.cs 新增私有ulong完整KV位集及internal只读验证访问;Reset完整字段和_footPrint同时归零,Replace完整保留UInt64并用显式unchecked低32位投影到_footPrint(注释说明视图位宽)。公开EFootPrint : int、IFootPrintService及旧LoadData/UpdateFootPrint/UploadData不改;受控范围禁止旧保存,完整值的4B3消费另审,不承诺退出受控后可玩
M24 UI/ShootingRange/ShootingRangeData.cs 原RawData struct保持;Reset EventId/CycleId/RedirectId/AmmoCount/CurrentStageIdx/ChainProgress=0,RewardFlag/TargetFlag按Int64归零,HasBeenOpened=false,Inflation=0,_kvLoadPresent=false(internal只读KVLoadPresent)。Missing不赋null,Present全字段替换后标记true;未就绪不得读玩法getter
M25 DataCenter/EventPotionDataManager.cs m_PotionData=null,运行关卡/表缓存回未激活;存在只装载原始数据,不调用Init
M26 DataCenter/EventPinballDataManager.cs m_PinballData=null,运行关卡/表缓存回未激活;同上
M27 DataCenter/EventScratchTicketDataManager.cs m_ScratchTicketData=null,运行关卡/表缓存回未激活;同上
M28 UI/Shop/ThanksGivingPackData.cs;UI/Shop/ChainPackWithProgressData.cs 两个实例的_data各自Missing/null,无共享可变对象;不做礼包迁移/领奖
M29 UI/PinataPack/EventPinataPackDataManager.cs 同一容器的EventPinataPackData实例直接将EventID/PinataMainID/PurchasedCount三个自动属性全置0,然后由已解码状态全量赋值;不调用AddPurchasedCount或GetFreshPinataPackData,不替换容器注册、不反射。Decode不得new该数据类(其字段初始化会Resolve表),用纯三整数DTO
M30 SandDig/DiggingGameManager.cs _dataModel替换全新未初始化实例、_broadLogic=null、游戏状态Normal;已有玩法资源/音频/board时拒绝受控准入,不在此释放活跃玩法资源
M31 Services/UserService.cs及其新增KV partial;Services/AggUserService.cs 本次副本/意图/绑定集合独立,MMTest=0、EAccoutUrl空、TapJoy余额0;真实SDK集合不作跨账号复用;PF公开身份默认不因受控加载而变成已授权

上表的“回未激活/派生引用”限定为相应loader能够写入或由该存档直接缓存的状态,不授权全面重构模块。实现需将各行定义映射到字段并在测试中显式断言,不能只测一个代表字段。若源码出现清单外的同步消费或状态写入,视为设计差异先补审,不能自动扩白名单。

固定Apply顺序:M01基础核心→M02→M03→M04→M05→M06→M07→M08→M09→M10→M11→M12→M13→M14→M15…M30→M31通知。全部Reset先执行,再开始任何Replace;物化DTO之间的引用由本次状态创建,配置引用只能来自同一只读快照。

6. 附件验收与证据口径

  • 每行至少:存在合法值、完全缺失、存在坏值;A有数据/B缺数据在同对象上检查整组字段;所有三类用例不能只靠字典已清空来冒充实际模块状态。
  • 每个现代/旧表示同时出现的组合验证一致与冲突;动态地图多ID、缺某地图、旧地图不会残留;同一模块多个可选键只缺其中一项也须清空该项。
  • 数据类型测试覆盖两种货币键别名、19槽精确长度、时间文化歧义、负位掩码与非负计数区别、null/重复属性/未知表项;无正式账号值。
  • 零副作用计数同时覆盖写入、SDK/网络调用、PlayerPrefs、事件、UI及计时器;任何非白名单调用导致失败,不能用最终数据库没变化代替。
  • 本附件为已审设计约束与待验清单,没有声称31模块已经实测通过,也没有声称现有业务支持重载;主会话须在代码交付后逐行填入实测证据。

7. D1/D2补审合并的边界与必验项

D1:空对象和空紧凑整体表示空集合。JSON null、对象值冒充旧AD字符串、缺槽/多槽、空记录、溢出、重复属性、枚举归一化后重复和未定义枚举均为Malformed;不尝试另一值类型解码、不回落合法新表示掩盖坏旧表示。分别验证三种成就表示的合法/缺失/坏值、AD字符串正例/对象值反例、Target超UInt64、重复枚举别名、三者共存一致/冲突、坏AD与合法ad共存仍失败。M08在同对象A→B缺失时为空,零保存/事件。

D2:明确投影表达式例如unchecked((EFootPrint)(int)(uint)completeBits),注释视图仅含低32位、完整值保留在ulong字段;不依赖默认溢出检查设置。验证0、低位组合、2^31、2^32、UInt64.MaxValue的完整值与投影视图,拒负值/溢出,A高位→B缺失时两个字段一起归零。必须在Unity真实对象上验证,离线算术不能替代;旧PF原方法及公开面保持,既定PF行为回归仍执行。guard禁止受控范围内UploadData写入,未来4B3完整值消费另审。

P2-α迁移盘点输入新增:旧PF成就加载器曾静默跳过的AD坏槽条目。当前受控KV按严格合同整体Malformed;未来真实档方案必须覆盖识别、备份、修复或人工退出,并经独立评审及用户产品确认。此次不读取或迁移真实档,不授权自动跳过/清档/默认建档。

版本历史:原附件SHA256为9E1A49E389CD6E9B38FA2CB55E5839ED5356E915AECCD4787FFE6F8276340745,保持不变。本正式版仅合并已审D1/D2及三条P3落地,未增加业务文件白名单;4B2实施完成仍需独立实施复核,检查点1不重做,检查点2–4顺序不变。

8. D3合并历史与验收补充

正式版1及原受审附件保留。D3已外部通过并由主会话裁决,三处值类型空态使用显式存在标记+全字段清零;具体字段与测试以《阶段4B2检查点2值类型空态差异补审方案》§2/§4为准。JSON null仍Malformed,缺键不规范成默认礼包,不改PF原字段类型/方法。同一真实对象A全部字段有值→B缺失→Present,验证所有字段及标记;M22集合不共享、M24 Int64高位清零;失败Reset后标记false,Reset失败Poisoned。D1/D2及其他合同不变。

7. D4/D5已批准实施细则(对应原M03/M18行)

以下细则及本轮裁决是M03/M18当前完整合同,原行须结合本节执行。引用差异提案的“建议/提请”均已按裁决接受;此为实施批准,不是验收。

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/零副作用证据。

P3补充:列清全部实例表随动读取者;承认热更时静态容量不再冻结;必须验证原MaxG较小时不抬高。