using game.PlayerDataStorage; using Newtonsoft.Json.Linq; using System.Security.Cryptography; var suiteRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../Evidence", DateTime.UtcNow.ToString("yyyyMMdd-HHmmss-fff"))); Directory.CreateDirectory(suiteRoot); var api = new Uri("http://s101.local:18080/"); // Identity only; this program has no HTTP dependency. var outcomes = new List(); int count = 0; void Check(bool condition, string name) { if (!condition) throw new Exception(name); outcomes.Add("PASS " + name); } void Case(string name, Action test) { var root = Path.Combine(suiteRoot, (++count).ToString("D2")); Directory.CreateDirectory(root); test(root); Console.WriteLine("PASS " + name); } Dictionary Values(params string[] pairs) { var d = new Dictionary(StringComparer.Ordinal); for(int n=0;n fault = null) { var r=PlayerKVSyncState.Open(root,api,"101",fault);Check(r.Success,"Open succeeds");return r.State; } string MainFile(string root) => Path.Combine(new KVIdentity(root,api,"101").DirectoryPath,"state.json"); Case("absolute vs drive-relative root identity", root => { var normal = new KVIdentity(root,api,"101"); Check(Path.IsPathFullyQualified(normal.DirectoryPath),"normal absolute root remains absolute"); var driveRelative = "C:CodexOutput/upserver/KVSyncReviewTests"; Check(Path.IsPathRooted(driveRelative) && !Path.IsPathFullyQualified(driveRelative),"Windows drive-relative input is rooted but not absolute"); bool rejected = false; try { _ = new KVIdentity(driveRelative,api,"101"); } catch (ArgumentException) { rejected = true; } outcomes.Add("OBSERVED DriveRelativeRejected="+rejected); Check(rejected,"drive-relative root rejected after reviewed fix"); Check(PlayerKVSyncState.Open(@"\relative",api,"101").Error==PlayerKVOpenError.Validation,"root-relative path rejected after reviewed fix"); File.WriteAllText(Path.Combine(suiteRoot,"root-validation-observation.txt"),"DriveRelativeRejected="+rejected+Environment.NewLine); }); Case("active old version / newer pending / restart unknown", root => { using(var s=Open(root)) { Check(s.Reconcile(Values("k","base")).Success,"baseline persisted"); var v1=s.Update("k","one"); Check(v1.Success,"v1 accepted"); Check(s.BeginBatch(long.MaxValue,out var b).Success && b.Items.Count==1,"active v1 persisted"); Check(s.Update("k","two").Success,"v2 accepted during v1"); } using(var s=Open(root)) { Check(s.Status==PlayerKVSyncStatus.UnknownOutcome,"active recovery freezes"); Check(s.Inspect().UnknownBatch.Items[0].Target.Value=="one" && s.Inspect().Pending["k"].Target.Value=="two","both versions survive restart"); Check(s.Reconcile(Values("k","base")).Success && s.Status==PlayerKVSyncStatus.UnknownOutcome,"R=B cannot unlock unknown"); Check(s.Reconcile(Values("k","two")).Success && s.Status==PlayerKVSyncStatus.UnknownOutcome,"R=L cannot unlock unknown"); Check(!s.Update("k","three").Success,"unknown refuses editing"); } }); Case("confirmation advances newer pending base and preserves localOnly", root=>{ using var s=Open(root); s.Reconcile(Values("k","base"));s.Update("k","one");s.BeginBatch(long.MaxValue,out var b); s.Update("k","two");s.Update("k","local",false); Check(s.ConfirmBatch(b.Id).Success,"confirm v1 succeeds"); Check(s.Inspect().Pending["k"].BaseValue.Value=="one" && s.Inspect().Pending["k"].Target.Value=="two","v2 base advanced and target preserved"); Check(s.ReadLocal()["k"]=="local","localOnly survives remote confirmation"); Check(s.Delete("k").Success && !s.ReadLocal().ContainsKey("k"),"Delete removes localOnly atomically"); }); foreach(var stage in Enum.GetValues()) Case("disk fault "+stage,root=>{ bool armed=false; using(var s=Open(root, at=>{if(armed && at==stage)throw new IOException("Injected review fault");})) { Check(s.Reconcile(Values("k","base")).Success,"seed baseline before disk fault");armed=true; var edit=s.Update("k","target"); Check(!edit.Success && s.Status==PlayerKVSyncStatus.StorageError,"fault freezes without accepted edit"); Check(s.Inspect().Pending.Count==0,"failed commit never publishes memory"); Check(edit.LocalCommitMayHaveSucceeded==(stage==KVCacheWriteStage.AfterReplace),"commit uncertainty matches injection boundary"); Check(!s.Update("k","third").Success,"frozen edit is blocked"); } using(var s=Open(root)) { Check(s.Inspect().Pending.Count==(stage==KVCacheWriteStage.AfterReplace?1:0),"reopen reads actually committed revision"); } }); Case("corrupt snapshots reject and release file lock",root=>{ using(var s=Open(root)){s.Reconcile(Values("k","base"));s.Update("k","target");} var file=MainFile(root);var valid=File.ReadAllText(file); var corruptions = new List{"[{}]","null","{}",valid+" {}",valid.Replace("\"formatVersion\":1","\"formatVersion\":1,\"formatVersion\":1")}; foreach(var field in new[]{"revision","nextSequence","formatVersion"}) { var j=JObject.Parse(valid);j[field]=JToken.Parse("9223372036854775808");corruptions.Add(j.ToString()); j=JObject.Parse(valid);j[field]=JValue.CreateNull();corruptions.Add(j.ToString()); } foreach(var malformed in corruptions) { File.WriteAllText(file,malformed);var result=PlayerKVSyncState.Open(root,api,"101"); Check(result.Error==PlayerKVOpenError.RecoveryRequired,"malformed snapshot rejects with RecoveryRequired"); File.WriteAllText(file,valid);using var recovered=Open(root); Check(recovered.Inspect().Pending.Count==1,"failed open released exclusive lock and preserved source log"); } }); Case("backup is evidence only / invalid backup does not veto valid main",root=>{ using(var s=Open(root)){s.Reconcile(Values());s.Update("k","target");} var file=MainFile(root);var valid=File.ReadAllText(file);File.WriteAllText(file+".bak","corrupt"); using(var s=Open(root)){Check(s.Inspect().Pending.Count==1,"valid main wins over corrupt backup");} File.Delete(file); Check(PlayerKVSyncState.Open(root,api,"101").Error==PlayerKVOpenError.RecoveryRequired,"missing main plus corrupt backup cannot create empty cache"); File.WriteAllText(file,valid);using var restored=Open(root); }); Case("exclusive lock and repeated dispose",root=>{ var first=Open(root);Check(PlayerKVSyncState.Open(root,api,"101").Error==PlayerKVOpenError.Busy,"duplicate owner returns Busy"); first.Dispose();first.Dispose();Check(!first.Reconcile(Values()).Success,"closed state rejects writes"); using var second=Open(root);Check(second.Reconcile(Values()).Success,"new owner opens after Dispose"); }); Case("batch association corruption and legal same/newer sequence",root=>{ using(var s=Open(root)) { s.Reconcile(Values("k","base"));s.Update("k","one");s.BeginBatch(long.MaxValue,out _);s.Update("k","two"); } var file=MainFile(root);var valid=File.ReadAllText(file); var mutations=new (string,Action)[] { ("active plus unknown",j=>j["unknownBatch"]=j["activeBatch"].DeepClone()), ("missing corresponding pending",j=>j["pending"]=new JArray()), ("active newer than pending",j=>j["activeBatch"]["items"][0]["sequence"]=j["nextSequence"].DeepClone()), ("same sequence with unequal targets",j=>j["pending"][0]["sequence"]=j["activeBatch"]["items"][0]["sequence"].DeepClone()), ("active and pending base mismatch",j=>j["activeBatch"]["items"][0]["base"]["value"]="wrong"), ("active key absent from pending",j=>j["activeBatch"]["items"][0]["key"]="other"), ("duplicate batch key",j=>((JArray)j["activeBatch"]["items"]).Add(j["activeBatch"]["items"][0].DeepClone())), ("empty batch",j=>j["activeBatch"]["items"]=new JArray()), ("invalid batch id",j=>j["activeBatch"]["id"]="not-guid"), ("batch null items",j=>j["activeBatch"]["items"]=null), ("scalar batch",j=>j["activeBatch"]=1), ("no baseline with active",j=>j["hasBaseline"]=false) }; foreach(var (name,mutate) in mutations) { var json=JObject.Parse(valid);mutate(json);File.WriteAllText(file,json.ToString()); var result=PlayerKVSyncState.Open(root,api,"101"); Check(result.Error==PlayerKVOpenError.RecoveryRequired,"relation corruption rejected: "+name); } File.WriteAllText(file,valid);using(var s=Open(root)) {Check(s.Inspect().Pending["k"].Target.Value=="two","valid newer sequence is accepted");} var same=JObject.Parse(valid);same["pending"][0]=same["activeBatch"]["items"][0].DeepClone();File.WriteAllText(file,same.ToString()); using(var s=Open(root)) { Check(s.Inspect().Pending["k"].Sequence==s.Inspect().UnknownBatch.Items[0].Sequence,"legal same sequence reference is accepted"); } }); Case("ordinary conflict survives restart with edit base distinct from last observation",root=>{ using(var s=Open(root)){s.Reconcile(Values("k","base"));s.Update("k","target");Check(s.Reconcile(Values("k","other")).Success && s.Status==PlayerKVSyncStatus.Conflict,"conflict records new baseline observation");} using(var s=Open(root)) { Check(s.Inspect().Baseline["k"]=="other" && s.Inspect().Pending["k"].BaseValue.Value=="base","baseline observation differs legitimately from edit base"); Check(s.Reconcile(Values("k","other")).Success && s.Status==PlayerKVSyncStatus.Conflict,"ordinary conflict does not disappear on reopen"); Check(s.Reconcile(Values("k","base")).Success && s.Status==PlayerKVSyncStatus.Ready,"ordinary conflict can resolve on matching edit base"); } }); var sourceFiles=new[]{"PlayerKVSyncModels.cs","PlayerKVCacheStore.cs","PlayerKVSyncState.cs"}; foreach(var source in sourceFiles) { var path=Path.GetFullPath(Path.Combine(AppContext.BaseDirectory,"../../../../../../Client/Assets/Scripts/Services/PlayerData",source)); outcomes.Add("SHA256 "+source+" "+Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(path)))); } outcomes.Add("CompletedCases="+count); File.WriteAllLines(Path.Combine(suiteRoot,"Latest-review.log"),outcomes); Console.WriteLine("Evidence="+suiteRoot); Console.WriteLine("Assertions="+outcomes.Count(s=>s.StartsWith("PASS ")));