#if UNITY_EDITOR using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; using UnityEditor; namespace game.PlayerDataStorage { // Fixed module cases plus the complete B2-20 transaction. Global state is restored after actual drainage. internal static class PlayerKVEditorValidationHost { internal static Task RunAsync(string caseId) { if (caseId != "M08-M23" && caseId != "M21-M22-M24" && caseId != "M09-M10-M12" && caseId != "M14" && caseId != "M07" && caseId != "M20-M21-M22-M24-M28-M29" && caseId != "M16-M17" && caseId != "M06-M13" && caseId != "M15-M26-M27" && caseId != "M30" && caseId != "M19" && caseId != "M11" && caseId != "M25" && caseId != "M01-M02-M21-M22-M24" && caseId != "M04-M21-M22-M24" && caseId != "M05-M21-M22-M24" && caseId != "M03" && caseId != "M18" && caseId != "M31" && caseId != "B2-20") throw new ArgumentException("Unknown fixed validation case."); if (EditorApplication.isPlayingOrWillChangePlaymode || EditorApplication.isCompiling || EditorApplication.isUpdating) throw new InvalidOperationException("Editor is not idle."); int assertions = 0; if (caseId == "B2-20") return RunFullLoad(); if (caseId == "M31") return RunLoginState(); if (caseId == "M18") return RunPartner(); if (caseId == "M03") return RunFish(); if (caseId == "M21-M22-M24") return RunD3(); if (caseId == "M09-M10-M12") return RunCollections(); if (caseId == "M14") return RunClub(); if (caseId == "M20-M21-M22-M24-M28-M29") return RunD3(true); if (caseId == "M07") return RunCamp(); if (caseId == "M16-M17") return RunOffers(); if (caseId == "M06-M13") return RunBuffTrigger(); if (caseId == "M15-M26-M27") return RunActivities(); if (caseId == "M30") return RunDigging(); if (caseId == "M19") return RunCube(); if (caseId == "M11") return RunBattlePass(); if (caseId == "M25") return RunPotion(); if (caseId == "M05-M21-M22-M24") return RunD3(false,false,false,true); if (caseId == "M04-M21-M22-M24") return RunD3(false,false,true); if (caseId == "M01-M02-M21-M22-M24") return RunD3(false,true); void Check(bool value) { assertions++; if (!value) throw new InvalidOperationException("KV module assertion failed."); } var names = Enum.GetNames(typeof(cfg.ConditionType)).ToDictionary(n => n, n => (int)Enum.Parse(typeof(cfg.ConditionType), n), StringComparer.Ordinal); var snapshot = new PlayerKVSchemaSnapshot("synthetic-M08-M23", new[] { 101 }, new[] { 1 }, names, CultureInfo.InvariantCulture, new DateTime(2026, 9, 23, 0, 0, 0, DateTimeKind.Utc), 17); var achievements = new AchievementDataManager(null); var foot = new FootPrintService(); try { var e = new PlayerKVSchemaDiagnostics(); var a = AchievementDataManager.DecodeForKVLoad(new Dictionary(), new Dictionary { ["AD"] = "{\"0\":\"7|18446744073709551615|9\"}" }, snapshot, e); Check(a != null && e.TotalErrors == 0); achievements.ResetForKVLoad(); achievements.ReplaceFromKVDecoded(a); var actual = achievements.FindForKVValidation((cfg.ConditionType)0); Check(actual != null && actual.Id == 7 && actual.Target == ulong.MaxValue && actual.day == 9); actual.Target = 0; Check(achievements.FindForKVValidation((cfg.ConditionType)0).Target == ulong.MaxValue); var bad = AchievementDataManager.DecodeForKVLoad(new Dictionary(), new Dictionary { ["AD"] = "{\"0\":\"7|9\"}", ["ad"] = "0:7|1|9" }, snapshot, new PlayerKVSchemaDiagnostics()); Check(bad == null && achievements.FindForKVValidation((cfg.ConditionType)0).Target == ulong.MaxValue); var b = AchievementDataManager.DecodeForKVLoad(new Dictionary(), new Dictionary(), snapshot, new PlayerKVSchemaDiagnostics()); achievements.ResetForKVLoad(); achievements.ReplaceFromKVDecoded(b); Check(achievements.IsEmptyForKVValidation && achievements.FindForKVValidation((cfg.ConditionType)0) == null); foreach (ulong bits in new[] { 0UL, 255UL, 1UL << 31, 1UL << 32, ulong.MaxValue }) { e = new PlayerKVSchemaDiagnostics(); var state = FootPrintService.DecodeForKVLoad(new Dictionary { ["FootPrint"] = bits.ToString(CultureInfo.InvariantCulture) }, e); Check(state != null && e.TotalErrors == 0); foot.ResetForKVLoad(); foot.ReplaceFromKVDecoded(state); Check(foot.CompleteFootPrintForKVValidation == bits); Check(unchecked((uint)(int)foot.FootPrint) == unchecked((uint)bits)); var missing = FootPrintService.DecodeForKVLoad(new Dictionary(), new PlayerKVSchemaDiagnostics()); foot.ResetForKVLoad(); foot.ReplaceFromKVDecoded(missing); Check(foot.CompleteFootPrintForKVValidation == 0 && (int)foot.FootPrint == 0); } foreach (string value in new[] { "-1", "18446744073709551616", "null" }) Check(FootPrintService.DecodeForKVLoad(new Dictionary { ["FootPrint"] = value }, new PlayerKVSchemaDiagnostics()) == null); return Task.FromResult(JsonConvert.SerializeObject(new { success = true, caseId, assertions, scope = "M08-M23-only", fullB220 = false, sharedContainerTouched = false })); } catch (Exception ex) { return Task.FromResult(JsonConvert.SerializeObject(new { success = false, caseId, assertions, errorType = ex.GetType().Name, scope = "M08-M23-only", fullB220 = false })); } finally { // No global replacement or background work exists in these synchronous pure cases. achievements.ResetForKVLoad(); achievements.Dispose(); foot.ResetForKVLoad(); } } static bool Same(PlayerKVPlayerState a,PlayerKVPlayerState b) => ReferenceEquals(a,b) || (a!=null && b!=null && Same(a.Core,b.Core) && SameDictionary(a.Modern,b.Modern,(x,y)=>Same(x,y)) && SameDictionary(a.VipScores,b.VipScores,(x,y)=>Same(x,y)) && SameList(a.NewRods,b.NewRods,(x,y)=>Same(x,y)) && Same(a.Season,b.Season) && Same(a.Appraise,b.Appraise) && Same(a.Building,b.Building)); static bool Same(PlayerKVShopState a,PlayerKVShopState b) => ReferenceEquals(a,b) || (a!=null && b!=null && SameDictionary(a.Daily,b.Daily,(x,y)=>Same(x,y)) && SameDictionary(a.All,b.All,(x,y)=>Same(x,y)) && SameDictionary(a.Gift,b.Gift,(x,y)=>Same(x,y)) && SameDictionary(a.Advert,b.Advert,(x,y)=>Same(x,y)) && SameDictionary(a.Items,b.Items,(x,y)=>Same(x,y)) && SameList(a.Buy,b.Buy,(x,y)=>Same(x,y)) && Same(a.Voucher,b.Voucher) && Same(a.RefreshCount,b.RefreshCount) && Same(a.RefreshTime,b.RefreshTime) && Same(a.Chain,b.Chain) && Same(a.Turntable,b.Turntable) && Same(a.Vip,b.Vip) && Same(a.Coupon,b.Coupon)); static bool Same(PlayerKVEventBaseState a,PlayerKVEventBaseState b) => ReferenceEquals(a,b) || (a!=null && b!=null && SameDictionary(a.Limited,b.Limited,(x,y)=>Same(x,y)) && SameDictionary(a.Fixed,b.Fixed,(x,y)=>Same(x,y)) && SameDictionary(a.Target,b.Target,(x,y)=>Same(x,y)) && SameDictionary(a.Rank,b.Rank,(x,y)=>Same(x,y)) && SameDictionary(a.Wheel,b.Wheel,(x,y)=>Same(x,y)) && Same(a.Coupon,b.Coupon) && Same(a.Operate,b.Operate) && Same(a.AllOperate,b.AllOperate) && Same(a.Free,b.Free) && Same(a.PvpToken,b.PvpToken)); static bool Same(PlayerKVCampState a,PlayerKVCampState b) => ReferenceEquals(a,b) || (a!=null && b!=null && Same(a.IsFix,b.IsFix) && Same(a.Skyscraper,b.Skyscraper) && SameDictionary(a.Values,b.Values,(x,y)=>Same(x,y))); static bool Same(PlayerKVClubState a,PlayerKVClubState b) => ReferenceEquals(a,b) || (a!=null && b!=null && Same(a.SignTime,b.SignTime) && Same(a.DonateTime,b.DonateTime) && Same(a.DonateCount,b.DonateCount) && Same(a.RequestEnd,b.RequestEnd) && Same(a.OpenRed,b.OpenRed)); static bool Same(PlayerKVEventPackState a,PlayerKVEventPackState b) => ReferenceEquals(a,b) || (a!=null && b!=null && Same(a.EventId,b.EventId) && Same(a.PurchaseCount,b.PurchaseCount) && Same(a.VipLevel,b.VipLevel) && Same(a.RedirectId,b.RedirectId)); static bool Same(PlayerKVPiggyBankState a,PlayerKVPiggyBankState b) => ReferenceEquals(a,b) || (a!=null && b!=null && Same(a.IsPresent,b.IsPresent) && Same(a.currentEventID,b.currentEventID) && Same(a.purchaseCount,b.purchaseCount) && Same(a.VIPLvlWhenEventActivate,b.VIPLvlWhenEventActivate) && Same(a.savingProgress,b.savingProgress) && Same(a.redirectID,b.redirectID) && Same(a.doNeedTrigger,b.doNeedTrigger) && Same(a.isTriggered,b.isTriggered) && Same(a.inflation,b.inflation)); static bool Same(PlayerKVBargainState a,PlayerKVBargainState b) => ReferenceEquals(a,b) || (a!=null && b!=null && Same(a.IsPresent,b.IsPresent) && Same(a.currentEventID,b.currentEventID) && Same(a.redirectID,b.redirectID) && Same(a.VIPLvlWhenEventActivate,b.VIPLvlWhenEventActivate) && Same(a.purchaseCount,b.purchaseCount) && Same(a.savingProgress,b.savingProgress) && Same(a.discountLvl,b.discountLvl) && Same(a.visualProgress,b.visualProgress) && Same(a.visualDiscountLvl,b.visualDiscountLvl) && Same(a.rodID,b.rodID) && Same(a.lastPackIDBought,b.lastPackIDBought) && Same(a.packID,b.packID) && Same(a.doNeedTrigger,b.doNeedTrigger) && Same(a.isTriggered,b.isTriggered) && Same(a.inflation,b.inflation) && (a.selections==null ? b.selections==null : b.selections!=null && SameList(a.selections,b.selections,(x,y)=>Same(x,y)))); static bool Same(PlayerKVShootingRangeState a,PlayerKVShootingRangeState b) => ReferenceEquals(a,b) || (a!=null && b!=null && Same(a.IsPresent,b.IsPresent) && Same(a.EventId,b.EventId) && Same(a.CycleId,b.CycleId) && Same(a.RedirectId,b.RedirectId) && Same(a.AmmoCount,b.AmmoCount) && Same(a.CurrentStageIdx,b.CurrentStageIdx) && Same(a.ChainProgress,b.ChainProgress) && Same(a.RewardFlag,b.RewardFlag) && Same(a.TargetFlag,b.TargetFlag) && Same(a.HasBeenOpened,b.HasBeenOpened) && Same(a.Inflation,b.Inflation)); static bool Same(PlayerKVProgressPackState a,PlayerKVProgressPackState b) => ReferenceEquals(a,b) || (a!=null && b!=null && Same(a.IsPresent,b.IsPresent) && Same(a.EventId,b.EventId) && Same(a.RedirectId,b.RedirectId) && Same(a.TokenProgress,b.TokenProgress) && Same(a.ChainProgress,b.ChainProgress)); static bool Same(PlayerKVShopItemValue a,PlayerKVShopItemValue b) => ReferenceEquals(a,b) || (a!=null && b!=null && Same(a.Id,b.Id) && Same(a.Discount,b.Discount) && Same(a.Bought,b.Bought)); static bool Same(PlayerKVDailyValue a,PlayerKVDailyValue b) => ReferenceEquals(a,b) || (a!=null && b!=null && Same(a.Id,b.Id) && Same(a.Index,b.Index) && Same(a.LastId,b.LastId) && Same(a.Time,b.Time)); static bool Same(PlayerKVAdvertValue a,PlayerKVAdvertValue b) => ReferenceEquals(a,b) || (a!=null && b!=null && Same(a.Id,b.Id) && Same(a.Index,b.Index) && Same(a.Day,b.Day) && Same(a.Time,b.Time) && Same(a.Open,b.Open)); static bool Same(PlayerKVCouponState a,PlayerKVCouponState b) => ReferenceEquals(a,b) || (a!=null && b!=null && Same(a.ActivateTime,b.ActivateTime) && Same(a.EventId,b.EventId) && Same(a.RedirectId,b.RedirectId) && Same(a.Bought,b.Bought)); static bool Same(PlayerKVLimitedValue a,PlayerKVLimitedValue b) => ReferenceEquals(a,b) || (a!=null && b!=null && Same(a.Id,b.Id) && Same(a.Duration,b.Duration) && Same(a.StartTime,b.StartTime)); static bool Same(PlayerKVFixedTimeValue a,PlayerKVFixedTimeValue b) => ReferenceEquals(a,b) || (a!=null && b!=null && Same(a.id,b.id) && Same(a.endTime,b.endTime)); static bool Same(T a,T b) => EqualityComparer.Default.Equals(a,b); static bool SameList(IReadOnlyList a,IReadOnlyList b,Func same) => a.Count==b.Count && Enumerable.Range(0,a.Count).All(i=>same(a[i],b[i])); static bool SameDictionary(IReadOnlyDictionary a,IReadOnlyDictionary b,Func same) => a.Count==b.Count && a.All(p=>b.TryGetValue(p.Key,out var value) && same(p.Value,value)); static bool Same(PlayerKVCoreState a,PlayerKVCoreState b) => Enumerable.Range(0,16).All(i=> i==1 || i==10 ? a.Unsigned(i)==b.Unsigned(i) : i==7 ? a.EnergyTime==b.EnergyTime : a.Integer(i)==b.Integer(i)); static bool ContainsPayload(Newtonsoft.Json.Linq.JToken actual,Newtonsoft.Json.Linq.JToken expected) { if(expected is Newtonsoft.Json.Linq.JObject obj) return actual is Newtonsoft.Json.Linq.JObject && obj.Properties().All(p=>ContainsPayload(actual[p.Name],p.Value)); if(expected is Newtonsoft.Json.Linq.JArray arr) return actual is Newtonsoft.Json.Linq.JArray other && arr.Count==other.Count && Enumerable.Range(0,arr.Count).All(i=>ContainsPayload(other[i],arr[i])); return Newtonsoft.Json.Linq.JToken.DeepEquals(actual,expected); } sealed class ValidationConfig : asap.core.IConfig { readonly Dictionary values=new Dictionary(); public void Set(string key,object value) => values[key]=value; public T Get(string key,T fallback) => values.TryGetValue(key,out var value) ? (T)Convert.ChangeType(value,typeof(T),CultureInfo.InvariantCulture) : fallback; public string this[string key] => Get(key,(string)null); public float GetFloat(string key,float fallback) => Get(key,fallback); } sealed class ValidationEvents : asap.core.IEventAggregator { readonly asap.core.EventAggregator inner=new asap.core.EventAggregator(); internal int Published,Subscriptions; public IObservable GetEvent(string key) { Subscriptions++; return inner.GetEvent(key); } public void Publish(T value,string key) { Published++; throw new InvalidOperationException("Gameplay event during controlled load."); } } sealed class ValidationHttp : System.Net.Http.HttpMessageHandler { readonly Func send; internal ValidationHttp(Func send) { this.send=send; } protected override Task SendAsync(System.Net.Http.HttpRequestMessage request,System.Threading.CancellationToken token) => Task.FromResult(send(request)); } static System.Net.Http.HttpResponseMessage ValidationJson(object value) => new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content=new System.Net.Http.StringContent(JsonConvert.SerializeObject(value),System.Text.Encoding.UTF8,"application/json") }; static Dictionary FullRaw(cfg.Tables tables,bool optional) { var now=new DateTime(2026,9,23,0,0,0,DateTimeKind.Utc); string day=now.ToString("O",CultureInfo.InvariantCulture),end=now.AddDays(1).ToString("O",CultureInfo.InvariantCulture); var modern=new Dictionary{["newData"]=string.Join(",",new[]{"3","999","4","5","6","7",tables.TbRodData.DataList[0].ID.ToString(CultureInfo.InvariantCulture),day,"-1","8","77","9","0","0","10","11","12","13","14"})}; var raw=new Dictionary(); void Put(string key,object value) { raw[key]=JsonConvert.SerializeObject(value); } if(optional) { modern["vipL"]="1,2"; modern["ld"]="1.25|2.5|3"; modern["bd"]="1,2"; modern["ct"]="1:2"; modern["rt"]="3:4"; int condition=(int)Enum.GetValues(typeof(cfg.ConditionType)).GetValue(0); modern["ad"]=condition+":1|2|3"; Put("NewRodList",new[]{7,8}); raw["SeasonId"]="9"; raw["IsOpenAppraise"]="1"; raw["InfiniteBuildingData"]="synthetic"; foreach(var key in new[]{"RodData","RodLevelData","FishSwimToMap","FishSwimToMapCount","MapProgress","HonorLevelData"}) raw[key]="{\"-1\":7}"; var fish=tables.TbFishData.DataList.First(f=>tables.TbMapData.DataMap.ContainsKey(f.AreaID)); Put("Map_5_"+fish.AreaID,new Dictionary{[fish.ID]="0|-1|2|3|True"}); Put("InitRodAccessoriesData",new{GloveID=1,RodToSkin=new Dictionary{[2]=3},GloveIds=new[]{4},SkinIds=new[]{5},GloveNewIds=new[]{6},SkinNewIds=new[]{7}}); raw["ShopPackBuyCount"]="{\"x\":1}"; raw["ShopAllPackBuyCount"]="{\"2\":3}"; raw["ShopGiftBuyCount"]="{\"4\":5}"; raw["Voucher"]="6"; var daily=new{ID=1,index=2,lastID=3,time="raw"}; Put("ChainPackDataNew",daily); Put("TurntableAdData",daily); Put("VIPAdPackData",new{ID=7,index=8,day=9,cdTime="raw",isOpen=true}); Put("BuyData",new[]{1.5,2.5}); raw["AdvertAllCount"]="{\"-2\":10}"; raw["DiamondShopRefreshCount"]="11"; raw["DiamondShopRefreshTime"]=day; Put("DiamondShopItemRecords",new Dictionary{[12]=new{ItemID=13,Discount=.5,hasBought=1}}); raw["BlackFridayCouponDate"]="2026,9,23,1,2,3,14,15,1"; Put("LimitedTimeEventDic",new Dictionary{[1]=new{id=2,duration=9999999d,startTime=day}}); Put("FixedTimeEventDic",new Dictionary{[3]=new{id=4,endTime=end}}); raw["WheelLevelCount"]="{\"5\":6}"; foreach(var key in new[]{"TurntableRoll","WheelOperateCount","AllWheelOperateCount","WheelLevelFreeCount","PVPToken"}) raw[key]="7"; var buff=new{SortID=1,sourceID=2,buffID=3,isEnd=false,buffEndTime=end}; Put("BuffDataList",new Dictionary{[7]=buff}); Put("TargetBuffDataList",new[]{buff}); Put("FishDailyBuff",buff); Put("GlobalBuffDataList",new Dictionary{[7]=buff}); raw["IsFix"]="true";raw["IsOpenSkyscraper"]="1";raw["CampDataDic"]="{\"CampId\":205}";raw["GuideSaveDataNew"]="A|3|1;B|4|0"; Put("BattlePassRecord",new{ID=1,Level=2,Exp=3,BuyVipToastIndex=4,IsUnlockVip=1,NormalRewardGet=new[]{5,6},VipRewardGet=new[]{7,8},LastDropIDs=new[]{9},InflationRate=2.5f}); Put("BattleTask",new{dic_dailyTasks=new Dictionary{[9]=10},dic_weeklyTasks=new Dictionary{[11]=12},loginDays=new[]{13,14},DailyTaskEndTime=now,WeeklyTaskEndTime=now.AddDays(1)}); Put("MonthlySignEvent",new{index=4,dailySignEvent=daily}); Put("TriggerPackDataList",new[]{new{ID=1,index=2,lastID=3,vipLevel=4,mapID=5,time="raw"}}); Put("GiftVIPDData",new Dictionary{["A"]=new{oldVipLevel=1,curVipLevel=2,timer=3}}); Put("FixedTimeShopDic",new Dictionary{[7]=new{id=8,endTime=end}}); raw["ClubSignin"]=day;raw["IsOpenClubRed"]="1";Put("LureDonateData",new{DonateCountTime=day,ClubLureDonateCount=7,ClubLureRequestCDEnd=end}); Put("FishingChallengeData",new{FirstOpen=true,EventId=11,RankTierLevel=11,Finished=11,RedirectID=11,Score=11,ProcessedScore=11,step=11,uiStep=11,index=11,StartTime=day,EndTime=end,eliminateNumber=new[]{7,9},robotAccountList=new[]{7,9},InflationRate=1.5f}); var offer=new{FirstOpen=true,EventId=7,StartTime=day,EndTime=end,ItemId=-1,LotteryNum=5,Items=new[]{new{Id=1,Item=2,Number=3,Status=2}},RewardList=new[]{8,9},Finished=true,ChestReward=10}; Put("OfferChainsChestData",offer);Put("OfferDiscoPartyManager",offer); Put("EventPartnerGatherData",new{FirstOpen=false,EventId=18,StartTime=day,EndTime=end,CurrentPoints=1,CurrentMiningProgress=2,Token=3,InvitedCount=4,TicketCount=5, CompletedNodesList=new[]{new[]{6,7}},BuilderPartnerScore=new[]{new{Item1=8,Item2=9}},PartnerBonus=new{Item1=1.25f,Item2=end},FreeSpinNum=10, Bots=new Dictionary{[11]=new{Id="synthetic-bot",ScorePlayer=12,ScoreBot=13,Grade=14,TargetScore=15,WakeStartTime=day,WakingSeconds=16}}, IsRecvFinalReward=true,UIFinishedPartners=new[]{true,false},IsFinished=true,SpinState=2,InflationRate=2.5f,IsEnableAutoMatch=true,LastAutoMatchTime=day}); Put("EventCubeMeltData",new{TicketCount=1,FirstOpen=true,IsFinished=true,EventId=2,IsInGuide=true,StartTime=day,EndTime=end,CurrentStagetId=3, CollectItems=new[]{new{ItemId=4,Count=5,MaxCount=6,ItemTrueId=7}},BoardData=new[]{8,9},BoardViewSize=new{x=10,y=11},CollectRewardsId=new[]{12,13},IsFinalRewardGet=true,PieceDatas=new[]{new[]{14,15},new[]{16,17}},Cycle=18,InflationRate=1.5f}); Put("EventPackData",new{currentEventID=-1,purchaseCount=2,VIPLvlWhenEventActivate=3,redirectID=4}); Put("PiggyBankPackData",new{currentEventID=11,purchaseCount=11,VIPLvlWhenEventActivate=11,savingProgress=11,redirectID=0,doNeedTrigger=true,isTriggered=true,inflation=1.5f}); Put("GeneralEventPackData",new{currentEventID=11,redirectID=tables.TbEventPackManager.DataList.First(p=>p.PackType==3).ID,VIPLvlWhenEventActivate=11,purchaseCount=11,savingProgress=11,discountLvl=11,visualProgress=11,visualDiscountLvl=11,rodID=11,lastPackIDBought=11,packID=11,doNeedTrigger=true,isTriggered=true,inflation=1.5f,selections=new[]{7,9}}); raw["FootPrint"]="18446744073709551615"; Put("EventShootingRangeData",new{EventId=11,CycleId=11,RedirectId=11,AmmoCount=11,CurrentStageIdx=11,ChainProgress=11,RewardFlag=long.MaxValue,TargetFlag=long.MinValue,HasBeenOpened=true,Inflation=1.5f}); var item=new{name="synthetic",index=3,isOver=true,DataType=2,ColorTypeList=new[]{7,9},ShelfTypeList=new[]{0,1},GridStateList=new[]{0,5},FromDisplayAreaIndexList=new[]{1,2}}; Put("epdk",new{EventID=11,PotionID=11,StageID=11,Stage1TotalLevel=11,Stage2TotalLevel=11,LevelID=11,LevelIndex=11,CorrectNum=11,SubmitNum=11,Token=11,ConsumedToken=11,Rule=11,PreEventID=11,PrePotionID=11,PreStageID=11,PreLevelID=11,Round=11,FirstPlayedEventId=11,IsOver=true,Name="synthetic",InflationRate=1.5f,LevelTargetData=item,LevelDisplayData=item,LevelProcessedList=new[]{item}}); Put("epbdk",new{EventID=11,PinballMainID=11,SpinPoint=11,Scores=11,Multiple=11,Token=11,ConsumedToken=11,RewardIndex=11,Force=1.5f,LuckyValue=1.5f,InflationRate=1.5f}); Put("estdk",new{EventID=11,ScratchMainID=11,ChainPackID=11,Token=11,ConsumedToken=11,RewardTaskConsumedToken=11,EventScratchRewardTaskID=11,EventScratchCardID=11,TotalConsumedToken=11,ChainPackProgress=11,magIndex=11,IsOver=true,LuckyValue=1.5f,InflationRate=1.5f,StampRewardList=new[]{7,9},StampRewardDict=new Dictionary{[7]=new{EventScratchCardID=8,Count=9}}}); raw["ThanksGivingPackData"]="1,2,3,4";raw["ThanksGivingPackMigrationData"]="5,6,7,8";Put("EventPinataPackData",new{EventID=7,PinataMainID=8,PurchasedCount=9}); raw["EAccoutUrl"]="{\"synthetic\":\"avatar\"}";raw["hwGoogle"]="1";raw["Apple"]="1";raw["TapJoy_Diamond"]="12"; } Put("NewPlayerDataDic",modern); return raw; } static async Task RunFullLoad() { if(running || asap.core.GContext.Ready || UnityEngine.Resources.FindObjectsOfTypeAll().Length!=0 || PlayFabMgr.HasLiveInstanceForKVLoad) throw new InvalidOperationException("Game context is active."); running=true; int assertions=0,auth=0,gets=0,borrowed=0,forbidden=0; bool success=false,restored=false; string errorType=null; var originalContainer=asap.core.GContext.container; var originalEvents=ValidationContextAccess.Events; var isolated=new asap.core.Container(); var events=new ValidationEvents(); UnityEngine.GameObject shell=null; AggUserService user=null; PlayerKVGameLoadTransaction transaction=null; void Check(bool value) { assertions++; if(!value) throw new InvalidOperationException("Full load assertion failed."); } try { var directory=System.IO.Path.GetFullPath(System.IO.Path.Combine(UnityEngine.Application.dataPath,"../../cfgData")); var tables=new cfg.Tables(name=>SimpleJSON.JSON.Parse(System.IO.File.ReadAllText(System.IO.Path.Combine(directory,name+".json")))); var snapshot=new PlayerKVSchemaSnapshot("local-full-4B2",tables.TbRodData.DataMap.Keys,tables.TbMapData.DataMap.Keys, Enum.GetNames(typeof(cfg.ConditionType)).ToDictionary(n=>n,n=>(int)Enum.Parse(typeof(cfg.ConditionType),n)),CultureInfo.InvariantCulture, new DateTime(2026,9,23,0,0,0,DateTimeKind.Utc),tables.TbGlobalConfig.InitWheelTicket, tables.TbEventPackManager.DataMap.ToDictionary(p=>p.Key,p=>p.Value.PackType), tables.TbFishingEvent.DataMap.ToDictionary(p=>p.Key,p=>new PlayerKVEventConfig(p.Value.RedirectID,(p.Value.TimeDefinition as cfg.LimitedTime)?.EndTime)),tables.TbGlobalConfig.GlobalTimeOffset, tables.TbFishData.DataMap.ToDictionary(p=>p.Key,p=>new PlayerKVFishConfig(p.Key,p.Value.AreaID,p.Value.Quality,p.Value.FishCardID,p.Value.BaseWeight,p.Value.PointExtraParam,p.Value.WeightRandom)), tables.TbFishCard.DataMap.ToDictionary(p=>p.Key,p=>new PlayerKVFishCardConfig(p.Key,p.Value.MaxLevel,p.Value.CardsRequiredList,p.Value.WeightMagList))); ValidationContextAccess.Use(isolated); ValidationContextAccess.Events=events; Check(asap.core.ContainerExtensions.RegisterInstance(isolated,tables)); shell=new UnityEngine.GameObject("KV controlled inactive fixture"); shell.SetActive(false); shell.hideFlags=UnityEngine.HideFlags.HideAndDontSave; var pf=shell.AddComponent(); Check(pf.IsUnstartedForKVLoad && !PlayFabMgr.HasLiveInstanceForKVLoad); var api=new Uri("http://synthetic.invalid/"); var config=new ValidationConfig(); config.Set(GConstant.K_Event_API_URL,api.AbsoluteUri); var server=new CustomServerMgr(config,_=>new ValidationHttp(_=>{forbidden++;throw new InvalidOperationException("PF API transport forbidden.");})); user=new AggUserService(PlayerKVControlledLoadCapability.CreateForValidation(_=>throw new InvalidOperationException())){CustomId="101",config=config,customServerMgr=server}; var player=new GameCore.PlayerData(tables,null); var small=new GameCore.SmallGameData(tables,null,null); var fish=new GameCore.PlayerFishData(tables,null); var shop=new GameCore.PlayerShopData(); var eventBase=new GameCore.FishingEventData(tables); var buffs=new BuffDataCenter(tables); var camp=new CampDataMM(tables,null); var achievements=new AchievementDataManager(tables); var box=new FishingBoxData(); var guide=new GameCore.GuideDataCenter(null,null); var battle=new BattlePassData(tables); var sign=new GameCore.SigninData(tables); var trigger=new GameCore.TriggerPackData(tables); var club=new ClubService(); var challenge=new FishingChallengeManager(); var chains=new DataCenter.OfferChainsChestManager(); var disco=new DataCenter.OfferDiscoPartyManager(); var partner=new DataCenter.EventPartnerGatherManager(); var cube=new EventCubeMeltManager(); var eventPack=new EventPackData(); var piggy=new PiggyBankPackData(); var bargain=new BargainPackData(); var foot=new FootPrintService(); var shooting=new EventShootingRangeData(); var potion=new EventPotionDataManager(); var pinball=new EventPinballDataManager(); var scratch=new EventScratchTicketDataManager(); var progress=eventBase.ThanksGivingPackData; var migration=eventBase.ChainPackWithProgressMigrationData; var pinataTarget=new EventPinataPackData(); Check(asap.core.ContainerExtensions.RegisterInstance(isolated,pinataTarget)); var pinata=EventPinataPackDataManager.CreateForKVLoad(); var digging=new Script.RuntimeScript.DiggingGameManager(); var modules=new List(); void Add(int id,Action reset,Action replace) where T:class => modules.Add(new PlayerKVLoadModule(id,reset,d=>replace(d.Module(id)))); Add(1,player.ResetForKVLoad,player.ReplaceFromKVDecoded); Add(2,small.ResetForKVLoad,small.ReplaceFromKVDecoded); Add(3,fish.ResetForKVLoad,fish.ReplaceFromKVDecoded); Add(4,shop.ResetForKVLoad,shop.ReplaceFromKVDecoded); Add(5,eventBase.ResetForKVLoad,eventBase.ReplaceFromKVDecoded); Add(6,buffs.ResetForKVLoad,buffs.ReplaceFromKVDecoded); Add(7,camp.ResetForKVLoad,camp.ReplaceFromKVDecoded); Add(8,achievements.ResetForKVLoad,achievements.ReplaceFromKVDecoded); Add(9,box.ResetForKVLoad,box.ReplaceFromKVDecoded); Add(10,guide.ResetForKVLoad,guide.ReplaceFromKVDecoded); Add(11,battle.ResetForKVLoad,battle.ReplaceFromKVDecoded); Add(12,sign.ResetForKVLoad,sign.ReplaceFromKVDecoded); Add(13,trigger.ResetForKVLoad,trigger.ReplaceFromKVDecoded); Add(14,club.ResetForKVLoad,club.ReplaceFromKVDecoded); Add(15,challenge.ResetForKVLoad,challenge.ReplaceFromKVDecoded); Add(16,chains.ResetForKVLoad,chains.ReplaceFromKVDecoded); Add(17,disco.ResetForKVLoad,disco.ReplaceFromKVDecoded); Add(18,partner.ResetForKVLoad,partner.ReplaceFromKVDecoded); Add(19,cube.ResetForKVLoad,cube.ReplaceFromKVDecoded); Add(20,eventPack.ResetForKVLoad,eventPack.ReplaceFromKVDecoded); Add(21,piggy.ResetForKVLoad,piggy.ReplaceFromKVDecoded); Add(22,bargain.ResetForKVLoad,bargain.ReplaceFromKVDecoded); Add(23,foot.ResetForKVLoad,foot.ReplaceFromKVDecoded); Add(24,shooting.ResetForKVLoad,shooting.ReplaceFromKVDecoded); Add(25,potion.ResetForKVLoad,potion.ReplaceFromKVDecoded); Add(26,pinball.ResetForKVLoad,pinball.ReplaceFromKVDecoded); Add(27,scratch.ResetForKVLoad,scratch.ReplaceFromKVDecoded); Add(28,()=>{progress.ResetForKVLoad();migration.ResetForKVLoad();},p=>{progress.ReplaceFromKVDecoded(p.Current);migration.ReplaceFromKVDecoded(p.Migration);}); Add(29,pinata.ResetForKVLoad,pinata.ReplaceFromKVDecoded); Add(30,digging.ResetForKVLoad,digging.ReplaceFromKVDecoded); modules.Add(new PlayerKVLoadModule(31,user.ResetForKVLoad,d=>user.ReplaceFromKVDecoded(d.Module(31),d.Epoch,d.Raw))); transaction=new PlayerKVGameLoadTransaction(modules,System.Threading.Thread.CurrentThread.ManagedThreadId,()=>user.KVIntents?.Discard()); user.ConfigureKVForValidation(transaction,pf,server,()=>!asap.core.GContext.Ready && digging.CanResetForKVLoad); var rawA=FullRaw(tables,true); var rawB=FullRaw(tables,false); void CheckSavedPayload(string key,object actual,params string[] ignored) { var expected=Newtonsoft.Json.Linq.JToken.Parse(rawA[key]); foreach(var name in ignored) ((Newtonsoft.Json.Linq.JObject)expected).Remove(name); Check(ContainsPayload(Newtonsoft.Json.Linq.JToken.Parse(JsonConvert.SerializeObject(actual)),expected)); } void CheckSnapshots(Dictionary raw) { var errors=new PlayerKVSchemaDiagnostics();var modern=PlayerKVSaveSchema.DecodeModern(raw,errors); Check(Same(player.SnapshotForKVValidation(),PlayerKVSaveSchema.DecodePlayer(raw,modern,snapshot,errors))); Check(Same(shop.SnapshotForKVValidation(),PlayerKVSaveSchema.DecodeShop(raw,snapshot,errors))); Check(Same(eventBase.SnapshotForKVValidation(),PlayerKVSaveSchema.DecodeEventBase(raw,modern,snapshot,errors))); Check(Same(camp.SnapshotForKVValidation(),PlayerKVSaveSchema.DecodeCamp(raw,errors))); Check(Same(club.SnapshotForKVValidation(),PlayerKVSaveSchema.DecodeClub(raw,snapshot,errors))); Check(Same(eventPack.SnapshotForKVValidation(),PlayerKVSaveSchema.DecodeEventPack(raw,snapshot,errors))); Check(Same(piggy.SnapshotForKVValidation(),PlayerKVSaveSchema.DecodePiggyBank(raw,snapshot,errors))); Check(Same(bargain.SnapshotForKVValidation(),PlayerKVSaveSchema.DecodeBargain(raw,snapshot,errors))); Check(Same(shooting.SnapshotForKVValidation(),PlayerKVSaveSchema.DecodeShootingRange(raw,snapshot,errors))); Check(Same(progress.SnapshotForKVValidation(),PlayerKVSaveSchema.DecodeProgressPack(raw,false,errors))); Check(Same(migration.SnapshotForKVValidation(),PlayerKVSaveSchema.DecodeProgressPack(raw,true,errors))); Check(errors.TotalErrors==0); } PlayerKVGameLoadCapability Capability(Dictionary raw,bool newPF) { System.Net.CookieContainer jar=null; return PlayerKVGameLoadCapability.CreateForValidation(new PlayerKVIdentitySnapshot("FF",newPF,newPF ? PlayerKVIdentityChannel.hwVkid : PlayerKVIdentityChannel.none,Array.Empty()),snapshot,api, System.IO.Path.GetFullPath(System.IO.Path.Combine(UnityEngine.Application.dataPath,"../../CodexOutput/upserver/KVGameLoadTests/unity-cache",Guid.NewGuid().ToString("N"))), cookies=> {jar=cookies;return new ValidationHttp(r=>{if(r.RequestUri.AbsolutePath!="/account/Login"){forbidden++;throw new InvalidOperationException();} auth++;jar.Add(api,new System.Net.Cookie("sid","synthetic"));return ValidationJson(new{state=0,internalId=17,newCreate=false,createTime="2026-01-01T00:00:00Z",svrTime="2026-01-02T00:00:00Z"});});}, cookies=>{Check(ReferenceEquals(cookies,jar));return new ValidationHttp(r=>{if(r.RequestUri.AbsolutePath!="/playerdata/Get"){forbidden++;throw new InvalidOperationException();}gets++;return ValidationJson(new{data=raw});});}, cookies=>{Check(ReferenceEquals(cookies,jar));return new ValidationHttp(r=>{borrowed++;return new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized){Content=new System.Net.Http.StringContent("{}")} ;});}); } var a=await user.LoadKVForValidationAsync(Capability(rawA,true)); Check(a.State==PlayerKVGameLoadState.LoadedForValidation && a.ModuleCount==31 && a.IntentCount==2); Check(user.KVLoadedForValidation && !user.IsLogin && !user.IsAuthorized && user.InternalId==0 && user.KVReceiverCountForValidation==0 && user.KVNotificationCount==1); CheckSnapshots(rawA); CheckSavedPayload("BattlePassRecord",battle.BattlePassRecord); CheckSavedPayload("BattleTask",battle.BattleTask); CheckSavedPayload("MonthlySignEvent",sign.monthlySignEvent); CheckSavedPayload("FishingChallengeData",challenge.fishingChallengeData); CheckSavedPayload("OfferChainsChestData",chains.OfferChainsData); CheckSavedPayload("OfferDiscoPartyManager",disco.OfferDiscoPartyData,"Finished","ChestReward"); CheckSavedPayload("EventPartnerGatherData",partner.GatherData); CheckSavedPayload("EventCubeMeltData",cube.EventData); CheckSavedPayload("epdk",potion.PotionData); CheckSavedPayload("epbdk",pinball.PinballData); CheckSavedPayload("estdk",scratch.ScratchTicketData); CheckSavedPayload("TriggerPackDataList",trigger.triggerPackList); CheckSavedPayload("GiftVIPDData",trigger.giftVIPDData); CheckSavedPayload("InitRodAccessoriesData",fish.rodAccessoriesData); var selectedFish=tables.TbFishData.DataList.First(f=>tables.TbMapData.DataMap.ContainsKey(f.AreaID)); var fishValue=fish.FishForKVValidation(selectedFish.AreaID,selectedFish.ID); Check(fishValue.Level==0 && fishValue.MaxG==-1 && fishValue.TotalCard==2 && fishValue.TotalNum==3 && fishValue.NewFish && fish.IndexesMatchForKVValidation(selectedFish.AreaID,selectedFish.ID,selectedFish.FishCardID)); Check(fish.DictionariesMatchForKVValidation(PlayerKVSaveSchema.DecodeFish(rawA,PlayerKVSaveSchema.DecodeModern(rawA,new PlayerKVSchemaDiagnostics()),snapshot,new PlayerKVSchemaDiagnostics()))); Check(fish.LuckDataDic["InitFishLuck"]==1.25f && fish.LuckDataDic["InitBotLuck"]==2.5f && fish.LuckDataDic["BotCount"]==3 && fish.InitBotLuck==0 && fish.InitFishLuck==0 && fish.BotCount==0); Check(box.FishingBoxOpenNum[1]==2 && user.KVTapJoyBalance==12 && user.MMTestForKVValidation==15 && user.EAccoutUrl["synthetic"]=="avatar" && user.KVLinkedAccounts.OrderBy(c=>c).SequenceEqual(new[]{PlayerKVIdentityChannel.hwGoogle,PlayerKVIdentityChannel.Apple,PlayerKVIdentityChannel.hwVkid}.OrderBy(c=>c))); for(int kind=0;kind<4;kind++) {var buff=buffs.BuffForKVValidation(kind,kind==1 ? 0 : 7);Check(buff.SortID==1 && buff.sourceID==2 && buff.buffID==3 && !buff.isEnd && buff.buffEndTime==snapshot.NowUtc.AddDays(1) && !buff.isNew && buff.addDown==0);} Check(small.BombGameCount==13 && small.FishHuntGameCount==14 && small.IsOpponentEmptyForKVValidation); var achievement=achievements.FindForKVValidation((cfg.ConditionType)Enum.GetValues(typeof(cfg.ConditionType)).GetValue(0)); Check(achievement.Id==1 && achievement.Target==2 && achievement.day==3); Check(guide.GuideSaveDataDic["A"].groupName=="A" && guide.GuideSaveDataDic["A"].guideSubIndex==3 && guide.GuideSaveDataDic["A"].isFinish && !guide.GuideSaveDataDic["B"].isFinish && guide.GuideSaveDataDic["B"].guideSubIndex==4); Check(trigger.FixedForKVValidation(7).id==8 && trigger.FixedForKVValidation(7).endTime==snapshot.NowUtc.AddDays(1).ToString("O",CultureInfo.InvariantCulture)); Check(pinataTarget.EventID==7 && pinataTarget.PinataMainID==8 && pinataTarget.PurchasedCount==9); Check(player.SnapshotForKVValidation().Season==9 && small.HeistGameCount==12 && !fish.EmptyForKVValidation && shop.SnapshotForKVValidation().Voucher==6); Check(eventBase.SnapshotForKVValidation().PvpToken==7 && !buffs.IsEmptyForKVValidation && camp.SnapshotForKVValidation().IsFix && !achievements.IsEmptyForKVValidation); Check(box.FishingBoxOpenNum.Count==1 && guide.GuideSaveDataDic.Count==2 && battle.BattlePassRecord.ID==1 && sign.monthlySignEvent.index==4 && !trigger.IsEmptyForKVValidation); Check(club.SnapshotForKVValidation().DonateCount==7 && challenge.fishingChallengeData!=null && chains.OfferChainsData!=null && disco.OfferDiscoPartyData!=null && partner.GatherData!=null && cube.EventData!=null); Check(eventPack.SnapshotForKVValidation().PurchaseCount==2 && piggy.KVLoadPresent && bargain.KVLoadPresent && foot.CompleteFootPrintForKVValidation==ulong.MaxValue && shooting.KVLoadPresent); Check(potion.PotionData!=null && pinball.PinballData!=null && scratch.ScratchTicketData!=null && progress.SnapshotForKVValidation().IsPresent && migration.SnapshotForKVValidation().IsPresent && pinataTarget.PurchasedCount==9 && digging.DataModel!=null); var oldPartner=partner.PartnerInfoManager; var oldRobot=partner.RobotInvitationSystem; var oldModel=digging.DataModel; var intents=user.KVIntents; user.CustomId="102"; var b=await user.LoadKVForValidationAsync(Capability(rawB,false)); Check(b.State==PlayerKVGameLoadState.LoadedForValidation && b.ModuleCount==31 && b.IntentCount==1 && b.Epoch>a.Epoch); CheckSnapshots(rawB); Check(player.SnapshotForKVValidation().Season==0 && !player.SnapshotForKVValidation().Appraise && player.SnapshotForKVValidation().NewRods.Count==0 && player.SnapshotForKVValidation().VipScores.Count==0 && player.IsDerivedEmptyForKVValidation && small.IsOpponentEmptyForKVValidation); Check(fish.EmptyForKVValidation && shop.SnapshotForKVValidation().Daily.Count==0 && shop.SnapshotForKVValidation().Items.Count==0 && shop.SnapshotForKVValidation().Voucher==0); Check(eventBase.SnapshotForKVValidation().Limited.Count==0 && eventBase.SnapshotForKVValidation().Fixed.Count==0 && eventBase.SnapshotForKVValidation().Target.Count==0 && eventBase.SnapshotForKVValidation().Rank.Count==0 && eventBase.SnapshotForKVValidation().Wheel.Count==0 && eventBase.IsInactiveForKVValidation); Check(buffs.IsEmptyForKVValidation && !camp.SnapshotForKVValidation().IsFix && camp.SnapshotForKVValidation().Values.Count==0 && achievements.IsEmptyForKVValidation && box.FishingBoxOpenNum.Count==0 && guide.GuideSaveDataDic.Count==0); Check(battle.BattlePassRecord.ID==0 && battle.BattlePassRecord.NormalRewardGet.Count==0 && battle.BattleTask.dic_dailyTasks.Count==0 && sign.monthlySignEvent==null && trigger.IsEmptyForKVValidation); var br=battle.BattlePassRecord; var bt=battle.BattleTask; Check(br.Level==1 && br.Exp==0 && br.BuyVipToastIndex==0 && br.IsUnlockVip==0 && br.InflationRate==0 && br.VipRewardGet.Count==0 && br.LastDropIDs.Count==0 && bt.dic_weeklyTasks.Count==0 && bt.loginDays.Count==0 && bt.DailyTaskEndTime==default && bt.WeeklyTaskEndTime==default); Check(club.SnapshotForKVValidation().SignTime==null && club.SnapshotForKVValidation().DonateCount==0 && club.SnapshotForKVValidation().OpenRed); Check(challenge.fishingChallengeData==null && challenge.IsInactiveForKVValidation && chains.OfferChainsData==null && chains.IsInactiveForKVValidation && disco.OfferDiscoPartyData==null && disco.FreeTime==1 && disco.IsInactiveForKVValidation); Check(partner.GatherData==null && partner.RuntimeEmptyForKVValidation && !ReferenceEquals(oldPartner,partner.PartnerInfoManager) && !ReferenceEquals(oldRobot,partner.RobotInvitationSystem)); Check(cube.EventData==null && cube.IsInactiveForKVValidation && eventPack.SnapshotForKVValidation().PurchaseCount==0 && !piggy.KVLoadPresent && !bargain.KVLoadPresent && !shooting.KVLoadPresent && foot.CompleteFootPrintForKVValidation==0); Check(potion.PotionData==null && potion.IsInactiveForKVValidation && pinball.PinballData==null && pinball.IsInactiveForKVValidation && scratch.ScratchTicketData==null && scratch.IsInactiveForKVValidation); Check(!progress.SnapshotForKVValidation().IsPresent && !migration.SnapshotForKVValidation().IsPresent && pinataTarget.EventID==0 && pinataTarget.PinataMainID==0 && pinataTarget.PurchasedCount==0 && !ReferenceEquals(oldModel,digging.DataModel)); Check(user.KVTapJoyBalance==0 && user.MMTestForKVValidation==0 && user.EAccoutUrl.Count==0 && user.KVLinkedAccounts.Count==0 && intents.Discarded && user.KVReceiverCountForValidation==0 && user.KVNotificationCount==2); Check(auth==2 && gets==2 && forbidden==0 && events.Published==0 && events.Subscriptions==0 && pf.IsUnstartedForKVLoad); var unauthorized=await server.SendKVForValidationAsync("__kv_validation/unauthorized"); await user.CloseKVForValidationAsync(); Check(!unauthorized.IsCurrent && !user.KVLoadedForValidation && user.InternalId==0 && borrowed==1 && user.KVIntents==null); success=true; } catch(Exception ex) { errorType=ex.GetType().Name; } finally { try { if(user!=null) await user.CloseKVForValidationAsync(); if(transaction!=null) Check(transaction.Clear()); } finally { isolated.Dispose(); if(shell!=null) UnityEngine.Object.DestroyImmediate(shell); ValidationContextAccess.Use(originalContainer); ValidationContextAccess.Events=originalEvents; running=false; restored=ReferenceEquals(originalContainer,asap.core.GContext.container) && ReferenceEquals(originalEvents,ValidationContextAccess.Events); } } return JsonConvert.SerializeObject(new{success=success && restored,caseId="B2-20",assertions,errorType,restored,auth,gets,borrowed,forbidden,eventPublishes=events.Published,eventSubscriptions=events.Subscriptions, realModules=31,sameObjects=true,fullB220=success && restored,scope="31-real-modules-two-epochs-controlled-transaction"}); } static Task RunLoginState() { int assertions=0; void Check(bool value) { assertions++; if(!value) throw new InvalidOperationException("Login state assertion failed."); } var capability=PlayerKVControlledLoadCapability.CreateForValidation(_=>throw new InvalidOperationException("No transport in module test.")); var user=new AggUserService(capability); try { var identity=new PlayerKVIdentitySnapshot("FF",true,PlayerKVIdentityChannel.hwVkid,new[]{PlayerKVIdentityChannel.hwFacebook}); var raw=new Dictionary{["EAccoutUrl"]="{\"synthetic\":\"avatar\"}",["hwGoogle"]="1",["Apple"]="1",["TapJoy_Diamond"]="12"}; var a=PlayerKVSaveSchema.DecodeLogin(raw,identity,new PlayerKVSchemaDiagnostics()); user.ResetForKVLoad(); user.ReplaceFromKVDecoded(a,1,raw); Check(user.MMTestForKVValidation==15 && user.KVTapJoyBalance==12 && user.EAccoutUrl["synthetic"]=="avatar"); Check(user.KVLinkedAccounts.Count==4 && user.KVLinkedAccounts.Contains(PlayerKVIdentityChannel.hwFacebook) && user.KVLinkedAccounts.Contains(PlayerKVIdentityChannel.hwVkid)); Check(user.KVIntents.Count==2 && user.KVLoadingCopy["MMTest"]=="MMFailParam#15" && user.KVLoadingCopy["hwVkid"]=="1" && !raw.ContainsKey("hwVkid")); Check(!user.IsLogin && !user.IsAuthorized && user.InternalId==0 && user.ApiPlayerId==null); var intents=user.KVIntents; var oldLinks=user.KVLinkedAccounts; user.EAccoutUrl.Clear(); raw.Clear(); Check(a.AccountUrls.Count==1 && a.Linked.Count==4 && a.Normalizations.Count==2); var b=PlayerKVSaveSchema.DecodeLogin(new Dictionary(),new PlayerKVIdentitySnapshot(null,false,PlayerKVIdentityChannel.none,Array.Empty()),new PlayerKVSchemaDiagnostics()); user.ResetForKVLoad(); user.ReplaceFromKVDecoded(b,2,new Dictionary()); Check(intents.Discarded && intents.DiscardedCount==2 && intents.Count==0); Check(user.MMTestForKVValidation==0 && user.KVTapJoyBalance==0 && user.EAccoutUrl.Count==0 && user.KVLinkedAccounts.Count==0 && oldLinks.Count==4); Check(user.KVLoadingCopy.Count==1 && user.KVIntents.Count==1 && user.KVIntents.Epoch==2); user.ResetForKVLoad(); Check(user.KVIntents==null && user.KVLoadingCopy==null && user.EAccoutUrl.Count==0 && user.KVLinkedAccounts.Count==0 && user.InternalId==0); return Task.FromResult(JsonConvert.SerializeObject(new{success=true,caseId="M31",assertions,fullB220=false,sharedContainerTouched=false})); } catch(Exception ex) { return Task.FromResult(JsonConvert.SerializeObject(new{success=false,caseId="M31",assertions,errorType=ex.GetType().Name,fullB220=false})); } finally { user.ResetForKVLoad(); } } static Task RunPartner() { if(running || asap.core.GContext.Ready || UnityEngine.Resources.FindObjectsOfTypeAll().Length!=0) throw new InvalidOperationException("Game context is active."); running=true; var originalContainer=asap.core.GContext.container; var originalAggregator=ValidationContextAccess.Events; var containerA=new asap.core.Container(); var containerB=new asap.core.Container(); bool success=false,restored=false; int assertions=0; string errorType=null; void Check(bool value) { assertions++; if(!value) throw new InvalidOperationException("Partner assertion failed."); } try { var directory=System.IO.Path.GetFullPath(System.IO.Path.Combine(UnityEngine.Application.dataPath,"../../cfgData")); cfg.Tables Tables(bool change) => new cfg.Tables(name=> { var json=SimpleJSON.JSON.Parse(System.IO.File.ReadAllText(System.IO.Path.Combine(directory,name+".json"))); if(change && name=="tbeventpartnerconfig") json[0]["InvitationCount"][1]=json[0]["InvitationCount"][1].AsInt+1; return json; }); var tablesA=Tables(false); var tablesB=Tables(true); int capacityA=tablesA.TbEventPartnerConfig.InvitationCount[1],capacityB=tablesB.TbEventPartnerConfig.InvitationCount[1]; Check(capacityB==capacityA+1); Check(asap.core.ContainerExtensions.RegisterInstance(containerA,tablesA)); Check(asap.core.ContainerExtensions.RegisterInstance(containerB,tablesB)); ValidationContextAccess.Use(containerA); ValidationContextAccess.Events=new asap.core.EventAggregator(); // Empty containers do not have factories for live identity/friend services. Check(asap.core.ContainerExtensions.Resolve(containerA)==null && asap.core.ContainerExtensions.Resolve(containerA)==null); var module=new DataCenter.EventPartnerGatherManager(); var oldPartner=module.PartnerInfoManager; var oldRobot=module.RobotInvitationSystem; Check(oldPartner.BuildPartners.Length==capacityA && EventPartnerGather.PartnerInfoManager.BuilderCapacity==capacityA); var now=new DateTime(2026,9,23,0,0,0,DateTimeKind.Utc); var sample=new DataCenter.EventPartnerGatherData { FirstOpen=false,EventId=18,StartTime=now,EndTime=now.AddDays(1),CurrentPoints=1,CurrentMiningProgress=2, Token=3,InvitedCount=4,TicketCount=5,CompletedNodesList=new List>{new List{6,7},new List()}, BuilderPartnerScore=new List<(int,int)>{(8,9)},PartnerBonus=(1.25f,now.AddHours(1)),FreeSpinNum=10, Bots=new Dictionary{[11]=new EventPartnerData.EventBuildBotInfo { Id="synthetic-bot",ScorePlayer=12,ScoreBot=13,Grade=14,TargetScore=15,WakeStartTime=now.AddHours(2),WakingSeconds=16 }}, IsRecvFinalReward=true,UIFinishedPartners=new List{true,false},IsFinished=true,SpinState=DataCenter.ESpinState.Morespin, InflationRate=2.5f,IsEnableAutoMatch=true,LastAutoMatchTime=now.AddHours(3) }; var text=JsonConvert.SerializeObject(sample); var snapshot=new PlayerKVSchemaSnapshot("local-M18",Array.Empty(),Array.Empty(),new Dictionary(),CultureInfo.InvariantCulture,now,0); var state=DataCenter.EventPartnerGatherManager.DecodeForKVLoad(new Dictionary{["EventPartnerGatherData"]=text},snapshot,new PlayerKVSchemaDiagnostics()); Check(state!=null); module.ResetForKVLoad(); module.ReplaceFromKVDecoded(state); Check(JsonConvert.SerializeObject(module.GatherData)==text && module.GatherData.Bots[11].ActionQueue==null); Check(module.RuntimeEmptyForKVValidation && !ReferenceEquals(oldPartner,module.PartnerInfoManager) && !ReferenceEquals(oldRobot,module.RobotInvitationSystem)); module.GatherData.CompletedNodesList[0].Clear(); module.GatherData.BuilderPartnerScore.Clear(); module.GatherData.Bots[11].ScoreBot=999; module.GatherData.UIFinishedPartners.Clear(); Check(state.CompletedNodesList[0].Count==2 && state.BuilderPartnerScore.Count==1 && state.Bots[11].ScoreBot==13 && state.UIFinishedPartners.Count==2); module.ReplaceFromKVDecoded(state); Check(JsonConvert.SerializeObject(module.GatherData)==text); var partnerA=module.PartnerInfoManager; var robotA=module.RobotInvitationSystem; partnerA.Cache.RefusedPartnerIds=new List{"synthetic-old"}; ValidationContextAccess.Use(containerB); ValidationContextAccess.Events=new asap.core.EventAggregator(); Check(asap.core.ContainerExtensions.Resolve(containerB)==null && asap.core.ContainerExtensions.Resolve(containerB)==null); var moduleB=new DataCenter.EventPartnerGatherManager(); Check(partnerA.BuildPartners.Length==capacityA && oldPartner.BuildPartners.Length==capacityA && moduleB.PartnerInfoManager.BuildPartners.Length==capacityB && EventPartnerGather.PartnerInfoManager.BuilderCapacity==capacityB); Check(oldPartner.ActiveFriendCountThreshold==tablesA.TbEventPartnerConfig.FriendRecommendParam1[0]); var missing=DataCenter.EventPartnerGatherManager.DecodeForKVLoad(new Dictionary(),snapshot,new PlayerKVSchemaDiagnostics()); module.ResetForKVLoad(); module.ReplaceFromKVDecoded(missing); Check(module.GatherData==null && module.RuntimeEmptyForKVValidation); Check(!ReferenceEquals(partnerA,module.PartnerInfoManager) && !ReferenceEquals(robotA,module.RobotInvitationSystem)); Check(module.PartnerInfoManager.BuildPartners.Length==capacityB && module.PartnerInfoManager.BuildPartners.All(p=>p==null) && module.PartnerInfoManager.Cache.ActionQ==null && module.PartnerInfoManager.Cache.RefusedPartnerIds==null && module.PartnerInfoManager.EventBuildInfo==null); Check(partnerA.Cache.RefusedPartnerIds.Count==1); module.ReplaceFromKVDecoded(state); Check(JsonConvert.SerializeObject(module.GatherData)==text); module.ResetForKVLoad(); moduleB.ResetForKVLoad(); success=true; } catch(Exception ex) { errorType=ex.GetType().Name; } finally { try { containerA.Dispose(); containerB.Dispose(); } finally { ValidationContextAccess.Use(originalContainer); ValidationContextAccess.Events=originalAggregator; running=false; restored=ReferenceEquals(asap.core.GContext.container,originalContainer) && ReferenceEquals(ValidationContextAccess.Events,originalAggregator); } } return Task.FromResult(JsonConvert.SerializeObject(new { success=success && restored,caseId="M18",assertions,errorType,restored,scope="M18-D4-only",fullB220=false })); } static Task RunFish() { int assertions=0; int roundingCase=-1,actualRoundBits=0,expectedRoundBits=0; void Check(bool value) { assertions++; if(!value) throw new InvalidOperationException("Fish assertion failed."); } var module=new GameCore.PlayerFishData(null,null); try { var directory=System.IO.Path.GetFullPath(System.IO.Path.Combine(UnityEngine.Application.dataPath,"../../cfgData")); var tables=new cfg.Tables(name=>SimpleJSON.JSON.Parse(System.IO.File.ReadAllText(System.IO.Path.Combine(directory,name+".json")))); var snapshot=new PlayerKVSchemaSnapshot("local-M03",tables.TbRodData.DataMap.Keys,tables.TbMapData.DataMap.Keys, new Dictionary(),CultureInfo.InvariantCulture,new DateTime(2026,9,23,0,0,0,DateTimeKind.Utc),tables.TbGlobalConfig.InitWheelTicket, fish:tables.TbFishData.DataMap.ToDictionary(p=>p.Key,p=>new PlayerKVFishConfig(p.Key,p.Value.AreaID,p.Value.Quality,p.Value.FishCardID,p.Value.BaseWeight,p.Value.PointExtraParam,p.Value.WeightRandom)), cards:tables.TbFishCard.DataMap.ToDictionary(p=>p.Key,p=>new PlayerKVFishCardConfig(p.Key,p.Value.MaxLevel,p.Value.CardsRequiredList,p.Value.WeightMagList))); var config=tables.TbFishData.DataList.First(f=>f.Quality<=5 && snapshot.HasMap(f.AreaID) && snapshot.Card(f.FishCardID)!=null && snapshot.Card(f.FishCardID).MaxLevel>=1 && snapshot.Card(f.FishCardID).Weights.Count>=1 && snapshot.Card(f.FishCardID).Required.Count>=1); var raw=new Dictionary { ["Map_5_"+config.AreaID]=JsonConvert.SerializeObject(new Dictionary{[config.ID]="1|99999|0|7|True"}), ["InitRodAccessoriesData"]="{\"GloveID\":1,\"RodToSkin\":{\"2\":3},\"GloveIds\":[4],\"SkinIds\":[5],\"GloveNewIds\":[6],\"SkinNewIds\":[7]}" }; foreach(var key in new[] { "RodData","RodLevelData","FishSwimToMap","FishSwimToMapCount","MapProgress","HonorLevelData" }) raw[key]="{\"-1\":7}"; var state=GameCore.PlayerFishData.DecodeForKVLoad(raw,new Dictionary{["ld"]="1.25|2.5|3"},snapshot,new PlayerKVSchemaDiagnostics()); Check(state!=null); module.ResetForKVLoad(); module.ReplaceFromKVDecoded(state); var actual=module.FishForKVValidation(config.AreaID,config.ID); float limit=config.BaseWeight*config.PointExtraParam*(1+config.WeightRandom); limit*=1+tables.TbFishCard[config.FishCardID].WeightMagList[0]; limit=UnityEngine.Mathf.Round(limit*100)/100; Check(actual.Level==1 && actual.MaxG==Math.Min(99999,limit) && actual.TotalCard==Math.Max(0,tables.TbFishCard[config.FishCardID].CardsRequiredList[0]) && actual.TotalNum==7 && actual.NewFish); Check(module.IndexesMatchForKVValidation(config.AreaID,config.ID,config.FishCardID) && module.DictionariesMatchForKVValidation(state)); Check(module.InitFishLuck==0 && module.InitBotLuck==0 && module.BotCount==0 && module.LuckDataDic["BotCount"]==3); var acc=module.rodAccessoriesData; Check(acc.GloveID==1 && acc.RodToSkin[2]==3 && acc.GloveIds[0]==4 && acc.SkinIds[0]==5 && acc.GloveNewIds[0]==6 && acc.SkinNewIds[0]==7); actual.TotalNum=999; module.LuckDataDic.Clear(); acc.RodToSkin.Clear(); acc.GloveIds.Clear(); acc.SkinIds.Clear(); acc.GloveNewIds.Clear(); acc.SkinNewIds.Clear(); Check(state.Fish[config.ID].Number==7 && state.Luck.Count==3 && state.Accessories.RodSkin.Count==1 && state.Accessories.Gloves.Count==1 && state.Accessories.Skins.Count==1 && state.Accessories.NewGloves.Count==1 && state.Accessories.NewSkins.Count==1); module.ReplaceFromKVDecoded(state); Check(!ReferenceEquals(actual,module.FishForKVValidation(config.AreaID,config.ID)) && module.FishForKVValidation(config.AreaID,config.ID).TotalNum==7); module.IsOpenMap=true; module.IsOpenRod=true; module.IsOpenPiercing=true; module.MapTipforUnlocked=8; module.RodSelectedInPack=9; module.InitBotLuck=2; module.InitFishLuck=3; module.BotCount=4; module.ResetForKVLoad(); module.ReplaceFromKVDecoded(GameCore.PlayerFishData.DecodeForKVLoad(new Dictionary(),new Dictionary(),snapshot,new PlayerKVSchemaDiagnostics())); Check(module.EmptyForKVValidation); foreach(float n in new[] { 0f,.005f,.015f,.025f,-.005f,-.015f,-.025f,.0049999994f,.0050000004f,123.456f,-123.456f,1000000f,1e20f,1e30f }) { roundingCase++; actualRoundBits=BitConverter.ToInt32(BitConverter.GetBytes(PlayerKVSaveSchema.RoundFishWeight(n)),0); expectedRoundBits=BitConverter.ToInt32(BitConverter.GetBytes(UnityEngine.Mathf.Round(n*100)/100),0); Check(actualRoundBits==expectedRoundBits); } var synthetic=new PlayerKVSchemaSnapshot("M03-min",Array.Empty(),new[]{1},new Dictionary(),CultureInfo.InvariantCulture,snapshot.NowUtc,0, fish:new Dictionary{[1]=new PlayerKVFishConfig(1,1,5,1,2,1,0)},cards:new Dictionary{[1]=new PlayerKVFishCardConfig(1,1,new[]{10},new[]{0f})}); var small=GameCore.PlayerFishData.DecodeForKVLoad(new Dictionary{["Map_5_1"]="{\"1\":\"1|0.125|0|1|false\"}"},new Dictionary(),synthetic,new PlayerKVSchemaDiagnostics()); module.ResetForKVLoad(); module.ReplaceFromKVDecoded(small); Check(module.FishForKVValidation(1,1).MaxG==.125f && module.IndexesMatchForKVValidation(1,1,1)); return Task.FromResult(JsonConvert.SerializeObject(new {success=true,caseId="M03",assertions,fullB220=false,sharedContainerTouched=false})); } catch(Exception ex) { return Task.FromResult(JsonConvert.SerializeObject(new {success=false,caseId="M03",assertions,errorType=ex.GetType().Name,roundingCase,actualRoundBits,expectedRoundBits,fullB220=false})); } finally { module.ResetForKVLoad(); } } static Task RunPotion() { int assertions=0; void Check(bool value) { assertions++; if(!value) throw new InvalidOperationException("Potion assertion failed."); } var manager=new EventPotionDataManager(); try { var item=new { name="synthetic",index=3,isOver=true,DataType=2,ColorTypeList=new[] { 7,9 },ShelfTypeList=new[] { 0,1 },GridStateList=new[] { 0,5 },FromDisplayAreaIndexList=new[] { 1,2 } }; var state=EventPotionDataManager.DecodeForKVLoad(new Dictionary { ["epdk"]=JsonConvert.SerializeObject(new { EventID=11,PotionID=11,StageID=11,Stage1TotalLevel=11,Stage2TotalLevel=11,LevelID=11,LevelIndex=11,CorrectNum=11,SubmitNum=11,Token=11,ConsumedToken=11,Rule=11,PreEventID=11,PrePotionID=11,PreStageID=11,PreLevelID=11,Round=11,FirstPlayedEventId=11,IsOver=true,Name="synthetic",InflationRate=1.5f,LevelTargetData=item,LevelDisplayData=item,LevelProcessedList=new[] { item } }) },new PlayerKVSchemaDiagnostics()); Check(state!=null && state.IsPresent); manager.ResetForKVLoad(); manager.ReplaceFromKVDecoded(state); var actual=manager.PotionData; Check(actual.EventID==state.EventID); Check(actual.PotionID==state.PotionID); Check(actual.StageID==state.StageID); Check(actual.Stage1TotalLevel==state.Stage1TotalLevel); Check(actual.Stage2TotalLevel==state.Stage2TotalLevel); Check(actual.LevelID==state.LevelID); Check(actual.LevelIndex==state.LevelIndex); Check(actual.CorrectNum==state.CorrectNum); Check(actual.SubmitNum==state.SubmitNum); Check(actual.Token==state.Token); Check(actual.ConsumedToken==state.ConsumedToken); Check(actual.Rule==state.Rule); Check(actual.PreEventID==state.PreEventID); Check(actual.PrePotionID==state.PrePotionID); Check(actual.PreStageID==state.PreStageID); Check(actual.PreLevelID==state.PreLevelID); Check(actual.Round==state.Round); Check(actual.FirstPlayedEventId==state.FirstPlayedEventId); Check(actual.IsOver==state.IsOver); Check(actual.Name==state.Name); Check(actual.InflationRate==state.InflationRate); foreach(var entry in new[] { actual.LevelTargetData,actual.LevelDisplayData,actual.LevelProcessedList[0] }) { Check(entry.name=="synthetic" && entry.index==3 && entry.isOver && (int)entry.DataType==2 && entry.ColorTypeList.SequenceEqual(new[] { 7,9 }) && entry.ShelfTypeList.Select(n=>(int)n).SequenceEqual(new[] { 0,1 }) && entry.GridStateList.Select(n=>(int)n).SequenceEqual(new[] { 0,5 }) && entry.FromDisplayAreaIndexList.SequenceEqual(new[] { 1,2 })); entry.ColorTypeList.Clear(); entry.ShelfTypeList.Clear(); entry.GridStateList.Clear(); entry.FromDisplayAreaIndexList.Clear(); } Check(state.LevelTargetData.Colors.Count==2 && state.LevelDisplayData.Shelves.Count==2 && state.LevelProcessedList[0].Grids.Count==2); manager.ReplaceFromKVDecoded(state); Check(!ReferenceEquals(actual,manager.PotionData) && manager.PotionData.LevelTargetData.ColorTypeList.Count==2 && !ReferenceEquals(manager.PotionData.LevelTargetData,manager.PotionData.LevelDisplayData)); manager.ScrollerDataList.Add(actual.LevelTargetData); manager.ResetForKVLoad(); manager.ReplaceFromKVDecoded(EventPotionDataManager.DecodeForKVLoad(new Dictionary(),new PlayerKVSchemaDiagnostics())); Check(manager.PotionData==null && manager.IsInactiveForKVValidation); return Task.FromResult(JsonConvert.SerializeObject(new { success=true,caseId="M25",assertions,fullB220=false,sharedContainerTouched=false })); } catch(Exception ex) { return Task.FromResult(JsonConvert.SerializeObject(new { success=false,caseId="M25",assertions,errorType=ex.GetType().Name,fullB220=false })); } finally { manager.ResetForKVLoad(); } } static Task RunBattlePass() { int assertions=0; void Check(bool value) { assertions++; if(!value) throw new InvalidOperationException("Battle pass assertion failed."); } var manager=new BattlePassData(null); var snapshot=new PlayerKVSchemaSnapshot("M11",Array.Empty(),Array.Empty(),new Dictionary(), CultureInfo.InvariantCulture,new DateTime(2026,9,23),0); try { var raw=new Dictionary { ["BattlePassRecord"]=JsonConvert.SerializeObject(new { ID=1,Level=2,Exp=3,BuyVipToastIndex=4,IsUnlockVip=1, NormalRewardGet=new[] { 5,6 },VipRewardGet=new[] { 7,8 },LastDropIDs=new[] { 99 },InflationRate=2.5f }), ["BattleTask"]=JsonConvert.SerializeObject(new { dic_dailyTasks=new Dictionary { [9]=10 }, dic_weeklyTasks=new Dictionary { [11]=12 },loginDays=new[] { 13,14 },DailyTaskEndTime=new DateTime(15),WeeklyTaskEndTime=new DateTime(16) }) }; var a=BattlePassData.DecodeForKVLoad(raw,snapshot,new PlayerKVSchemaDiagnostics()); manager.ResetForKVLoad(); manager.ReplaceFromKVDecoded(a); var r=manager.BattlePassRecord; var t=manager.BattleTask; Check(r.ID==1 && r.Level==2 && r.Exp==3 && r.BuyVipToastIndex==4 && r.IsUnlockVip==1 && r.NormalRewardGet.SequenceEqual(new[] { 5,6 }) && r.VipRewardGet.SequenceEqual(new[] { 7,8 }) && r.LastDropIDs.SequenceEqual(new[] { 99 }) && r.InflationRate==2.5f); Check(t.dic_dailyTasks[9]==10 && t.dic_weeklyTasks[11]==12 && t.loginDays.SequenceEqual(new[] { 13,14 }) && t.DailyTaskEndTime.Ticks==15 && t.WeeklyTaskEndTime.Ticks==16); r.NormalRewardGet.Clear(); r.VipRewardGet.Clear(); r.LastDropIDs.Clear(); t.dic_dailyTasks.Clear(); t.dic_weeklyTasks.Clear(); t.loginDays.Clear(); Check(a.Record.Normal.Count==2 && a.Record.Vip.Count==2 && a.Record.LastDrops.Count==1 && a.Task.Daily.Count==1 && a.Task.Weekly.Count==1 && a.Task.LoginDays.Count==2); manager.ResetForKVLoad(); manager.ReplaceFromKVDecoded(BattlePassData.DecodeForKVLoad(new Dictionary(),snapshot,new PlayerKVSchemaDiagnostics())); r=manager.BattlePassRecord; t=manager.BattleTask; Check(r.ID==0 && r.Level==1 && r.Exp==0 && r.BuyVipToastIndex==0 && r.IsUnlockVip==0 && r.InflationRate==0 && r.NormalRewardGet.Count==0 && r.VipRewardGet.Count==0 && r.LastDropIDs.Count==0); Check(t.dic_dailyTasks.Count==0 && t.dic_weeklyTasks.Count==0 && t.loginDays.Count==0 && t.DailyTaskEndTime==default && t.WeeklyTaskEndTime==default); return Task.FromResult(JsonConvert.SerializeObject(new { success=true,caseId="M11",assertions,fullB220=false,sharedContainerTouched=false })); } catch(Exception ex) { return Task.FromResult(JsonConvert.SerializeObject(new { success=false,caseId="M11",assertions,errorType=ex.GetType().Name,fullB220=false })); } finally { manager.ResetForKVLoad(); } } static Task RunCube() { int assertions=0; void Check(bool value) { assertions++; if(!value) throw new InvalidOperationException("Cube assertion failed."); } var manager=new EventCubeMeltManager(); var snapshot=new PlayerKVSchemaSnapshot("M19",Array.Empty(),Array.Empty(),new Dictionary(), CultureInfo.InvariantCulture,new DateTime(2026,9,23),0); try { const string json="{\"TicketCount\":1,\"FirstOpen\":true,\"IsFinished\":true,\"EventId\":2,\"IsInGuide\":true,\"StartTime\":\"2026-09-22T00:00:00.0000000Z\",\"EndTime\":\"2026-09-24T00:00:00.0000000Z\",\"CurrentStagetId\":3,\"CollectItems\":[{\"ItemId\":4,\"Count\":5,\"MaxCount\":6,\"ItemTrueId\":7}],\"BoardData\":[8,9],\"BoardViewSize\":{\"x\":10,\"y\":11},\"CollectRewardsId\":[12,13],\"IsFinalRewardGet\":true,\"PieceDatas\":[[14,15],[16,17]],\"Cycle\":18,\"InflationRate\":1.5}"; var a=EventCubeMeltManager.DecodeForKVLoad(new Dictionary { ["EventCubeMeltData"]=json },snapshot,new PlayerKVSchemaDiagnostics()); manager.ResetForKVLoad(); manager.ReplaceFromKVDecoded(a); var actual=manager.EventData; Check(actual.TicketCount==1 && actual.FirstOpen && actual.IsFinished && actual.EventId==2 && actual.IsInGuide && actual.StartTime==a.StartTime && actual.EndTime==a.EndTime && actual.CurrentStagetId==3); Check(actual.CollectItems[0].ItemId==4 && actual.CollectItems[0].Count==5 && actual.CollectItems[0].MaxCount==6 && actual.CollectItems[0].ItemTrueId==7 && actual.BoardData.SequenceEqual(new[] { 8,9 })); Check(actual.BoardViewSize.x==10 && actual.BoardViewSize.y==11 && actual.CollectRewardsId.SequenceEqual(new[] { 12,13 }) && actual.IsFinalRewardGet && actual.Cycle==18 && actual.InflationRate==1.5f); Check(actual.PieceDatas[0].SequenceEqual(new[] { 14,15 }) && actual.PieceDatas[1].SequenceEqual(new[] { 16,17 })); actual.PieceDatas[0].Clear(); actual.BoardData.Clear(); actual.CollectItems[0].Count=99; actual.CollectRewardsId.Clear(); Check(a.PieceDatas[0].Count==2 && a.BoardData.Count==2 && a.CollectItems[0].Count==5 && a.CollectRewardsId.Count==2); manager.ReplaceFromKVDecoded(a); Check(!ReferenceEquals(actual,manager.EventData) && manager.EventData.PieceDatas[0].Count==2 && manager.EventData.CollectItems[0].Count==5); manager.PrevPieceDatas=new List> { new List { 99 } }; manager.CurrentPieceDatas=new List> { new List { 88 } }; manager.ResetForKVLoad(); manager.ReplaceFromKVDecoded(EventCubeMeltManager.DecodeForKVLoad(new Dictionary(),snapshot,new PlayerKVSchemaDiagnostics())); Check(manager.EventData==null && manager.IsInactiveForKVValidation); return Task.FromResult(JsonConvert.SerializeObject(new { success=true,caseId="M19",assertions,fullB220=false,sharedContainerTouched=false })); } catch(Exception ex) { return Task.FromResult(JsonConvert.SerializeObject(new { success=false,caseId="M19",assertions,errorType=ex.GetType().Name,fullB220=false })); } finally { manager.ResetForKVLoad(); } } static Task RunDigging() { int assertions=0; void Check(bool value) { assertions++; if(!value) throw new InvalidOperationException("Digging assertion failed."); } var manager=new Script.RuntimeScript.DiggingGameManager(); try { var state=Script.RuntimeScript.DiggingGameManager.DecodeForKVLoad(); manager.ResetForKVLoad(); manager.ReplaceFromKVDecoded(state); var a=manager.DataModel; a.InflationRate=7; a.NewAddDigCount=9; manager.ResetForKVLoad(); manager.ReplaceFromKVDecoded(state); Check(manager.DataModel!=null && !ReferenceEquals(a,manager.DataModel) && manager.DataModel.InflationRate==0 && manager.DataModel.NewAddDigCount==0); Check(manager.CanResetForKVLoad && manager.BroadLogic==null && manager.GameState==Script.RuntimeScript.SandDigGameState.Normal); var active=manager.DataModel; active.AllClamps.Add(1); Check(!manager.CanResetForKVLoad); bool rejected=false; try { manager.ResetForKVLoad(); } catch(InvalidOperationException) { rejected=true; } Check(rejected && ReferenceEquals(manager.DataModel,active) && active.AllClamps.Count==1); active.AllClamps.Clear(); return Task.FromResult(JsonConvert.SerializeObject(new { success=true,caseId="M30",assertions,fullB220=false,sharedContainerTouched=false })); } catch(Exception ex) { return Task.FromResult(JsonConvert.SerializeObject(new { success=false,caseId="M30",assertions,errorType=ex.GetType().Name,fullB220=false })); } finally { if(manager.CanResetForKVLoad) manager.ResetForKVLoad(); } } static Task RunActivities() { int assertions=0; void Check(bool value) { assertions++; if(!value) throw new InvalidOperationException("Activity assertion failed."); } var snapshot=new PlayerKVSchemaSnapshot("M15-M26-M27",Array.Empty(),Array.Empty(),new Dictionary(), CultureInfo.InvariantCulture,new DateTime(2026,9,23),0); var challenge=new FishingChallengeManager(); var pinball=new EventPinballDataManager(); var scratch=new EventScratchTicketDataManager(); try { var nullErrors = new PlayerKVSchemaDiagnostics(); var nullState = FishingChallengeManager.DecodeForKVLoad(new Dictionary { ["FishingChallengeData"] = " \r\n\tnull " }, snapshot, nullErrors); Check(nullState != null && nullState.IsPresent && nullState.IsExplicitNull && nullErrors.TotalErrors == 0); challenge.ReplaceFromKVDecoded(nullState); Check(challenge.fishingChallengeData == null && challenge.IsInactiveForKVValidation); Check(challenge.ChallengeState() == 0 && !challenge.ShouldOpen()); Check(JsonConvert.SerializeObject(challenge.fishingChallengeData) == "null"); { var raw=new Dictionary { ["FishingChallengeData"]=JsonConvert.SerializeObject(new { FirstOpen=true,EventId=11,RankTierLevel=11,Finished=11,RedirectID=11,Score=11,ProcessedScore=11,step=11,uiStep=11,index=11,StartTime="2026-09-23T00:00:00.0000000Z",EndTime="2026-09-23T00:00:00.0000000Z",eliminateNumber=new[] { 7,9 },robotAccountList=new[] { 7,9 },InflationRate=1.5f }) }; var state=FishingChallengeManager.DecodeForKVLoad(raw,snapshot,new PlayerKVSchemaDiagnostics()); Check(state!=null && state.IsPresent); challenge.ResetForKVLoad(); challenge.ReplaceFromKVDecoded(state); var actual=challenge.fishingChallengeData; Check(actual.FirstOpen==state.FirstOpen); Check(actual.EventId==state.EventId); Check(actual.RankTierLevel==state.RankTierLevel); Check(actual.Finished==state.Finished); Check(actual.RedirectID==state.RedirectID); Check(actual.Score==state.Score); Check(actual.ProcessedScore==state.ProcessedScore); Check(actual.step==state.step); Check(actual.uiStep==state.uiStep); Check(actual.index==state.index); Check(actual.StartTime==state.StartTime); Check(actual.EndTime==state.EndTime); Check(actual.eliminateNumber.SequenceEqual(state.eliminateNumber)); Check(actual.robotAccountList.SequenceEqual(state.robotAccountList)); Check(actual.InflationRate==state.InflationRate); challenge.ReplaceFromKVDecoded(state); Check(!ReferenceEquals(actual,challenge.fishingChallengeData)); actual.eliminateNumber.Clear(); Check(state.eliminateNumber.Count==2 && challenge.fishingChallengeData.eliminateNumber.Count==2); actual.robotAccountList.Clear(); Check(state.robotAccountList.Count==2 && challenge.fishingChallengeData.robotAccountList.Count==2); challenge.ResetForKVLoad(); challenge.ReplaceFromKVDecoded(nullState); Check(challenge.fishingChallengeData == null && challenge.IsInactiveForKVValidation); Check(challenge.ChallengeState() == 0 && !challenge.ShouldOpen()); Check(JsonConvert.SerializeObject(challenge.fishingChallengeData) == "null"); challenge.ReplaceFromKVDecoded(state); Check(challenge.fishingChallengeData != null && !state.IsExplicitNull); challenge.ResetForKVLoad(); challenge.ReplaceFromKVDecoded(FishingChallengeManager.DecodeForKVLoad(new Dictionary(),snapshot,new PlayerKVSchemaDiagnostics())); Check(challenge.fishingChallengeData==null && challenge.IsInactiveForKVValidation); } { var raw=new Dictionary { ["epbdk"]=JsonConvert.SerializeObject(new { EventID=11,PinballMainID=11,SpinPoint=11,Scores=11,Multiple=11,Token=11,ConsumedToken=11,RewardIndex=11,Force=1.5f,LuckyValue=1.5f,InflationRate=1.5f }) }; var state=EventPinballDataManager.DecodeForKVLoad(raw,snapshot,new PlayerKVSchemaDiagnostics()); Check(state!=null && state.IsPresent); pinball.ResetForKVLoad(); pinball.ReplaceFromKVDecoded(state); var actual=pinball.PinballData; Check(actual.EventID==state.EventID); Check(actual.PinballMainID==state.PinballMainID); Check(actual.SpinPoint==state.SpinPoint); Check(actual.Scores==state.Scores); Check(actual.Multiple==state.Multiple); Check(actual.Token==state.Token); Check(actual.ConsumedToken==state.ConsumedToken); Check(actual.RewardIndex==state.RewardIndex); Check(actual.Force==state.Force); Check(actual.LuckyValue==state.LuckyValue); Check(actual.InflationRate==state.InflationRate); pinball.ReplaceFromKVDecoded(state); Check(!ReferenceEquals(actual,pinball.PinballData)); pinball.ResetForKVLoad(); pinball.ReplaceFromKVDecoded(EventPinballDataManager.DecodeForKVLoad(new Dictionary(),snapshot,new PlayerKVSchemaDiagnostics())); Check(pinball.PinballData==null && pinball.IsInactiveForKVValidation); } { var raw=new Dictionary { ["estdk"]=JsonConvert.SerializeObject(new { EventID=11,ScratchMainID=11,ChainPackID=11,Token=11,ConsumedToken=11,RewardTaskConsumedToken=11,EventScratchRewardTaskID=11,EventScratchCardID=11,TotalConsumedToken=11,ChainPackProgress=11,magIndex=11,IsOver=true,LuckyValue=1.5f,InflationRate=1.5f,StampRewardList=new[] { 7,9 },StampRewardDict=new Dictionary { [7]=new { EventScratchCardID=8,Count=9 } } }) }; var state=EventScratchTicketDataManager.DecodeForKVLoad(raw,snapshot,new PlayerKVSchemaDiagnostics()); Check(state!=null && state.IsPresent); scratch.ResetForKVLoad(); scratch.ReplaceFromKVDecoded(state); var actual=scratch.ScratchTicketData; Check(actual.EventID==state.EventID); Check(actual.ScratchMainID==state.ScratchMainID); Check(actual.ChainPackID==state.ChainPackID); Check(actual.Token==state.Token); Check(actual.ConsumedToken==state.ConsumedToken); Check(actual.RewardTaskConsumedToken==state.RewardTaskConsumedToken); Check(actual.EventScratchRewardTaskID==state.EventScratchRewardTaskID); Check(actual.EventScratchCardID==state.EventScratchCardID); Check(actual.TotalConsumedToken==state.TotalConsumedToken); Check(actual.ChainPackProgress==state.ChainPackProgress); Check(actual.magIndex==state.magIndex); Check(actual.IsOver==state.IsOver); Check(actual.LuckyValue==state.LuckyValue); Check(actual.InflationRate==state.InflationRate); Check(actual.StampRewardList.SequenceEqual(state.StampRewardList)); Check(actual.StampRewardDict[7].EventScratchCardID==state.StampRewardDict[7].EventScratchCardID && actual.StampRewardDict[7].Count==state.StampRewardDict[7].Count); scratch.ReplaceFromKVDecoded(state); Check(!ReferenceEquals(actual,scratch.ScratchTicketData)); actual.StampRewardList.Clear(); Check(state.StampRewardList.Count==2 && scratch.ScratchTicketData.StampRewardList.Count==2); actual.StampRewardDict[7].Count=99; Check(state.StampRewardDict[7].Count==9 && scratch.ScratchTicketData.StampRewardDict[7].Count==9); scratch.ResetForKVLoad(); scratch.ReplaceFromKVDecoded(EventScratchTicketDataManager.DecodeForKVLoad(new Dictionary(),snapshot,new PlayerKVSchemaDiagnostics())); Check(scratch.ScratchTicketData==null && scratch.IsInactiveForKVValidation); } return Task.FromResult(JsonConvert.SerializeObject(new { success=true,caseId="M15-M26-M27",assertions,fullB220=false,sharedContainerTouched=false })); } catch(Exception ex) { return Task.FromResult(JsonConvert.SerializeObject(new { success=false,caseId="M15-M26-M27",assertions,errorType=ex.GetType().Name,fullB220=false })); } finally { challenge.ResetForKVLoad(); pinball.ResetForKVLoad(); scratch.ResetForKVLoad(); } } static Task RunBuffTrigger() { int assertions=0; void Check(bool value) { assertions++; if(!value) throw new InvalidOperationException("Buff/trigger assertion failed."); } var buffs=new BuffDataCenter(null); var triggers=new GameCore.TriggerPackData(null); var snapshot=new PlayerKVSchemaSnapshot("M06-M13",Array.Empty(),Array.Empty(),new Dictionary(), CultureInfo.InvariantCulture,new DateTime(2026,9,23),0); try { const string value="{\"SortID\":1,\"sourceID\":2,\"buffID\":3,\"isEnd\":false,\"buffEndTime\":\"2026-09-24T00:00:00.0000000Z\"}"; var state=BuffDataCenter.DecodeForKVLoad(new Dictionary { ["BuffDataList"]="{\"7\":"+value+"}",["TargetBuffDataList"]="["+value+"]", ["FishDailyBuff"]=value,["GlobalBuffDataList"]="{\"7\":"+value+"}" },snapshot,new PlayerKVSchemaDiagnostics()); buffs.ResetForKVLoad(); buffs.ReplaceFromKVDecoded(state); for(int kind=0;kind<4;kind++) { var actual=buffs.BuffForKVValidation(kind,kind==1 ? 0 : 7); Check(actual.SortID==1 && actual.sourceID==2 && actual.buffID==3 && !actual.isEnd && actual.buffEndTime==state.Daily.buffEndTime && !actual.isNew && actual.addDown==0); actual.buffID=99; actual.isNew=true; actual.addDown=9; } Check(state.Map[7].buffID==3 && state.Target[0].buffID==3 && state.Daily.buffID==3 && state.Global[7].buffID==3); buffs.showFishBuffIds.Add(new FishBuffTimeData { buffID=99 }); buffs.ResetForKVLoad(); buffs.ReplaceFromKVDecoded(BuffDataCenter.DecodeForKVLoad(new Dictionary(),snapshot,new PlayerKVSchemaDiagnostics())); Check(buffs.IsEmptyForKVValidation); var raw=new Dictionary { ["TriggerPackDataList"]="[{\"ID\":1,\"index\":2,\"lastID\":3,\"vipLevel\":4,\"mapID\":5,\"time\":\"2026-09-24\"}]", ["GiftVIPDData"]="{\"A\":{\"oldVipLevel\":1,\"curVipLevel\":2,\"timer\":3}}", ["FixedTimeShopDic"]="{\"7\":{\"id\":8,\"endTime\":\"2026-09-24\"}}" }; var packs=GameCore.TriggerPackData.DecodeForKVLoad(raw,new PlayerKVSchemaDiagnostics()); triggers.ResetForKVLoad(); triggers.ReplaceFromKVDecoded(packs); var pack=triggers.triggerPackList[0]; var gift=triggers.giftVIPDData["A"]; var fixedTime=triggers.FixedForKVValidation(7); Check(pack.ID==1 && pack.index==2 && pack.lastID==3 && pack.vipLevel==4 && pack.mapID==5 && pack.time=="2026-09-24"); Check(gift.oldVipLevel==1 && gift.curVipLevel==2 && gift.timer==3 && fixedTime.id==8 && fixedTime.endTime=="2026-09-24"); pack.ID=99; gift.timer=99; fixedTime.id=99; Check(packs.Packs[0].ID==1 && packs.Gifts["A"].timer==3 && packs.Fixed[7].id==8); triggers.ResetForKVLoad(); triggers.ReplaceFromKVDecoded(GameCore.TriggerPackData.DecodeForKVLoad(new Dictionary(),new PlayerKVSchemaDiagnostics())); Check(triggers.IsEmptyForKVValidation); return Task.FromResult(JsonConvert.SerializeObject(new { success=true,caseId="M06-M13",assertions,fullB220=false,sharedContainerTouched=false })); } catch(Exception ex) { return Task.FromResult(JsonConvert.SerializeObject(new { success=false,caseId="M06-M13",assertions,errorType=ex.GetType().Name,fullB220=false })); } finally { buffs.ResetForKVLoad(); triggers.ResetForKVLoad(); } } static Task RunOffers() { int assertions=0; void Check(bool value) { assertions++; if(!value) throw new InvalidOperationException("Offer assertion failed."); } var chains=new DataCenter.OfferChainsChestManager(); var disco=new DataCenter.OfferDiscoPartyManager(); var snapshot=new PlayerKVSchemaSnapshot("M16-M17",Array.Empty(),Array.Empty(),new Dictionary(), CultureInfo.InvariantCulture,new DateTime(2026,9,23),0); try { var text=JsonConvert.SerializeObject(new { FirstOpen=true,EventId=7,StartTime="2026-09-22T00:00:00.0000000Z",EndTime="2026-09-24T00:00:00.0000000Z", ItemId=-1,LotteryNum=5,Items=new[] { new { Id=1,Item=2,Number=3,Status=2 } },RewardList=new[] { 8,9 },Finished=true,ChestReward=10 }); { var state=DataCenter.OfferChainsChestManager.DecodeForKVLoad(new Dictionary { ["OfferChainsChestData"]=text },snapshot,new PlayerKVSchemaDiagnostics()); chains.ResetForKVLoad(); chains.ReplaceFromKVDecoded(state); var actual=chains.OfferChainsData; Check(actual.FirstOpen && actual.EventId==7 && actual.StartTime==state.StartTime && actual.EndTime==state.EndTime && actual.ItemId == -1 && actual.LotteryNum==5); Check(actual.Items.Count==1 && actual.Items[0].Id==1 && actual.Items[0].Item==2 && actual.Items[0].Number==3 && (int)actual.Items[0].Status==2 && actual.RewardList.SequenceEqual(new[] { 8,9 })); Check(actual.Finished && actual.ChestReward==10); actual.Items[0].Number=99; actual.RewardList.Clear(); Check(state.Items[0].Number==3 && state.RewardList.Count==2); chains.ReplaceFromKVDecoded(state); Check(!ReferenceEquals(actual,chains.OfferChainsData) && !ReferenceEquals(actual.Items[0],chains.OfferChainsData.Items[0]) && chains.OfferChainsData.Items[0].Number==3); chains.ValidIds.Add(7); var missing=DataCenter.OfferChainsChestManager.DecodeForKVLoad(new Dictionary(),snapshot,new PlayerKVSchemaDiagnostics()); chains.ResetForKVLoad(); chains.ReplaceFromKVDecoded(missing); Check(chains.OfferChainsData==null && chains.ValidIds.Count==0 && chains.IsInactiveForKVValidation); Check(chains.OfferChainsChestMain==null && chains.OfferChainsChestRewards==null); } { var state=DataCenter.OfferDiscoPartyManager.DecodeForKVLoad(new Dictionary { ["OfferDiscoPartyManager"]=text },snapshot,new PlayerKVSchemaDiagnostics()); disco.ResetForKVLoad(); disco.ReplaceFromKVDecoded(state); var actual=disco.OfferDiscoPartyData; Check(actual.FirstOpen && actual.EventId==7 && actual.StartTime==state.StartTime && actual.EndTime==state.EndTime && actual.ItemId == -1 && actual.LotteryNum==5); Check(actual.Items.Count==1 && actual.Items[0].Id==1 && actual.Items[0].Item==2 && actual.Items[0].Number==3 && (int)actual.Items[0].Status==2 && actual.RewardList.SequenceEqual(new[] { 8,9 })); actual.Items[0].Number=99; actual.RewardList.Clear(); Check(state.Items[0].Number==3 && state.RewardList.Count==2); disco.ReplaceFromKVDecoded(state); Check(!ReferenceEquals(actual,disco.OfferDiscoPartyData) && !ReferenceEquals(actual.Items[0],disco.OfferDiscoPartyData.Items[0]) && disco.OfferDiscoPartyData.Items[0].Number==3); disco.ValidIds.Add(7); disco.FreeTime=99; var missing=DataCenter.OfferDiscoPartyManager.DecodeForKVLoad(new Dictionary(),snapshot,new PlayerKVSchemaDiagnostics()); disco.ResetForKVLoad(); disco.ReplaceFromKVDecoded(missing); Check(disco.OfferDiscoPartyData==null && disco.ValidIds.Count==0 && disco.IsInactiveForKVValidation); Check(disco.OfferDiscoParty==null && disco.OfferDiscoPartyRewards==null && disco.FreeTime==1); } return Task.FromResult(JsonConvert.SerializeObject(new { success=true,caseId="M16-M17",assertions,fullB220=false,sharedContainerTouched=false })); } catch(Exception ex) { return Task.FromResult(JsonConvert.SerializeObject(new { success=false,caseId="M16-M17",assertions,errorType=ex.GetType().Name,fullB220=false })); } finally { chains.ResetForKVLoad(); disco.ResetForKVLoad(); } } static Task RunCamp() { int assertions=0; void Check(bool value) { assertions++; if (!value) throw new InvalidOperationException("Camp assertion failed."); } var camp=new CampDataMM(null,null); try { var a=CampDataMM.DecodeForKVLoad(new Dictionary { ["IsFix"]="true",["IsOpenSkyscraper"]="1",["CampDataDic"]="{\"CampId\":205}" },new PlayerKVSchemaDiagnostics()); camp.ResetForKVLoad(); camp.ReplaceFromKVDecoded(a); var actual=camp.SnapshotForKVValidation(); Check(actual.IsFix && actual.Skyscraper=="1" && actual.Values["CampId"]==205); camp.Id=205; camp.PreID=204; camp.SetCampAreaIndex("3"); camp.SetCampAreaStep("4"); camp.IsUpdataArea=true; var b=CampDataMM.DecodeForKVLoad(new Dictionary(),new PlayerKVSchemaDiagnostics()); camp.ResetForKVLoad(); camp.ReplaceFromKVDecoded(b); actual=camp.SnapshotForKVValidation(); Check(!actual.IsFix && actual.Skyscraper=="0" && actual.Values.Count==0); Check(camp.Id==101 && camp.PreID==0 && camp.AreaIndex==0 && camp.AreaStep==0 && camp.AreaId==0 && camp.AreaStepCount==0 && camp.TimelineId==0); Check(camp.AreaIcon==null && camp.constructionArea==null && camp.StepInfo==null && camp.AreaStepList==null && !camp.IsUpdataArea); Check(a.Values.Count==1); return Task.FromResult(JsonConvert.SerializeObject(new { success=true,caseId="M07",assertions,fullB220=false,sharedContainerTouched=false })); } catch(Exception ex) { return Task.FromResult(JsonConvert.SerializeObject(new { success=false,caseId="M07",assertions,errorType=ex.GetType().Name,fullB220=false })); } finally { camp.ResetForKVLoad(); } } static Task RunClub() { int assertions = 0; void Check(bool condition) { assertions++; if (!condition) throw new InvalidOperationException("Club assertion failed."); } var club = new ClubService(); var snapshot = new PlayerKVSchemaSnapshot("M14",Array.Empty(),Array.Empty(),new Dictionary(), CultureInfo.InvariantCulture,new DateTime(2026,9,23,0,0,0,DateTimeKind.Utc),0); try { club.OpenPanelType = 17; club.IsOpenClubJoined = true; club.internalIdToPlayerId[1] = "synthetic"; var unrelated = club.internalIdToPlayerId; var a = ClubService.DecodeForKVLoad(new Dictionary { ["ClubSignin"] = "2026-09-23T00:00:00.0000000Z", ["LureDonateData"] = "{\"DonateCountTime\":\"2026-09-22T00:00:00.0000000Z\",\"ClubLureDonateCount\":7,\"ClubLureRequestCDEnd\":\"2026-09-24T00:00:00.0000000Z\"}", ["IsOpenClubRed"] = "1" },snapshot,new PlayerKVSchemaDiagnostics()); club.ResetForKVLoad(); club.ReplaceFromKVDecoded(a); var actual = club.SnapshotForKVValidation(); Check(actual.SignTime == a.SignTime && actual.DonateTime == a.DonateTime && actual.DonateCount == 7 && actual.RequestEnd == a.RequestEnd && !actual.OpenRed); var missing = ClubService.DecodeForKVLoad(new Dictionary(),snapshot,new PlayerKVSchemaDiagnostics()); club.ResetForKVLoad(); club.ReplaceFromKVDecoded(missing); actual = club.SnapshotForKVValidation(); Check(actual.SignTime == null && actual.DonateTime == default && actual.RequestEnd == default && actual.DonateCount == 0 && actual.OpenRed); Check(ReferenceEquals(unrelated,club.internalIdToPlayerId) && unrelated.Count == 1 && club.OpenPanelType == 17 && club.IsOpenClubJoined); return Task.FromResult(JsonConvert.SerializeObject(new { success=true,caseId="M14",assertions,fullB220=false,sharedContainerTouched=false })); } catch (Exception ex) { return Task.FromResult(JsonConvert.SerializeObject(new { success=false,caseId="M14",assertions,errorType=ex.GetType().Name,fullB220=false })); } finally { club.ResetForKVLoad(); } } static Task RunCollections() { int assertions = 0; void Check(bool condition) { assertions++; if (!condition) throw new InvalidOperationException("Collection assertion failed."); } var box = new FishingBoxData(); var guide = new GameCore.GuideDataCenter(null,null); var signin = new GameCore.SigninData(null); try { var source = new Dictionary { [1] = 7 }; var boxes = new PlayerKVIntMapState(source); source.Clear(); box.ResetForKVLoad(); box.ReplaceFromKVDecoded(boxes); Check(box.FishingBoxOpenNum.Count == 1 && box.FishingBoxOpenNum[1] == 7); box.FishingBoxOpenNum[1] = 8; box.FishingNewBoxs[2] = 9; Check(boxes.Values[1] == 7); var previousBoxes = box.FishingNewBoxs; var previousCounts = box.FishingBoxOpenNum; var missingBoxes = FishingBoxData.DecodeForKVLoad(new Dictionary(),new Dictionary(),new PlayerKVSchemaDiagnostics()); box.ResetForKVLoad(); box.ReplaceFromKVDecoded(missingBoxes); Check(box.FishingNewBoxs.Count == 0 && box.FishingBoxOpenNum.Count == 0); Check(!ReferenceEquals(previousBoxes,box.FishingNewBoxs) && !ReferenceEquals(previousCounts,box.FishingBoxOpenNum)); var guides = GameCore.GuideDataCenter.DecodeForKVLoad(new Dictionary { ["GuideSaveDataNew"] = "A|3|1;B|4|0" },new PlayerKVSchemaDiagnostics()); guide.ResetForKVLoad(); guide.ReplaceFromKVDecoded(guides); Check(guide.GuideSaveDataDic.Count == 2 && guide.GuideSaveDataDic["A"].guideSubIndex == 3 && guide.GuideSaveDataDic["A"].isFinish && guide.GuideSaveDataDic["A"].groupName == "A"); guide.GuideSaveDataDic["A"].guideSubIndex = 99; Check(guides.Values["A"].Index == 3); guide.curSaveData = guide.GuideSaveDataDic["A"]; guide.popupPanelNode = "synthetic"; guide.CurPanelName = "synthetic"; guide.curDefine = new GuidanceDefine(); guide.curGroupDefine = new GuidanceGroupDefine(); var definitions = new GuidanceGroupDefine[0]; guide.guidanceGroupDefines = definitions; var missingGuides = GameCore.GuideDataCenter.DecodeForKVLoad(new Dictionary(),new PlayerKVSchemaDiagnostics()); guide.ResetForKVLoad(); guide.ReplaceFromKVDecoded(missingGuides); Check(guide.GuideSaveDataDic.Count == 0 && guide.curSaveData == null && guide.curDefine == null && guide.curGroupDefine == null && guide.popupPanelNode == null && guide.CurPanelName == null); Check(ReferenceEquals(guide.guidanceGroupDefines,definitions)); var sign = GameCore.SigninData.DecodeForKVLoad(new Dictionary { ["MonthlySignEvent"] = "{\"index\":5,\"dailySignEvent\":{\"ID\":7,\"index\":2,\"lastID\":-1,\"time\":\"2026-09-23\"}}" },new PlayerKVSchemaDiagnostics()); signin.ResetForKVLoad(); signin.ReplaceFromKVDecoded(sign); var actual = signin.monthlySignEvent; Check(actual.index == 5 && actual.dailySignEvent.ID == 7 && actual.dailySignEvent.index == 2 && actual.dailySignEvent.lastID == -1 && actual.dailySignEvent.time == "2026-09-23"); actual.dailySignEvent.ID = 99; Check(sign.DailyId == 7); signin.ReplaceFromKVDecoded(sign); Check(!ReferenceEquals(actual,signin.monthlySignEvent) && !ReferenceEquals(actual.dailySignEvent,signin.monthlySignEvent.dailySignEvent) && signin.monthlySignEvent.dailySignEvent.ID == 7); var missingSign = GameCore.SigninData.DecodeForKVLoad(new Dictionary(),new PlayerKVSchemaDiagnostics()); signin.ResetForKVLoad(); signin.ReplaceFromKVDecoded(missingSign); Check(signin.monthlySignEvent == null); return Task.FromResult(JsonConvert.SerializeObject(new { success=true,caseId="M09-M10-M12",assertions,fullB220=false,sharedContainerTouched=false })); } catch (Exception ex) { return Task.FromResult(JsonConvert.SerializeObject(new { success=false,caseId="M09-M10-M12",assertions,errorType=ex.GetType().Name,fullB220=false })); } finally { box.ResetForKVLoad(); guide.ResetForKVLoad(); guide.Dispose(); signin.ResetForKVLoad(); } } // A static access shim only; never added as a component or instantiated. sealed class ValidationContextAccess : asap.core.GContext { protected override Task SetupAsync() => throw new InvalidOperationException("Not a gameplay context."); internal static void Use(asap.core.Container value) { container = value; } internal static asap.core.IEventAggregator Events { get => eventAggregator; set => eventAggregator = value; } } static bool running; static Task RunD3(bool includeProgress = false,bool includeCore = false,bool includeShop = false,bool includeEventBase = false) { if (running || asap.core.GContext.Ready || UnityEngine.Resources.FindObjectsOfTypeAll().Length != 0) throw new InvalidOperationException("Game context is active."); running = true; var originalContainer = asap.core.GContext.container; var originalAggregator = ValidationContextAccess.Events; var isolated = new asap.core.Container(); PiggyBankPackData piggy = null; BargainPackData bargain = null; EventShootingRangeData shooting = null; ThanksGivingPackData progress = null; ChainPackWithProgressMigrationData migration = null; EventPackData eventPack = null; EventPinataPackDataManager pinata = null; GameCore.FishingEventData eventBase = null; GameCore.PlayerShopData shop = null; GameCore.PlayerData player = null; GameCore.SmallGameData small = null; int assertions = 0; bool success = false, restored = false; string errorType = null; void Check(bool condition) { assertions++; if (!condition) throw new InvalidOperationException("D3 assertion failed."); } try { // Read local tables into a private graph. No active account, network, runtime init or table writes. var directory = System.IO.Path.GetFullPath(System.IO.Path.Combine(UnityEngine.Application.dataPath, "../../cfgData")); var tables = new cfg.Tables(name => SimpleJSON.JSON.Parse(System.IO.File.ReadAllText(System.IO.Path.Combine(directory, name + ".json")))); var packTypes = tables.TbEventPackManager.DataMap.ToDictionary(p => p.Key, p => p.Value.PackType); int bargainId = packTypes.First(p => p.Value == 3).Key; var snapshot = new PlayerKVSchemaSnapshot("local-D3", Array.Empty(), Array.Empty(), new Dictionary(), CultureInfo.InvariantCulture, new DateTime(2026,9,23,0,0,0,DateTimeKind.Utc),0,packTypes); Check(snapshot.HasPack(4055001)); ValidationContextAccess.Use(isolated); ValidationContextAccess.Events = new asap.core.EventAggregator(); Check(asap.core.ContainerExtensions.RegisterInstance(isolated, tables)); Check(ReferenceEquals(asap.core.ContainerExtensions.Resolve(isolated), tables)); piggy = new PiggyBankPackData(); bargain = new BargainPackData(); shooting = new EventShootingRangeData(); if(includeEventBase) { eventBase=new GameCore.FishingEventData(tables); var progressReference=eventBase.ThanksGivingPackData; var eventSnapshot=new PlayerKVSchemaSnapshot("local-M05",Array.Empty(),Array.Empty(),new Dictionary(),CultureInfo.InvariantCulture,snapshot.NowUtc,tables.TbGlobalConfig.InitWheelTicket,globalTimeOffset:tables.TbGlobalConfig.GlobalTimeOffset); var raw=new Dictionary { ["LimitedTimeEventDic"]="{\"1\":{\"id\":2,\"duration\":60.123456789,\"startTime\":\"2026-09-23T00:00:00.0000000Z\"},\"3\":{\"id\":4,\"duration\":60,\"startTime\":\"2026-09-22T00:00:00.0000000Z\"}}", ["FixedTimeEventDic"]="{\"5\":{\"id\":6,\"endTime\":\"raw\"}}", ["ETDropAfterDropDic"]="{\"1\":2,\"3\":4}",["RankTargetCount"]="{\"5\":6,\"7\":8}", ["WheelLevelCount"]="{\"9\":10}",["TurntableRoll"]="11",["WheelOperateCount"]="12",["AllWheelOperateCount"]="13",["WheelLevelFreeCount"]="14",["PVPToken"]="15" }; var state=GameCore.FishingEventData.DecodeForKVLoad(raw,new Dictionary{["ct"]="1:2",["rt"]="5:6"},eventSnapshot,new PlayerKVSchemaDiagnostics()); Check(state!=null); eventBase.ResetForKVLoad(); eventBase.ReplaceFromKVDecoded(state); var actual=eventBase.SnapshotForKVValidation(); Check(actual.Limited.Count==1 && actual.Limited[1].Id==2 && actual.Limited[1].Duration==60.123456789 && actual.Fixed[5].id==6 && actual.Fixed[5].endTime=="raw"); Check(actual.Target.Count==1 && actual.Target[1]==2 && actual.Rank.Count==1 && actual.Rank[5]==6 && actual.Wheel[9]==10); Check(actual.Coupon==11 && actual.Operate==12 && actual.AllOperate==13 && actual.Free==14 && actual.PvpToken==15 && eventBase.IsInactiveForKVValidation); eventBase.MutateCollectionsForKVValidation(); Check(state.Limited[1].Duration==60.123456789 && state.Fixed[5].id==6 && state.Target.Count==1 && state.Rank.Count==1 && state.Wheel.Count==1); eventBase.ResetForKVLoad(); eventBase.ReplaceFromKVDecoded(GameCore.FishingEventData.DecodeForKVLoad(new Dictionary(),new Dictionary(),eventSnapshot,new PlayerKVSchemaDiagnostics())); actual=eventBase.SnapshotForKVValidation(); Check(actual.Limited.Count==0 && actual.Fixed.Count==0 && actual.Target.Count==0 && actual.Rank.Count==0 && actual.Wheel.Count==0); Check(actual.Coupon==tables.TbGlobalConfig.InitWheelTicket && actual.Operate==0 && actual.AllOperate==0 && actual.Free==0 && actual.PvpToken==0 && eventBase.IsInactiveForKVValidation); Check(ReferenceEquals(progressReference,eventBase.ThanksGivingPackData)); } if(includeShop) { shop=new GameCore.PlayerShopData(); var couponReference=shop.CouponData; var daily="{\"ID\":1,\"index\":2,\"lastID\":3,\"time\":\"raw\"}"; var raw=new Dictionary { ["ShopPackBuyCount"]="{\"x\":1}",["ShopAllPackBuyCount"]="{\"2\":3}",["ShopGiftBuyCount"]="{\"4\":5}", ["Voucher"]="6",["ChainPackDataNew"]=daily,["TurntableAdData"]=daily, ["VIPAdPackData"]="{\"ID\":7,\"index\":8,\"day\":9,\"cdTime\":\"raw\",\"isOpen\":true}", ["BuyData"]="[1.5,2.5]",["AdvertAllCount"]="{\"-2\":10}",["DiamondShopRefreshCount"]="11", ["DiamondShopRefreshTime"]="2026-09-23T00:00:00.0000000Z", ["DiamondShopItemRecords"]="{\"12\":{\"ItemID\":13,\"Discount\":0.5,\"hasBought\":1}}", ["BlackFridayCouponDate"]="2026,9,23,1,2,3,14,15,1" }; var state=GameCore.PlayerShopData.DecodeForKVLoad(raw,snapshot,new PlayerKVSchemaDiagnostics()); Check(state!=null); shop.ResetForKVLoad(); shop.ReplaceFromKVDecoded(state); var actual=shop.SnapshotForKVValidation(); Check(actual.Daily["x"]==1 && actual.All[2]==3 && actual.Gift[4]==5 && actual.Voucher==6); Check(actual.Chain.Id==1 && actual.Chain.Index==2 && actual.Chain.LastId==3 && actual.Chain.Time=="raw" && actual.Turntable.LastId==3); Check(actual.Vip.Id==7 && actual.Vip.Index==8 && actual.Vip.Day==9 && actual.Vip.Time=="raw" && actual.Vip.Open); Check(actual.Advert[-2]==10 && actual.Buy.SequenceEqual(new[] {1.5f,2.5f}) && actual.RefreshCount==11 && actual.RefreshTime==snapshot.NowUtc); Check(actual.Items[12].Id==13 && actual.Items[12].Discount==.5f && actual.Items[12].Bought==1); Check(ReferenceEquals(couponReference,shop.CouponData) && actual.Coupon.ActivateTime==new DateTime(2026,9,23,1,2,3) && actual.Coupon.EventId==14 && actual.Coupon.RedirectId==15 && actual.Coupon.Bought); shop.shopPackDailyBuyCount.Clear(); shop.shopAllPackBuyCount.Clear(); shop.shopGiftBuyCount.Clear(); shop.ChainPackData.index=99; shop.TurntableAdData.index=99; shop.VIPAdPackData.index=99; shop.MutateCollectionsForKVValidation(); Check(state.Daily.Count==1 && state.All.Count==1 && state.Gift.Count==1 && state.Advert.Count==1 && state.Buy.Count==2 && state.Items[12].Discount==.5f && state.Chain.Index==2 && state.Turntable.Index==2 && state.Vip.Index==8); shop.ResetForKVLoad(); shop.ReplaceFromKVDecoded(GameCore.PlayerShopData.DecodeForKVLoad(new Dictionary(),snapshot,new PlayerKVSchemaDiagnostics())); actual=shop.SnapshotForKVValidation(); Check(actual.Daily.Count==0 && actual.All.Count==0 && actual.Gift.Count==0 && actual.Buy.Count==0 && actual.Items.Count==0 && actual.Advert.Count==0); Check(actual.Voucher==0 && actual.RefreshCount==0 && actual.RefreshTime==default && actual.Chain==null && actual.Turntable==null && actual.Vip==null); Check(ReferenceEquals(couponReference,shop.CouponData) && actual.Coupon.ActivateTime==default && actual.Coupon.EventId==0 && actual.Coupon.RedirectId==0 && !actual.Coupon.Bought); } if(includeCore) { int rod=tables.TbRodData.DataList[0].ID; var coreSnapshot=new PlayerKVSchemaSnapshot("local-M01-M02",tables.TbRodData.DataMap.Keys,tables.TbMapData.DataMap.Keys, new Dictionary(),CultureInfo.InvariantCulture,snapshot.NowUtc,0); var slots=new[] { "3","18446744073709551615","4","5","6","7",rod.ToString(CultureInfo.InvariantCulture), "2026-09-23T00:00:00.0000000Z","-1","8","18446744073709551614","9","0","0","10","11","12","13","14" }; var modern=new Dictionary { ["newData"]=string.Join(",",slots),["vipL"]="-1,5|0,6|1,7" }; var raw=new Dictionary { ["NewRodList"]="[7,8]",["SeasonId"]="9",["IsOpenAppraise"]="1",["InfiniteBuildingData"]="raw" }; var state=GameCore.PlayerData.DecodeForKVLoad(raw,modern,coreSnapshot,new PlayerKVSchemaDiagnostics()); Check(state!=null); player=new GameCore.PlayerData(tables,null); small=new GameCore.SmallGameData(tables,null,null); player.ResetForKVLoad(); small.ResetForKVLoad(); player.ReplaceFromKVDecoded(state); small.ReplaceFromKVDecoded(state.Core); var actual=player.SnapshotForKVValidation(); for(int i=0;i<16;i++) { object value=i==1 || i==10 ? (object)state.Core.Unsigned(i) : i==7 ? (object)state.Core.EnergyTime : state.Core.Integer(i); Check(actual.Core.Matches(i,value)); } Check(actual.NewRods.SequenceEqual(new[] { 7,8 }) && actual.Season==9 && actual.Appraise && actual.Building=="raw" && actual.VipScores[-1]==5 && actual.VipScores[0]==6 && actual.VipScores[1]==7); Check(small.HeistGameCount==12 && small.BombGameCount==13 && small.FishHuntGameCount==14); player.newRodList.Clear(); Check(state.NewRods.Count==2); small.opponentItemDatas.Add(null); small.opponentFriendItemDatas.Add(null); small.opponentNotFriendItemDatas.Add(null); small.opponentFriend.Add(null); small.powerData["synthetic"]=1; small.RobotMapId=99; small.nextRobotMapId=98; modern.Remove("vipL"); var b=GameCore.PlayerData.DecodeForKVLoad(new Dictionary(),modern,coreSnapshot,new PlayerKVSchemaDiagnostics()); player.ResetForKVLoad(); small.ResetForKVLoad(); player.ReplaceFromKVDecoded(b); small.ReplaceFromKVDecoded(b.Core); actual=player.SnapshotForKVValidation(); Check(actual.NewRods.Count==0 && actual.VipScores.Count==0 && actual.Season==0 && !actual.Appraise && actual.Building=="0"); Check(player.InfiniteBuildingLevel=="0"); // Actual leaderboard consumer after missing-key Replace. foreach(var building in new[]{"", "23,7"}) { var existing=new Dictionary{{"InfiniteBuildingData",building}}; var saved=GameCore.PlayerData.DecodeForKVLoad(existing,modern,coreSnapshot,new PlayerKVSchemaDiagnostics()); player.ResetForKVLoad(); player.ReplaceFromKVDecoded(saved); Check(player.InfiniteBuildingData==building && player.InfiniteBuildingLevel==building.Split(',')[0]); } var nullErrors=new PlayerKVSchemaDiagnostics(); Check(GameCore.PlayerData.DecodeForKVLoad(new Dictionary{{"InfiniteBuildingData",null}},modern,coreSnapshot,nullErrors)==null && nullErrors.TotalErrors>0); Check(small.IsOpponentEmptyForKVValidation && player.IsDerivedEmptyForKVValidation); player.ResetForKVLoad(); small.ResetForKVLoad(); actual=player.SnapshotForKVValidation(); for(int i=0;i<16;i++) { object value=i==1 || i==10 ? (object)0UL : i==7 ? (object)DateTime.MinValue : 0; Check(actual.Core.Matches(i,value)); } Check(small.HeistGameCount==0 && small.BombGameCount==0 && small.FishHuntGameCount==0 && player.PlayerDataDic.Count==0 && player.InfiniteBuildingData==null); } { var raw = new Dictionary { ["PiggyBankPackData"] = JsonConvert.SerializeObject(new { currentEventID = 11, purchaseCount = 11, VIPLvlWhenEventActivate = 11, savingProgress = 11, redirectID = 0, doNeedTrigger = true, isTriggered = true, inflation = 1.5f }) }; var errors = new PlayerKVSchemaDiagnostics(); var a = PiggyBankPackData.DecodeForKVLoad(raw, snapshot, errors); Check(a != null && a.IsPresent && errors.TotalErrors == 0); piggy.ResetForKVLoad(); piggy.ReplaceFromKVDecoded(a); var actual = piggy.SnapshotForKVValidation(); Check(piggy.KVLoadPresent && actual.IsPresent); Check(actual.currentEventID == a.currentEventID); Check(actual.purchaseCount == a.purchaseCount); Check(actual.VIPLvlWhenEventActivate == a.VIPLvlWhenEventActivate); Check(actual.savingProgress == a.savingProgress); Check(actual.redirectID == a.redirectID); Check(actual.doNeedTrigger == a.doNeedTrigger); Check(actual.isTriggered == a.isTriggered); Check(actual.inflation == a.inflation); var missing = PiggyBankPackData.DecodeForKVLoad(new Dictionary(), snapshot, new PlayerKVSchemaDiagnostics()); piggy.ResetForKVLoad(); piggy.ReplaceFromKVDecoded(missing); actual = piggy.SnapshotForKVValidation(); Check(!piggy.KVLoadPresent && !actual.IsPresent); Check(actual.currentEventID == 0); Check(actual.purchaseCount == 0); Check(actual.VIPLvlWhenEventActivate == 0); Check(actual.savingProgress == 0); Check(actual.redirectID == 0); Check(actual.doNeedTrigger == false); Check(actual.isTriggered == false); Check(actual.inflation == 0); piggy.ReplaceFromKVDecoded(a); actual = piggy.SnapshotForKVValidation(); Check(piggy.KVLoadPresent && actual.IsPresent); Check(actual.currentEventID == a.currentEventID); Check(actual.purchaseCount == a.purchaseCount); Check(actual.VIPLvlWhenEventActivate == a.VIPLvlWhenEventActivate); Check(actual.savingProgress == a.savingProgress); Check(actual.redirectID == a.redirectID); Check(actual.doNeedTrigger == a.doNeedTrigger); Check(actual.isTriggered == a.isTriggered); Check(actual.inflation == a.inflation); raw["PiggyBankPackData"] = "null"; Check(PiggyBankPackData.DecodeForKVLoad(raw,snapshot,new PlayerKVSchemaDiagnostics()) == null); Check(piggy.KVLoadPresent); // Rejected decode cannot mutate the live object. piggy.ResetForKVLoad(); Check(!piggy.KVLoadPresent); } { var raw = new Dictionary { ["GeneralEventPackData"] = JsonConvert.SerializeObject(new { currentEventID = 11, redirectID = bargainId, VIPLvlWhenEventActivate = 11, purchaseCount = 11, savingProgress = 11, discountLvl = 11, visualProgress = 11, visualDiscountLvl = 11, rodID = 11, lastPackIDBought = 11, packID = 11, doNeedTrigger = true, isTriggered = true, inflation = 1.5f, selections = new[] { 7, 9 } }) }; var errors = new PlayerKVSchemaDiagnostics(); var a = BargainPackData.DecodeForKVLoad(raw, snapshot, errors); Check(a != null && a.IsPresent && errors.TotalErrors == 0); bargain.ResetForKVLoad(); bargain.ReplaceFromKVDecoded(a); var actual = bargain.SnapshotForKVValidation(); Check(bargain.KVLoadPresent && actual.IsPresent); Check(actual.currentEventID == a.currentEventID); Check(actual.redirectID == a.redirectID); Check(actual.VIPLvlWhenEventActivate == a.VIPLvlWhenEventActivate); Check(actual.purchaseCount == a.purchaseCount); Check(actual.savingProgress == a.savingProgress); Check(actual.discountLvl == a.discountLvl); Check(actual.visualProgress == a.visualProgress); Check(actual.visualDiscountLvl == a.visualDiscountLvl); Check(actual.rodID == a.rodID); Check(actual.lastPackIDBought == a.lastPackIDBought); Check(actual.packID == a.packID); Check(actual.doNeedTrigger == a.doNeedTrigger); Check(actual.isTriggered == a.isTriggered); Check(actual.inflation == a.inflation); Check(actual.selections.SequenceEqual(a.selections)); var missing = BargainPackData.DecodeForKVLoad(new Dictionary(), snapshot, new PlayerKVSchemaDiagnostics()); bargain.ResetForKVLoad(); bargain.ReplaceFromKVDecoded(missing); actual = bargain.SnapshotForKVValidation(); Check(!bargain.KVLoadPresent && !actual.IsPresent); Check(actual.currentEventID == 0); Check(actual.redirectID == 0); Check(actual.VIPLvlWhenEventActivate == 0); Check(actual.purchaseCount == 0); Check(actual.savingProgress == 0); Check(actual.discountLvl == 0); Check(actual.visualProgress == 0); Check(actual.visualDiscountLvl == 0); Check(actual.rodID == 0); Check(actual.lastPackIDBought == 0); Check(actual.packID == 0); Check(actual.doNeedTrigger == false); Check(actual.isTriggered == false); Check(actual.inflation == 0); Check(actual.selections.Count == 0); bargain.ReplaceFromKVDecoded(a); actual = bargain.SnapshotForKVValidation(); Check(bargain.KVLoadPresent && actual.IsPresent); Check(actual.currentEventID == a.currentEventID); Check(actual.redirectID == a.redirectID); Check(actual.VIPLvlWhenEventActivate == a.VIPLvlWhenEventActivate); Check(actual.purchaseCount == a.purchaseCount); Check(actual.savingProgress == a.savingProgress); Check(actual.discountLvl == a.discountLvl); Check(actual.visualProgress == a.visualProgress); Check(actual.visualDiscountLvl == a.visualDiscountLvl); Check(actual.rodID == a.rodID); Check(actual.lastPackIDBought == a.lastPackIDBought); Check(actual.packID == a.packID); Check(actual.doNeedTrigger == a.doNeedTrigger); Check(actual.isTriggered == a.isTriggered); Check(actual.inflation == a.inflation); Check(actual.selections.SequenceEqual(a.selections)); var listObject = Newtonsoft.Json.Linq.JObject.Parse(raw["GeneralEventPackData"]); PlayerKVBargainState nullSnapshot = null; foreach (var selection in new Newtonsoft.Json.Linq.JToken[] { Newtonsoft.Json.Linq.JValue.CreateNull(), new Newtonsoft.Json.Linq.JArray(), new Newtonsoft.Json.Linq.JArray(7,9) }) { listObject["selections"] = selection; raw["GeneralEventPackData"] = listObject.ToString(Newtonsoft.Json.Formatting.None); errors = new PlayerKVSchemaDiagnostics(); var decoded = BargainPackData.DecodeForKVLoad(raw, snapshot, errors); Check(decoded != null && decoded.IsPresent && errors.TotalErrors == 0); bargain.ReplaceFromKVDecoded(decoded); var captured = bargain.SnapshotForKVValidation(); Check(bargain.KVLoadPresent && Same(decoded, captured)); // Fixed field read of the value SaveData serializes; never call the network save. var live = (GeneralEventPackData)typeof(BargainPackData).GetField("_data", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(bargain); var serialized = Newtonsoft.Json.Linq.JObject.Parse(JsonConvert.SerializeObject(live)); Check(Newtonsoft.Json.Linq.JToken.DeepEquals(serialized["selections"], selection)); if (selection.Type == Newtonsoft.Json.Linq.JTokenType.Null) { Check(live.selections == null && captured.selections == null); nullSnapshot = captured; var different = (Newtonsoft.Json.Linq.JObject)listObject.DeepClone(); different["purchaseCount"] = decoded.purchaseCount + 1; var differentState = BargainPackData.DecodeForKVLoad(new Dictionary { ["GeneralEventPackData"] = different.ToString(Newtonsoft.Json.Formatting.None) }, snapshot, new PlayerKVSchemaDiagnostics()); Check(differentState != null && !Same(captured, differentState)); } else { Check(captured.selections != null && !Same(nullSnapshot, captured) && !Same(captured, nullSnapshot)); Check(!ReferenceEquals(live.selections, decoded.selections) && !ReferenceEquals(live.selections, captured.selections)); Check(!ReferenceEquals(captured.selections, bargain.SnapshotForKVValidation().selections)); if (live.selections.Count > 0) { live.selections[0] = 99; Check(decoded.selections[0] == 7 && captured.selections[0] == 7); bargain.ReplaceFromKVDecoded(decoded); Check(Same(decoded, bargain.SnapshotForKVValidation())); } } } Check(bargain.SelectionsAreIndependentForKVValidation()); Check(nullSnapshot != null && nullSnapshot.selections == null); bargain.ReplaceFromKVDecoded(a); raw["GeneralEventPackData"] = "null"; Check(BargainPackData.DecodeForKVLoad(raw,snapshot,new PlayerKVSchemaDiagnostics()) == null); Check(bargain.KVLoadPresent); // Rejected decode cannot mutate the live object. bargain.ResetForKVLoad(); Check(!bargain.KVLoadPresent); } { var raw = new Dictionary { ["EventShootingRangeData"] = JsonConvert.SerializeObject(new { EventId = 11, CycleId = 11, RedirectId = 11, AmmoCount = 11, CurrentStageIdx = 11, ChainProgress = 11, RewardFlag = long.MaxValue, TargetFlag = long.MinValue, HasBeenOpened = true, Inflation = 1.5f }) }; var errors = new PlayerKVSchemaDiagnostics(); var a = EventShootingRangeData.DecodeForKVLoad(raw, snapshot, errors); Check(a != null && a.IsPresent && errors.TotalErrors == 0); shooting.ResetForKVLoad(); shooting.ReplaceFromKVDecoded(a); var actual = shooting.SnapshotForKVValidation(); Check(shooting.KVLoadPresent && actual.IsPresent); Check(actual.EventId == a.EventId); Check(actual.CycleId == a.CycleId); Check(actual.RedirectId == a.RedirectId); Check(actual.AmmoCount == a.AmmoCount); Check(actual.CurrentStageIdx == a.CurrentStageIdx); Check(actual.ChainProgress == a.ChainProgress); Check(actual.RewardFlag == a.RewardFlag); Check(actual.TargetFlag == a.TargetFlag); Check(actual.HasBeenOpened == a.HasBeenOpened); Check(actual.Inflation == a.Inflation); var missing = EventShootingRangeData.DecodeForKVLoad(new Dictionary(), snapshot, new PlayerKVSchemaDiagnostics()); shooting.ResetForKVLoad(); shooting.ReplaceFromKVDecoded(missing); actual = shooting.SnapshotForKVValidation(); Check(!shooting.KVLoadPresent && !actual.IsPresent); Check(actual.EventId == 0); Check(actual.CycleId == 0); Check(actual.RedirectId == 0); Check(actual.AmmoCount == 0); Check(actual.CurrentStageIdx == 0); Check(actual.ChainProgress == 0); Check(actual.RewardFlag == 0); Check(actual.TargetFlag == 0); Check(actual.HasBeenOpened == false); Check(actual.Inflation == 0); shooting.ReplaceFromKVDecoded(a); actual = shooting.SnapshotForKVValidation(); Check(shooting.KVLoadPresent && actual.IsPresent); Check(actual.EventId == a.EventId); Check(actual.CycleId == a.CycleId); Check(actual.RedirectId == a.RedirectId); Check(actual.AmmoCount == a.AmmoCount); Check(actual.CurrentStageIdx == a.CurrentStageIdx); Check(actual.ChainProgress == a.ChainProgress); Check(actual.RewardFlag == a.RewardFlag); Check(actual.TargetFlag == a.TargetFlag); Check(actual.HasBeenOpened == a.HasBeenOpened); Check(actual.Inflation == a.Inflation); raw["EventShootingRangeData"] = "null"; Check(EventShootingRangeData.DecodeForKVLoad(raw,snapshot,new PlayerKVSchemaDiagnostics()) == null); Check(shooting.KVLoadPresent); // Rejected decode cannot mutate the live object. shooting.ResetForKVLoad(); Check(!shooting.KVLoadPresent); } // Present zero values remain distinguishable from Missing (with required redirect config). piggy.ReplaceFromKVDecoded(new PlayerKVPiggyBankState(true,0,0,0,0,4055001,false,false,0)); bargain.ReplaceFromKVDecoded(new PlayerKVBargainState(true,0,bargainId,0,0,0,0,0,0,0,0,0,false,false,0,new int[0])); shooting.ReplaceFromKVDecoded(new PlayerKVShootingRangeState(true,0,0,0,0,0,0,0,0,false,0)); Check(piggy.KVLoadPresent && bargain.KVLoadPresent && shooting.KVLoadPresent); Check(bargain.SelectionsAreIndependentForKVValidation()); if (includeProgress) { var pinataTarget=new EventPinataPackData(); Check(asap.core.ContainerExtensions.RegisterInstance(isolated,pinataTarget)); pinata=EventPinataPackDataManager.CreateForKVLoad(); var pinataState=EventPinataPackDataManager.DecodeForKVLoad(new Dictionary { ["EventPinataPackData"]="{\"EventID\":1,\"PinataMainID\":2,\"PurchasedCount\":3}" },new PlayerKVSchemaDiagnostics()); pinata.ResetForKVLoad(); pinata.ReplaceFromKVDecoded(pinataState); Check(ReferenceEquals(pinataTarget,asap.core.ContainerExtensions.Resolve(isolated)) && pinataTarget.EventID==1 && pinataTarget.PinataMainID==2 && pinataTarget.PurchasedCount==3); pinata.ResetForKVLoad(); pinata.ReplaceFromKVDecoded(EventPinataPackDataManager.DecodeForKVLoad(new Dictionary(),new PlayerKVSchemaDiagnostics())); Check(ReferenceEquals(pinataTarget,asap.core.ContainerExtensions.Resolve(isolated)) && pinataTarget.EventID==0 && pinataTarget.PinataMainID==0 && pinataTarget.PurchasedCount==0); eventPack=new EventPackData(); var eventSnapshot=new PlayerKVSchemaSnapshot("local-M20",Array.Empty(),Array.Empty(),new Dictionary(), CultureInfo.InvariantCulture,snapshot.NowUtc,0,packTypes, tables.TbFishingEvent.DataMap.ToDictionary(p=>p.Key,p=>new PlayerKVEventConfig(p.Value.RedirectID,(p.Value.TimeDefinition as cfg.LimitedTime)?.EndTime))); var pack=EventPackData.DecodeForKVLoad(new Dictionary { ["EventPackData"]="{\"currentEventID\":0,\"purchaseCount\":7,\"VIPLvlWhenEventActivate\":8,\"redirectID\":9}" }, eventSnapshot,new PlayerKVSchemaDiagnostics()); eventPack.ResetForKVLoad(); eventPack.ReplaceFromKVDecoded(pack); var packActual=eventPack.SnapshotForKVValidation(); Check(packActual.EventId==0 && packActual.PurchaseCount==7 && packActual.VipLevel==8 && packActual.RedirectId==9); eventPack.ResetForKVLoad(); eventPack.ReplaceFromKVDecoded(EventPackData.DecodeForKVLoad(new Dictionary(),eventSnapshot,new PlayerKVSchemaDiagnostics())); packActual=eventPack.SnapshotForKVValidation(); Check(packActual.EventId==0 && packActual.PurchaseCount==0 && packActual.VipLevel==0 && packActual.RedirectId==0); progress = new ThanksGivingPackData(); migration = new ChainPackWithProgressMigrationData(); var raw = new Dictionary { ["ThanksGivingPackData"] = "1,2,3,4", ["ThanksGivingPackMigrationData"] = "5,6,7,8" }; var a = ThanksGivingPackData.DecodeForKVLoad(raw,new PlayerKVSchemaDiagnostics()); var b = ChainPackWithProgressMigrationData.DecodeForKVLoad(raw,new PlayerKVSchemaDiagnostics()); progress.ResetForKVLoad(); migration.ResetForKVLoad(); progress.ReplaceFromKVDecoded(a); migration.ReplaceFromKVDecoded(b); var actual = progress.SnapshotForKVValidation(); var actualMigration = migration.SnapshotForKVValidation(); Check(actual.IsPresent && actual.EventId == 1 && actual.RedirectId == 2 && actual.TokenProgress == 3 && actual.ChainProgress == 4); Check(actualMigration.IsPresent && actualMigration.EventId == 5 && actualMigration.RedirectId == 6 && actualMigration.TokenProgress == 7 && actualMigration.ChainProgress == 8); migration.ReplaceFromKVDecoded(a); progress.ChainProgress = 99; Check(migration.ChainProgress == 4 && a.ChainProgress == 4); var missing = ThanksGivingPackData.DecodeForKVLoad(new Dictionary(),new PlayerKVSchemaDiagnostics()); var missingMigration = ChainPackWithProgressMigrationData.DecodeForKVLoad(new Dictionary(),new PlayerKVSchemaDiagnostics()); progress.ResetForKVLoad(); migration.ResetForKVLoad(); progress.ReplaceFromKVDecoded(missing); migration.ReplaceFromKVDecoded(missingMigration); Check(!progress.SnapshotForKVValidation().IsPresent && !migration.SnapshotForKVValidation().IsPresent); } success = true; } catch (Exception ex) { errorType = ex.GetType().Name; } finally { try { piggy?.ResetForKVLoad(); bargain?.ResetForKVLoad(); shooting?.ResetForKVLoad(); progress?.ResetForKVLoad(); migration?.ResetForKVLoad(); eventPack?.ResetForKVLoad(); pinata?.ResetForKVLoad(); player?.ResetForKVLoad(); small?.ResetForKVLoad(); shop?.ResetForKVLoad(); eventBase?.ResetForKVLoad(); } catch (Exception ex) { success = false; errorType = ex.GetType().Name; } try { isolated.Dispose(); } catch (Exception ex) { success = false; errorType = ex.GetType().Name; } finally { // Synchronous test only: no workers, leases or pending operations can outlive this scope. ValidationContextAccess.Use(originalContainer); ValidationContextAccess.Events = originalAggregator; restored = ReferenceEquals(asap.core.GContext.container,originalContainer) && ReferenceEquals(ValidationContextAccess.Events,originalAggregator); running = false; } } return Task.FromResult(JsonConvert.SerializeObject(new { success = success && restored, caseId = includeEventBase ? "M05-M21-M22-M24" : includeShop ? "M04-M21-M22-M24" : includeCore ? "M01-M02-M21-M22-M24" : includeProgress ? "M20-M21-M22-M24-M28-M29" : "M21-M22-M24", assertions, restored, errorType, scope = includeEventBase ? "M05-D3-only" : includeShop ? "M04-D3-only" : includeCore ? "M01-M02-D3-only" : includeProgress ? "M20-D3-M28-M29-only" : "D3-only", fullB220 = false, formalRequests = 0 })); } } } #endif