保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。 保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。 验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。 包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
1451 lines
137 KiB
C#
1451 lines
137 KiB
C#
#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<string> 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<string, string>(),
|
|
new Dictionary<string, string> { ["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<string, string>(),
|
|
new Dictionary<string, string> { ["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<string, string>(), new Dictionary<string, string>(), 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<string, string>
|
|
{ ["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<string, string>(), 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<string, string> { ["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>(T a,T b) => EqualityComparer<T>.Default.Equals(a,b);
|
|
static bool SameList<T>(IReadOnlyList<T> a,IReadOnlyList<T> b,Func<T,T,bool> same) =>
|
|
a.Count==b.Count && Enumerable.Range(0,a.Count).All(i=>same(a[i],b[i]));
|
|
static bool SameDictionary<K,V>(IReadOnlyDictionary<K,V> a,IReadOnlyDictionary<K,V> b,Func<V,V,bool> 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<string,object> values=new Dictionary<string,object>();
|
|
public void Set(string key,object value) => values[key]=value;
|
|
public T Get<T>(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<T> GetEvent<T>(string key) { Subscriptions++; return inner.GetEvent<T>(key); }
|
|
public void Publish<T>(T value,string key) { Published++; throw new InvalidOperationException("Gameplay event during controlled load."); }
|
|
}
|
|
sealed class ValidationHttp : System.Net.Http.HttpMessageHandler
|
|
{
|
|
readonly Func<System.Net.Http.HttpRequestMessage,System.Net.Http.HttpResponseMessage> send;
|
|
internal ValidationHttp(Func<System.Net.Http.HttpRequestMessage,System.Net.Http.HttpResponseMessage> send) { this.send=send; }
|
|
protected override Task<System.Net.Http.HttpResponseMessage> 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<string,string> 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<string,string>{["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<string,string>();
|
|
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<int,string>{[fish.ID]="0|-1|2|3|True"});
|
|
Put("InitRodAccessoriesData",new{GloveID=1,RodToSkin=new Dictionary<int,int>{[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<int,object>{[12]=new{ItemID=13,Discount=.5,hasBought=1}}); raw["BlackFridayCouponDate"]="2026,9,23,1,2,3,14,15,1";
|
|
Put("LimitedTimeEventDic",new Dictionary<int,object>{[1]=new{id=2,duration=9999999d,startTime=day}});
|
|
Put("FixedTimeEventDic",new Dictionary<int,object>{[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<int,object>{[7]=buff}); Put("TargetBuffDataList",new[]{buff}); Put("FishDailyBuff",buff); Put("GlobalBuffDataList",new Dictionary<int,object>{[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<int,int>{[9]=10},dic_weeklyTasks=new Dictionary<int,int>{[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<string,object>{["A"]=new{oldVipLevel=1,curVipLevel=2,timer=3}}); Put("FixedTimeShopDic",new Dictionary<int,object>{[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<int,object>{[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<int,object>{[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<string> RunFullLoad()
|
|
{
|
|
if(running || asap.core.GContext.Ready || UnityEngine.Resources.FindObjectsOfTypeAll<asap.core.GContext>().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<cfg.Tables>(isolated,tables));
|
|
shell=new UnityEngine.GameObject("KV controlled inactive fixture"); shell.SetActive(false); shell.hideFlags=UnityEngine.HideFlags.HideAndDontSave;
|
|
var pf=shell.AddComponent<PlayFabMgr>(); 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(false); 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<EventPinataPackData>(isolated,pinataTarget));
|
|
var pinata=EventPinataPackDataManager.CreateForKVLoad(); var digging=new Script.RuntimeScript.DiggingGameManager();
|
|
var modules=new List<IPlayerKVLoadModule>();
|
|
void Add<T>(int id,Action reset,Action<T> replace) where T:class => modules.Add(new PlayerKVLoadModule(id,reset,d=>replace(d.Module<T>(id))));
|
|
Add<PlayerKVPlayerState>(1,player.ResetForKVLoad,player.ReplaceFromKVDecoded); Add<PlayerKVCoreState>(2,small.ResetForKVLoad,small.ReplaceFromKVDecoded);
|
|
Add<PlayerKVFishState>(3,fish.ResetForKVLoad,fish.ReplaceFromKVDecoded); Add<PlayerKVShopState>(4,shop.ResetForKVLoad,shop.ReplaceFromKVDecoded);
|
|
Add<PlayerKVEventBaseState>(5,eventBase.ResetForKVLoad,eventBase.ReplaceFromKVDecoded); Add<PlayerKVBuffState>(6,buffs.ResetForKVLoad,buffs.ReplaceFromKVDecoded);
|
|
Add<PlayerKVCampState>(7,camp.ResetForKVLoad,camp.ReplaceFromKVDecoded); Add<PlayerKVAchievementState>(8,achievements.ResetForKVLoad,achievements.ReplaceFromKVDecoded);
|
|
Add<PlayerKVIntMapState>(9,box.ResetForKVLoad,box.ReplaceFromKVDecoded); Add<PlayerKVGuideState>(10,guide.ResetForKVLoad,guide.ReplaceFromKVDecoded);
|
|
Add<PlayerKVBattlePassState>(11,battle.ResetForKVLoad,battle.ReplaceFromKVDecoded); Add<PlayerKVMonthlySignState>(12,sign.ResetForKVLoad,sign.ReplaceFromKVDecoded);
|
|
Add<PlayerKVTriggerState>(13,trigger.ResetForKVLoad,trigger.ReplaceFromKVDecoded); Add<PlayerKVClubState>(14,club.ResetForKVLoad,club.ReplaceFromKVDecoded);
|
|
Add<PlayerKVChallengeState>(15,challenge.ResetForKVLoad,challenge.ReplaceFromKVDecoded); Add<PlayerKVOfferState>(16,chains.ResetForKVLoad,chains.ReplaceFromKVDecoded);
|
|
Add<PlayerKVOfferState>(17,disco.ResetForKVLoad,disco.ReplaceFromKVDecoded); Add<PlayerKVPartnerState>(18,partner.ResetForKVLoad,partner.ReplaceFromKVDecoded);
|
|
Add<PlayerKVCubeState>(19,cube.ResetForKVLoad,cube.ReplaceFromKVDecoded); Add<PlayerKVEventPackState>(20,eventPack.ResetForKVLoad,eventPack.ReplaceFromKVDecoded);
|
|
Add<PlayerKVPiggyBankState>(21,piggy.ResetForKVLoad,piggy.ReplaceFromKVDecoded); Add<PlayerKVBargainState>(22,bargain.ResetForKVLoad,bargain.ReplaceFromKVDecoded);
|
|
Add<PlayerKVFootPrintState>(23,foot.ResetForKVLoad,foot.ReplaceFromKVDecoded); Add<PlayerKVShootingRangeState>(24,shooting.ResetForKVLoad,shooting.ReplaceFromKVDecoded);
|
|
Add<PlayerKVPotionState>(25,potion.ResetForKVLoad,potion.ReplaceFromKVDecoded); Add<PlayerKVPinballState>(26,pinball.ResetForKVLoad,pinball.ReplaceFromKVDecoded);
|
|
Add<PlayerKVScratchState>(27,scratch.ResetForKVLoad,scratch.ReplaceFromKVDecoded);
|
|
Add<PlayerKVProgressPackPair>(28,()=>{progress.ResetForKVLoad();migration.ResetForKVLoad();},p=>{progress.ReplaceFromKVDecoded(p.Current);migration.ReplaceFromKVDecoded(p.Migration);});
|
|
Add<PlayerKVPinataState>(29,pinata.ResetForKVLoad,pinata.ReplaceFromKVDecoded); Add<PlayerKVEmptyModuleState>(30,digging.ResetForKVLoad,digging.ReplaceFromKVDecoded);
|
|
modules.Add(new PlayerKVLoadModule(31,user.ResetForKVLoad,d=>user.ReplaceFromKVDecoded(d.Module<PlayerKVLoginState>(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<string,string> 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<string,string> raw,bool newPF)
|
|
{
|
|
System.Net.CookieContainer jar=null;
|
|
return PlayerKVGameLoadCapability.CreateForValidation(new PlayerKVIdentitySnapshot("FF",newPF,newPF ? PlayerKVIdentityChannel.hwVkid : PlayerKVIdentityChannel.none,Array.Empty<PlayerKVIdentityChannel>()),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<string> 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<string,string>{["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<string,string>(),new PlayerKVIdentitySnapshot(null,false,PlayerKVIdentityChannel.none,Array.Empty<PlayerKVIdentityChannel>()),new PlayerKVSchemaDiagnostics());
|
|
user.ResetForKVLoad(); user.ReplaceFromKVDecoded(b,2,new Dictionary<string,string>());
|
|
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<string> RunPartner()
|
|
{
|
|
if(running || asap.core.GContext.Ready || UnityEngine.Resources.FindObjectsOfTypeAll<asap.core.GContext>().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<cfg.Tables>(containerA,tablesA));
|
|
Check(asap.core.ContainerExtensions.RegisterInstance<cfg.Tables>(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<FriendService>(containerA)==null && asap.core.ContainerExtensions.Resolve<IUserService>(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<List<int>>{new List<int>{6,7},new List<int>()},
|
|
BuilderPartnerScore=new List<(int,int)>{(8,9)},PartnerBonus=(1.25f,now.AddHours(1)),FreeSpinNum=10,
|
|
Bots=new Dictionary<int,EventPartnerData.EventBuildBotInfo>{[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<bool>{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<int>(),Array.Empty<int>(),new Dictionary<string,int>(),CultureInfo.InvariantCulture,now,0);
|
|
var state=DataCenter.EventPartnerGatherManager.DecodeForKVLoad(new Dictionary<string,string>{["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<string>{"synthetic-old"};
|
|
ValidationContextAccess.Use(containerB); ValidationContextAccess.Events=new asap.core.EventAggregator();
|
|
Check(asap.core.ContainerExtensions.Resolve<FriendService>(containerB)==null && asap.core.ContainerExtensions.Resolve<IUserService>(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<string,string>(),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<string> 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<string,int>(),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<string,string> { ["Map_5_"+config.AreaID]=JsonConvert.SerializeObject(new Dictionary<int,string>{[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<string,string>{["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<string,string>(),new Dictionary<string,string>(),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<int>(),new[]{1},new Dictionary<string,int>(),CultureInfo.InvariantCulture,snapshot.NowUtc,0,
|
|
fish:new Dictionary<int,PlayerKVFishConfig>{[1]=new PlayerKVFishConfig(1,1,5,1,2,1,0)},cards:new Dictionary<int,PlayerKVFishCardConfig>{[1]=new PlayerKVFishCardConfig(1,1,new[]{10},new[]{0f})});
|
|
var small=GameCore.PlayerFishData.DecodeForKVLoad(new Dictionary<string,string>{["Map_5_1"]="{\"1\":\"1|0.125|0|1|false\"}"},new Dictionary<string,string>(),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<string> 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<string,string> { ["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<string,string>(),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<string> 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<int>(),Array.Empty<int>(),new Dictionary<string,int>(),
|
|
CultureInfo.InvariantCulture,new DateTime(2026,9,23),0);
|
|
try
|
|
{
|
|
var raw=new Dictionary<string,string> {
|
|
["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<int,int> { [9]=10 },
|
|
dic_weeklyTasks=new Dictionary<int,int> { [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<string,string>(),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<string> 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<int>(),Array.Empty<int>(),new Dictionary<string,int>(),
|
|
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<string,string> { ["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<List<int>> { new List<int> { 99 } }; manager.CurrentPieceDatas=new List<List<int>> { new List<int> { 88 } };
|
|
manager.ResetForKVLoad(); manager.ReplaceFromKVDecoded(EventCubeMeltManager.DecodeForKVLoad(new Dictionary<string,string>(),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<string> 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<string> 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<int>(),Array.Empty<int>(),new Dictionary<string,int>(),
|
|
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<string,string> { ["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<string,string> { ["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<string,string>(),snapshot,new PlayerKVSchemaDiagnostics()));
|
|
Check(challenge.fishingChallengeData==null && challenge.IsInactiveForKVValidation);
|
|
}
|
|
|
|
{
|
|
var raw=new Dictionary<string,string> { ["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<string,string>(),snapshot,new PlayerKVSchemaDiagnostics()));
|
|
Check(pinball.PinballData==null && pinball.IsInactiveForKVValidation);
|
|
}
|
|
|
|
{
|
|
var raw=new Dictionary<string,string> { ["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<int,object> { [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<string,string>(),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<string> 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<int>(),Array.Empty<int>(),new Dictionary<string,int>(),
|
|
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<string,string> {
|
|
["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<string,string>(),snapshot,new PlayerKVSchemaDiagnostics()));
|
|
Check(buffs.IsEmptyForKVValidation);
|
|
var raw=new Dictionary<string,string> {
|
|
["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<string,string>(),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<string> 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<int>(),Array.Empty<int>(),new Dictionary<string,int>(),
|
|
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<string,string> { ["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<string,string>(),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<string,string> { ["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<string,string>(),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<string> 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<string,string> { ["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<string,string>(),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<string> RunClub()
|
|
{
|
|
int assertions = 0;
|
|
void Check(bool condition) { assertions++; if (!condition) throw new InvalidOperationException("Club assertion failed."); }
|
|
var club = new ClubService(false);
|
|
var snapshot = new PlayerKVSchemaSnapshot("M14",Array.Empty<int>(),Array.Empty<int>(),new Dictionary<string,int>(),
|
|
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<string,string> {
|
|
["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<string,string>(),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<string> 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<int,int> { [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<string,string>(),new Dictionary<string,string>(),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<string,string> { ["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<string,string>(),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<string,string> {
|
|
["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<string,string>(),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<string> RunD3(bool includeProgress = false,bool includeCore = false,bool includeShop = false,bool includeEventBase = false)
|
|
{
|
|
if (running || asap.core.GContext.Ready || UnityEngine.Resources.FindObjectsOfTypeAll<asap.core.GContext>().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<int>(), Array.Empty<int>(),
|
|
new Dictionary<string,int>(), 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<cfg.Tables>(isolated, tables));
|
|
Check(ReferenceEquals(asap.core.ContainerExtensions.Resolve<cfg.Tables>(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<int>(),Array.Empty<int>(),new Dictionary<string,int>(),CultureInfo.InvariantCulture,snapshot.NowUtc,tables.TbGlobalConfig.InitWheelTicket,globalTimeOffset:tables.TbGlobalConfig.GlobalTimeOffset);
|
|
var raw=new Dictionary<string,string> {
|
|
["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<string,string>{["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<string,string>(),new Dictionary<string,string>(),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<string,string> {
|
|
["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<string,string>(),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<string,int>(),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<string,string> { ["newData"]=string.Join(",",slots),["vipL"]="-1,5|0,6|1,7" };
|
|
var raw=new Dictionary<string,string> { ["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<string,string>(),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<string,string>{{"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<string,string>{{"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<string,string> { ["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<string,string>(), 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<string,string> { ["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<string,string>(), 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<string,string> {
|
|
["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<string,string> { ["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<string,string>(), 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<EventPinataPackData>(isolated,pinataTarget));
|
|
pinata=EventPinataPackDataManager.CreateForKVLoad();
|
|
var pinataState=EventPinataPackDataManager.DecodeForKVLoad(new Dictionary<string,string> {
|
|
["EventPinataPackData"]="{\"EventID\":1,\"PinataMainID\":2,\"PurchasedCount\":3}"
|
|
},new PlayerKVSchemaDiagnostics());
|
|
pinata.ResetForKVLoad(); pinata.ReplaceFromKVDecoded(pinataState);
|
|
Check(ReferenceEquals(pinataTarget,asap.core.ContainerExtensions.Resolve<EventPinataPackData>(isolated)) &&
|
|
pinataTarget.EventID==1 && pinataTarget.PinataMainID==2 && pinataTarget.PurchasedCount==3);
|
|
pinata.ResetForKVLoad(); pinata.ReplaceFromKVDecoded(EventPinataPackDataManager.DecodeForKVLoad(new Dictionary<string,string>(),new PlayerKVSchemaDiagnostics()));
|
|
Check(ReferenceEquals(pinataTarget,asap.core.ContainerExtensions.Resolve<EventPinataPackData>(isolated)) &&
|
|
pinataTarget.EventID==0 && pinataTarget.PinataMainID==0 && pinataTarget.PurchasedCount==0);
|
|
eventPack=new EventPackData();
|
|
var eventSnapshot=new PlayerKVSchemaSnapshot("local-M20",Array.Empty<int>(),Array.Empty<int>(),new Dictionary<string,int>(),
|
|
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<string,string> { ["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<string,string>(),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<string,string> { ["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<string,string>(),new PlayerKVSchemaDiagnostics());
|
|
var missingMigration = ChainPackWithProgressMigrationData.DecodeForKVLoad(new Dictionary<string,string>(),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
|