using System.Buffers.Binary; using ICRebuild.Core; int passed=0; void Test(string name,Action body) { body(); Console.WriteLine($"PASS {name}"); passed++; } void Check(bool condition,string message="assertion failed") { if(!condition) throw new Exception(message); } void Throws(Action action) where T:Exception { try { action(); } catch(T) { return; } throw new Exception($"Expected {typeof(T).Name}"); } Test("network send phases preserve order owned payload identity and raw result",()=> { using var pipeline=new NetworkPipeline("v1"); var bytes=new byte[]{1,2,3}; List order=[]; NetworkObservation? before=null,after=null; using var a=pipeline.Subscribe(NetworkPhase.BeforeSend,p=>{order.Add("before");before=p;}); using var b=pipeline.Subscribe(NetworkPhase.AfterSend,p=>{order.Add("after");after=p;}); int calls=0,reads=0; var result=pipeline.Send(()=>{calls++;order.Add("native");bytes[0]=99;return 0;},filter=> { reads++;order.Add("read");return new(new(42,null,bytes,true)); }); Check(result==0 && calls==1 && reads==1 && order.SequenceEqual(["read","before","native","after"])); Check(before!=null && after!=null && ReferenceEquals(before.Message,after.Message)); Check(after!.NativeResult==0 && before!.NativeResult==null && after.Message.Payload.SequenceEqual(new byte[]{1,2,3})); Check(after.Message.GameVersion=="v1" && after.Message.Direction==PacketDirection.Up && pipeline.ObserverErrors==0); }); Test("network observer and capture failures never skip or retry the native send",()=> { using var pipeline=new NetworkPipeline("v1"); int calls=0,notified=0; using var a=pipeline.Subscribe(NetworkPhase.BeforeSend,_=>throw new InvalidOperationException("observer")); using var b=pipeline.Subscribe(NetworkPhase.BeforeSend,_=>notified++); using var c=pipeline.Subscribe(NetworkPhase.AfterSend,_=>throw new InvalidOperationException("after")); Func original=()=>{calls++;return 7;}; Check(pipeline.Send(original,_=>new(new(1,null,[],true)))==7); Check(calls==1 && notified==1 && pipeline.ObserverErrors==2); Check(pipeline.Send(original,_=>throw new InvalidDataException("reader"))==7); Check(calls==2 && pipeline.ReadErrors==1 && notified==1); var after=pipeline.AfterSend; Throws(()=>pipeline.Send(()=>{calls++;throw new InvalidOperationException("native");},_=>new(new(1,null,[],true)))); Check(calls==3 && pipeline.AfterSend==after); }); Test("network receive reads only after native success and preserves metadata failure",()=> { using var pipeline=new NetworkPipeline("v1"); int calls=0,reads=0; NetworkObservation? observed=null; using var s=pipeline.Subscribe(NetworkPhase.Received,p=>observed=p); Func,CaptureRead> capture=_=>{reads++;return new(new(42,123,[],false,"truncated"));}; Check(pipeline.Receive(()=>{calls++;return 0;},capture)==0 && reads==0 && observed==null); Check(pipeline.Receive(()=>{calls++;return 2;},capture)==2); Check(calls==2 && reads==1 && observed is { NativeResult:2 } && !observed.Message.Complete && observed.Message.Issue=="truncated"); Check(pipeline.ReadErrors==1 && pipeline.ObserverErrors==0); Throws(()=>pipeline.Receive(()=>{calls++;throw new InvalidOperationException();},capture)); Check(calls==3 && reads==1); }); Test("log pause direction and opcode filters do not filter independent network observers",()=> { using var pipeline=new NetworkPipeline("v1"); var buffer=new PacketBuffer(); var recorder=new PacketRecorder(buffer); using var up=pipeline.Subscribe(NetworkPhase.BeforeSend,recorder.Record); using var down=pipeline.Subscribe(NetworkPhase.Received,recorder.Record); int received=0,sent=0; using var businessDown=pipeline.Subscribe(NetworkPhase.Received,_=>received++,42); using var businessUp=pipeline.Subscribe(NetworkPhase.AfterSend,_=>sent++,42); void SendPair() { pipeline.Send(()=>1,_=>new(new(42,null,[1],true))); pipeline.Receive(()=>1,_=>new(new(42,123,[2],true))); } SendPair(); Check(buffer.Snapshot().Length==0); // paused by default buffer.Paused=false;buffer.OpcodeFilter=43; SendPair(); Check(buffer.Snapshot().Length==0); buffer.OpcodeFilter=null;buffer.DirectionFilter=PacketDirection.Down;SendPair(); Check(received==3 && sent==3 && buffer.Snapshot().Length==1 && recorder.CompleteDown==1 && recorder.CompleteUp==0); buffer.DirectionFilter=null; SendPair(); Check(buffer.Snapshot().Length==3 && recorder.CompleteUp==1 && pipeline.ObserverErrors==0); var record=buffer.Snapshot()[^1]; Check(record.Context is {Phase:NetworkPhase.Received,Stage:PacketStage.RawIpcPayload,Origin:PacketOrigin.NativeZone,NativeResult:1}); }); Test("network cancellation and shutdown remove callbacks without duplicate reattachment",()=> { using var pipeline=new NetworkPipeline("v1"); int calls=0,reads=0; CaptureRead Capture(Func filter) { reads++;return filter(42) ? new(new(42,null,[],true)) : new(null); } for(int i=0;i<3;i++) { var sub=pipeline.Subscribe(NetworkPhase.BeforeSend,_=>calls++,42); pipeline.Send(()=>1,Capture); sub.Dispose();sub.Dispose(); pipeline.Send(()=>1,Capture); // no observers: do not copy memory } Check(calls==3 && reads==3 && pipeline.SubscriberCount==0); IDisposable? after=null; using var before=pipeline.Subscribe(NetworkPhase.BeforeSend,_=>after!.Dispose()); after=pipeline.Subscribe(NetworkPhase.AfterSend,_=>calls++); pipeline.Send(()=>1,Capture); Check(calls==3); pipeline.Dispose();pipeline.Send(()=>1,Capture); Check(pipeline.SubscriberCount==0 && reads==4 && pipeline.ObserverErrors==0); Throws(()=>pipeline.Subscribe(NetworkPhase.Received,_=>{})); }); Test("shared network subscriptions survive recorder shutdown until final owner releases",()=> { using var pipeline=new NetworkPipeline("v1"); int started=0,stopped=0,logs=0,business=0; var shared=new SharedModuleResource(s=>{started++;s.Defer(()=>stopped++);}); var recorder=new ResourceScope();var consumer=new ResourceScope(); shared.Acquire(recorder);recorder.Own(pipeline.Subscribe(NetworkPhase.Received,_=>logs++)); shared.Acquire(consumer);consumer.Own(pipeline.Subscribe(NetworkPhase.Received,_=>business++)); pipeline.Receive(()=>1,_=>new(new(42,null,[],true))); recorder.Dispose(); pipeline.Receive(()=>1,_=>new(new(42,null,[],true))); Check(started==1 && stopped==0 && logs==1 && business==2); consumer.Dispose(); Check(stopped==1 && pipeline.SubscriberCount==0); }); Test("network record context roundtrips without changing timestamps or payload",()=> { var path=Path.Combine(Path.GetTempPath(),Guid.NewGuid()+".jsonl"); try { var message=new NetworkMessage(42,PacketDirection.Up,"v1",new(7,null,[1,2],true)); var buffer=new PacketBuffer { Paused=false }; new PacketRecorder(buffer).Record(new(message,NetworkPhase.BeforeSend)); PacketFiles.Export(path,buffer.Snapshot());var imported=PacketFiles.Import(path).Single(); Check(imported.Time==message.Time && imported.MonotonicTicks==message.MonotonicTicks && imported.Payload.SequenceEqual(message.Payload.ToArray())); Check(imported.Context is {ObservationId:42,Phase:NetworkPhase.BeforeSend,NativeResult:null}); } finally { File.Delete(path); } }); Test("status blocking requires current local identity and rejects replay or stale frames",()=> { var local=(nint)0x10000; var manager=local+StatusBlockPolicy.ManagerOffset; var frame=new GameFrame(true,local,false,false,DungeonSnapshot.Empty, LocalStatusManagerAddress:manager,LocalEntityId:42); Check(StatusBlockPolicy.IsLocal(frame,manager,local,42)); Check(!StatusBlockPolicy.IsLocal(frame,manager+16,local,42)); Check(!StatusBlockPolicy.IsLocal(frame,manager,local+0x10000,42)); Check(!StatusBlockPolicy.IsLocal(frame,manager,local,43)); // recycled address Check(!StatusBlockPolicy.IsLocal(frame,manager,0,42)); Check(!StatusBlockPolicy.IsLocal(frame with { IsReplay=true },manager,local,42)); Check(!StatusBlockPolicy.IsLocal(frame with { LocalEntityId=0xE0000000 },manager,local,0xE0000000)); Check(!StatusBlockPolicy.IsLocal(frame with { LocalStatusManagerAddress=manager+16 },manager+16,local,42)); long now=0; var frames=new FrameStore(()=>now,10); frames.Publish(frame); now=11; Check(!StatusBlockPolicy.IsLocal(frames.Read(),manager,local,42)); frames.Publish(frame); frames.Clear(); Check(!StatusBlockPolicy.IsLocal(frames.Read(),manager,local,42)); }); Test("status selection respects both list halves and immutable audited settings",()=> { var selected=new HashSet{1257,142,65535}; var settings=new SettingsSnapshot(new Dictionary(),[],true,0,blockedStatuses:selected); selected.Clear(); Check(settings.BlockedStatuses.SetEquals([1257u])); foreach(var index in new[]{0,29,30,59}) Check(StatusBlockPolicy.IsSelected(index,1257,settings.BlockedStatuses)); foreach(var index in new[]{-1,60,int.MaxValue}) Check(!StatusBlockPolicy.IsSelected(index,1257,settings.BlockedStatuses)); Check(!StatusBlockPolicy.IsSelected(0,0,settings.BlockedStatuses)); Check(!StatusBlockPolicy.IsSelected(0,1681,settings.BlockedStatuses)); Check(!StatusBlockPolicy.IsSelected(0,142,new HashSet{142})); Check(!StatusBlockPolicy.IsSelected(0,1257,new HashSet())); }); Test("blocked status forwards an empty update once and preserves native result and refresh flag",()=> { foreach(byte refresh in new byte[]{0,1}) { int calls=0; StatusSetter original=(manager,index,id,remaining,param,source,flag)=> { calls++; Check(manager==(nint)0x1234 && index==59 && flag==refresh); Check(id==0 && remaining==0 && param==0 && source==0); return 1; // batch caller must see a change and refresh flags }; Check(StatusBlockPolicy.Forward(original,true,0x1234,59,1257,12.5f,7,0x123456789ABCDEF0,refresh)==1); Check(calls==1); } }); Test("unselected status preserves every argument and original failure is never retried",()=> { int calls=0; StatusSetter original=(manager,index,id,remaining,param,source,flag)=> { calls++; Check(manager==(nint)0x1234 && index==30 && id==1681 && remaining==-12.5f); Check(param==ushort.MaxValue && source==0x123456789ABCDEF0 && flag==1); return 0; }; Check(StatusBlockPolicy.Forward(original,false,0x1234,30,1681,-12.5f,ushort.MaxValue,0x123456789ABCDEF0,1)==0); Check(calls==1); calls=0; StatusSetter failing=(_,_,_,_,_,_,_)=>{calls++;throw new InvalidOperationException();}; Throws(()=>StatusBlockPolicy.Forward(failing,true,0x1234,0,1257,0,0,0,0)); Check(calls==1); }); Test("approved scope contains exactly 24 unique features",()=> { int[] expected=[..Enumerable.Range(1,11),..Enumerable.Range(13,8),27,28,29,36,37]; Check(Features.All.Select(f=>f.Id).SequenceEqual(expected)); }); Test("partial startup rolls back resources in reverse order",()=> { using var manager=new ModuleManager(); List released=[]; manager.Register(9,s=> { s.Defer(()=>released.Add(1)); s.Defer(()=>released.Add(2)); throw new InvalidOperationException("scan failed"); }); Check(!manager.Enable(9)); Check(released.SequenceEqual([2,1])); Check(!manager.IsEnabled(9)); Check(manager.Status[9].State==ModuleState.Failed); }); Test("cleanup failure does not skip remaining resources",()=> { var scope=new ResourceScope(); var released=false; scope.Defer(()=>released=true); scope.Defer(()=>throw new InvalidOperationException()); Throws(scope.Dispose); Check(released); scope.Dispose(); }); Test("repeated enable and disable are idempotent",()=> { var manager=new ModuleManager(); int started=0,released=0; manager.Register(9,s=>{started++;s.Defer(()=>released++);}); Check(manager.Enable(9)); Check(manager.Enable(9)); manager.Disable(9); manager.Disable(9); manager.Dispose(); manager.Dispose(); Check(started==1 && released==1); Throws(()=>manager.Enable(9)); }); Test("dependent modules stop before their dependency",()=> { using var manager=new ModuleManager(); List stopped=[]; manager.Register(9,s=>s.Defer(()=>stopped.Add(9))); manager.Register(10,s=>s.Defer(()=>stopped.Add(10)),9); Check(!manager.Enable(10)); Check(manager.Enable(9)); Check(manager.Enable(10)); manager.Disable(9); Check(stopped.SequenceEqual([10,9])); }); Test("one unavailable module does not block another",()=> { using var manager=new ModuleManager(); manager.Register(11,_=>{}); Check(!manager.Enable(8)); Check(manager.Enable(11)); Check(manager.IsEnabled(11)); }); Test("failed cleanup prevents duplicate native attachment",()=> { using var manager=new ModuleManager(); var starts=0; manager.Register(9,s=>{starts++;s.Defer(()=>throw new InvalidOperationException("dispose failed"));}); Check(manager.Enable(9)); manager.Disable(9); Check(!manager.Enable(9)); Check(starts==1); }); Test("deep dungeon special floors and unknown state",()=> { Check(Policies.HeightDelta(true,true,new(true,100,1,9),-7)==-7); Check(Policies.HeightDelta(true,true,new(true,100,1,10),-7)==0); Check(Policies.HeightDelta(true,true,new(true,1108,3,99),-7)==0); Check(Policies.HeightDelta(true,true,new(true,1290,4,99),-7)==0); Check(Policies.HeightDelta(true,true,new(true,100,1,99),-7)==-7); Check(Policies.HeightDelta(true,true,DungeonSnapshot.Empty,-7)==0); Check(Policies.HeightDelta(false,false,DungeonSnapshot.Empty,-7)==0); Check(Policies.HeightDelta(true,false,DungeonSnapshot.Empty,-7)==-7); }); Test("nonfinite and out of range parameters",()=> { Check(Policies.FiniteClamp(float.NaN,0,5,1)==1); Check(Policies.FiniteClamp(float.PositiveInfinity,0,5)==0); Check(Policies.FiniteClamp(999,0,5)==5); Check(Policies.HeightDelta(true,false,DungeonSnapshot.Empty,float.NaN)==0); }); Test("timeline list semantics and empty list",()=> { HashSet list=[1,2]; Check(Policies.BlockTimeline(1,list,true)); Check(!Policies.BlockTimeline(3,list,true)); Check(!Policies.BlockTimeline(1,list,false)); Check(Policies.BlockTimeline(3,list,false)); Check(!Policies.BlockTimeline(1,new HashSet(),false)); }); Test("fall flag transformation preserves unrelated bits",()=> { Check(Policies.FallFlags(0x80000000)==0x80000000); Check((Policies.FallFlags(0x80000700)&0x80000000)!=0); Check((Policies.FallFlags(0x700)&0x700)==0); }); const string mapping="""{"version-a":{"UP_PingReq":[1],"DOWN_PingRsp":[1],"UP_EventStart":[3]}}"""; Test("opcode version and direction isolation",()=> { var map=new OpcodeMap(); map.Load(mapping,"version-a","fixture"); Check(map.Resolve("version-a",PacketDirection.Up,1)=="UP_PingReq"); Check(map.Resolve("version-a",PacketDirection.Down,1)=="DOWN_PingRsp"); Check(map.Resolve("version-b",PacketDirection.Up,1)==null); Check(map.Resolve("unknown",PacketDirection.Up,1)==null); }); Test("invalid mapping updates retain previous mapping",()=> { var map=new OpcodeMap(); map.Load(mapping,"version-a","fixture"); Throws(()=>map.Load("""{"version-a":{"UP_A":[1],"UP_B":[1]}}""","version-a","bad")); Throws(()=>map.Load("""{"version-a":{"UP_A":[65536]}}""","version-a","bad")); Throws(()=>map.Load(mapping,"missing","bad")); Check(map.Resolve("version-a",PacketDirection.Up,1)=="UP_PingReq"); }); Test("packet parser rejects malformed and ambiguous payloads",()=> { Check(!PacketParser.Parse("UP_PingReq",new byte[2]).Parsed); Check(PacketParser.Parse("UP_PingReq",new byte[28]).Parsed); Check(!PacketParser.Parse("UP_EventAction",new byte[1028]).Parsed); Check(!PacketParser.Parse(null,new byte[8]).Parsed); var effect=new byte[4+0x58]; effect[0]=17; Check(!PacketParser.Parse("DOWN_EffectResult",effect).Parsed); effect[0]=2; Check(!PacketParser.Parse("DOWN_EffectResult",effect).Parsed); effect[0]=1; effect[4+0x15]=5; Check(!PacketParser.Parse("DOWN_EffectResult",effect).Parsed); effect[4+0x15]=4; Check(PacketParser.Parse("DOWN_EffectResult",effect).Parsed); var movement=new byte[40]; BinaryPrimitives.WriteSingleLittleEndian(movement.AsSpan(12),float.NaN); Check(!PacketParser.Parse("UP_UpdatePositionHandler",movement).Parsed); }); Test("bounded buffer pause filter and copy ownership",()=> { var buffer=new PacketBuffer(2,10); byte[] data=[1,2]; Check(!buffer.Add(PacketDirection.Up,1,"a",null,null,data)); buffer.Paused=false; Check(buffer.Add(PacketDirection.Up,1,"a",null,null,data)); data[0]=9; Check(buffer.Snapshot()[0].Payload[0]==1); buffer.Add(PacketDirection.Up,2,"a",null,null,data); buffer.Add(PacketDirection.Down,3,"a",null,null,data); Check(buffer.Snapshot().Length==2 && buffer.Dropped==1); Check(!buffer.Add(PacketDirection.Up,1,"a",null,null,new byte[11])); Check(buffer.Dropped==2); buffer.DirectionFilter=PacketDirection.Up; Check(!buffer.Add(PacketDirection.Down,1,"a",null,null,data)); }); Test("offline import preserves chronology and invalid replacement is atomic",()=> { var buffer=new PacketBuffer(2); var time=DateTimeOffset.Parse("2026-01-01T00:00:00Z"); PacketRecord[] records=[new(42,99,time,PacketDirection.Up,1,"a",3,4,[5])]; buffer.Replace(records); Check(buffer.Snapshot()[0].Time==time && buffer.Snapshot()[0].Sequence==42); Throws(()=>buffer.Replace([records[0] with {Opcode=-1}])); Check(buffer.Snapshot()[0].Sequence==42); }); Test("ping correlation uses echoed value and version, not unrelated packets",()=> { var map=new OpcodeMap(); map.Load(mapping,"version-a","fixture"); var now=DateTimeOffset.UtcNow; var up=new byte[28]; var down=new byte[16]; BinaryPrimitives.WriteUInt32LittleEndian(up,123); BinaryPrimitives.WriteUInt32LittleEndian(down,123); PacketRecord[] packets=[new(1,0,now,PacketDirection.Up,1,"version-a",null,null,up),new(2,0,now.AddMilliseconds(75),PacketDirection.Down,1,"version-a",null,null,down)]; var pairs=PacketCorrelator.Ping(packets,map); Check(pairs.Length==1 && pairs[0].Elapsed.TotalMilliseconds==75); Check(PacketCorrelator.Ping([packets[0],packets[1] with {GameVersion="version-b"}],map).Length==0); }); Test("random truncated buffers never throw or read beyond input",()=> { var random=new Random(701); string?[] names=[null,"UP_PingReq","DOWN_PingRsp","UP_UpdatePositionHandler","UP_UpdatePositionInstance","UP_EventStart","UP_EventAction","DOWN_EffectResult"]; for(var i=0;i<3000;i++) { var data=new byte[random.Next(0,1800)]; random.NextBytes(data); PacketParser.Parse(names[i%names.Length],data); } }); Test("metadata events cannot masquerade as complete payloads",()=> { var map=new OpcodeMap(); map.Load(mapping,"version-a","fixture"); var buffer=new PacketBuffer(); buffer.Paused=false; buffer.Add(PacketDirection.Up,1,"version-a",null,null,ReadOnlySpan.Empty,false); Check(!buffer.Snapshot()[0].PayloadComplete); Check(PacketCorrelator.Ping(buffer.Snapshot(),map).Length==0); }); Test("concurrent capture stays bounded with unique ordered sequences",()=> { var buffer=new PacketBuffer(256); buffer.Paused=false; Parallel.For(0,8000,i=>buffer.Add(PacketDirection.Up,i%65536,"a",null,null,ReadOnlySpan.Empty,false)); var records=buffer.Snapshot(); Check(records.Length==256 && buffer.Dropped==7744); Check(records.Select(p=>p.Sequence).Distinct().Count()==256); Check(records.Zip(records.Skip(1)).All(p=>p.First.Sequence { var path=Path.Combine(AppContext.BaseDirectory,"roundtrip-"+Guid.NewGuid()+".jsonl"); try { PacketRecord[] data=[new(42,10,DateTimeOffset.Parse("2026-01-01T00:00:00+08:00"),PacketDirection.Down,2,"a",null,12,[],false)]; PacketFiles.Export(path,data); var loaded=PacketFiles.Import(path); Check(loaded.Length==1 && loaded[0].Time==data[0].Time && loaded[0].Sequence==42 && !loaded[0].PayloadComplete); } finally { if(File.Exists(path)) File.Delete(path); } }); Test("native worker reads only the published frame",()=> { long now=100; var frames=new FrameStore(()=>Interlocked.Read(ref now),500); Check(!frames.Read().Ready); var expected=new GameFrame(true,(nint)123,true,false,new(true,1108,3,98)); frames.Publish(expected); Parallel.For(0,10000,_=>Check(frames.Read()==expected)); now=601; Check(!frames.Read().Ready && frames.Read().LocalAddress==0); now=200; frames.Publish(expected); frames.Clear(); Check(!frames.Read().Ready); }); Test("published settings are isolated from UI collection mutations",()=> { Dictionary values=new() { [14]=2 }; HashSet list=[10]; var settings=new SettingsSnapshot(values,list,true,2); values[14]=5; list.Clear(); Parallel.For(0,5000,_=>Check(settings.Value(14)==2 && settings.Timelines.Contains(10))); }); Test("acceleration preserves native output instead of restoring zero",()=> { float state=0; int calls=0; AccelerationPolicy.Run(ref state,true,()=>{ Check(state==100); state=6; calls++; }); Check(state==6 && calls==1); state=3; AccelerationPolicy.Run(ref state,false,()=>{ Check(state==3); state=4; calls++; }); Check(state==4 && calls==2); }); Test("acceleration applies only to the moving local owner",()=> { var frame=new GameFrame(true,(nint)123,false,false,DungeonSnapshot.Empty); Check(AccelerationPolicy.ShouldPrime(frame,(nint)123,true,0)); Check(!AccelerationPolicy.ShouldPrime(frame,(nint)456,true,0)); Check(!AccelerationPolicy.ShouldPrime(frame,(nint)123,false,0)); Check(!AccelerationPolicy.ShouldPrime(frame with {IsSlowed=true},(nint)123,true,0)); Check(!AccelerationPolicy.ShouldPrime(frame,(nint)123,true,float.NaN)); }); Test("atomic snapshot export keeps previous file when serialization fails",()=> { var path=Path.Combine(AppContext.BaseDirectory,"atomic-"+Guid.NewGuid()+".jsonl"); IEnumerable Broken() { yield return new(1,0,DateTimeOffset.UtcNow,PacketDirection.Up,1,"a",null,null,[],false); throw new InvalidOperationException("capture interrupted"); } try { File.WriteAllText(path,"previous"); Throws(()=>PacketFiles.ExportAtomic(path,Broken())); Check(File.ReadAllText(path)=="previous"); Check(Directory.GetFiles(AppContext.BaseDirectory,Path.GetFileName(path)+".*.tmp").Length==0); } finally { if(File.Exists(path)) File.Delete(path); } }); Test("dash metadata decodes ushort ID without including flags",()=> { Check(DashPolicy.Timeline([0x34,0x12,0xF1,0xAF])==0x1234); Check(DashPolicy.Timeline([0x34])==0); }); Test("dash modes distinguish all-local from empty lists",()=> { HashSet empty=[], selected=[20]; Check(DashPolicy.Block(true,1,1,20,DashFilterMode.AllLocal,empty)); Check(!DashPolicy.Block(true,2,1,20,DashFilterMode.AllLocal,empty)); Check(!DashPolicy.Block(true,1,1,0,DashFilterMode.AllLocal,empty)); Check(!DashPolicy.Block(false,1,1,20,DashFilterMode.AllLocal,empty)); Check(!DashPolicy.Block(true,1,1,20,DashFilterMode.BlockListed,empty)); Check(!DashPolicy.Block(true,1,1,20,DashFilterMode.AllowListed,empty)); Check(DashPolicy.Block(true,1,1,20,DashFilterMode.BlockListed,selected)); Check(!DashPolicy.Block(true,1,1,20,DashFilterMode.AllowListed,selected)); Check(DashPolicy.Block(true,1,1,21,DashFilterMode.AllowListed,selected)); }); Test("target radius is expanded once while own radius remains unchanged",()=> { var frame=new GameFrame(true,1,false,false,DungeonSnapshot.Empty,TargetAddress:2,TargetEntityId:20); Check(TargetRadiusPolicy.Adjust(frame,2,20,1,5)==6); Check(TargetRadiusPolicy.Adjust(frame,1,10,0.5f,5)==0.5f); Check(TargetRadiusPolicy.Adjust(frame,3,30,1,5)==1); // Native landing uses target + own radius: 6 + 0.5, not the erroneous 6 + 5.5. Check(TargetRadiusPolicy.Adjust(frame,2,20,1,5)+TargetRadiusPolicy.Adjust(frame,1,10,0.5f,5)==6.5f); }); Test("render range and movement reads agree on the same target radius",()=> { var frame=new GameFrame(true,1,false,false,DungeonSnapshot.Empty,TargetAddress:2,TargetEntityId:20); Check(TargetRadiusPolicy.Adjust(frame,2,20,1,5)==6); using(NativeTargetRingScope.Enter(true)) Check(TargetRadiusPolicy.Adjust(frame,2,20,1,5)==6); using(ActionRangeCheckScope.Enter(true)) Check(TargetRadiusPolicy.Adjust(frame,2,20,1,5)==6); float workerResult=0; var worker=new Thread(()=>workerResult=TargetRadiusPolicy.Adjust(frame,2,20,1,5)); worker.Start(); worker.Join(); Check(workerResult==6); }); Test("target changes stale identities and invalid radii are handled",()=> { var frame=new GameFrame(true,1,false,false,DungeonSnapshot.Empty,TargetAddress:2,TargetEntityId:20); Check(TargetRadiusPolicy.Adjust(frame,2,99,1,5)==1); Check(TargetRadiusPolicy.Adjust(frame with {TargetAddress=3,TargetEntityId=30},2,20,1,5)==1); Check(TargetRadiusPolicy.Adjust(frame with {Ready=false},2,20,1,5)==1); Check(TargetRadiusPolicy.Adjust(frame with {TargetAddress=1,TargetEntityId=10},1,10,1,5)==1); Check(TargetRadiusPolicy.Adjust(frame,2,20,-1,5)==-1); Check(float.IsNaN(TargetRadiusPolicy.Adjust(frame,2,20,float.NaN,5))); Check(TargetRadiusPolicy.Adjust(frame,2,20,1,float.NaN)==1); }); Test("movement catalogue includes player displacement skills and excludes NPCs",()=> { var skills=MovementSkillCatalog.Select([ new(7386,"Onslaught",5189,true,true,false),new(29079,"Onslaught",12244,true,true,true), new(42,"NPC movement",111,false,true,false),new(43,"Ordinary skill",222,true,false,false), new(44,"Unmapped movement",0,true,true,false),new(45,"Shared",5189,true,true,false)]); Check(skills.Length==4 && skills.Count(MovementSkillCatalog.Mappable)==3); Check(skills.Where(MovementSkillCatalog.Mappable).Select(s=>s.Timeline).Distinct().Count()==2); Check(!skills.Any(s=>s.ActionId is 42 or 43)); }); Test("audited status candidates exclude mismatched legacy entries",()=> { Check(StatusCatalog.AuditedBlockCandidates.Count==38); Check(StatusCatalog.RejectedLegacyBlockIds.All(id=>!StatusCatalog.AuditedBlockCandidates.Contains(id))); Check(StatusCatalog.AuditedBlockCandidates.Contains(604)); Check(StatusCatalog.AuditedBlockCandidates.Contains(1293)); Check(StatusCatalog.AuditedBlockCandidates.Contains(1422)); Check(!StatusCatalog.AuditedBlockCandidates.Contains(142)); }); Test("slow guard includes newly audited player Heavy rows",()=> { Check(StatusCatalog.SlowCandidates.Count==37 && StatusCatalog.AuditedSlow.Count==40); Check(new uint[]{5044,5175,5356}.All(StatusCatalog.AuditedSlow.Contains)); Check(!StatusCatalog.AuditedSlow.Contains(630)); }); Test("permission, action and cooldown identifiers stay distinct",()=> { Check(NativeIds.MovementPermissionChecks.Contains(96)); Check(!NativeIds.MovementPermissionChecks.Contains(14)); Check(NativeIds.MudraActions.Contains(2259)); Check(!Policies.IsMudra(233)); // 233 is Ten's animation timeline, not its action ID. Check(!Policies.IsMudra(-1)); Check(NativeIds.GlobalCooldownGroup==57); }); Test("render diagnostics scope is independent of action diagnostics scope",()=> { Check(!NativeTargetRingScope.Active); using(NativeTargetRingScope.Enter(true)) { Check(NativeTargetRingScope.Active && !ActionRangeCheckScope.Active); using(NativeTargetRingScope.Enter(false)) Check(!NativeTargetRingScope.Active); Check(NativeTargetRingScope.Active); } Check(!NativeTargetRingScope.Active); }); Test("render diagnostics scope unwinds after errors",()=> { using(NativeTargetRingScope.Enter(true)) { bool workerState=true; var worker=new Thread(()=>workerState=NativeTargetRingScope.Active); worker.Start(); worker.Join(); Check(!workerState); } try { using var scope=NativeTargetRingScope.Enter(true); throw new InvalidOperationException(); } catch(InvalidOperationException) { } Check(!NativeTargetRingScope.Active); }); Test("shared range resource releases its leases independently",()=> { int starts=0,stops=0; var shared=new SharedModuleResource(s=>{starts++;s.Defer(()=>stops++);}); var a=new ResourceScope(); var b=new ResourceScope(); shared.Acquire(a); shared.Acquire(b); Check(starts==1); a.Dispose(); Check(stops==0); b.Dispose(); Check(stops==1); using var c=new ResourceScope(); shared.Acquire(c); Check(starts==2); }); Test("shared range startup failure cleans its partial resources",()=> { var stopped=false; var shared=new SharedModuleResource(s=>{s.Defer(()=>stopped=true);throw new InvalidOperationException();}); using var owner=new ResourceScope(); Throws(()=>shared.Acquire(owner)); Check(stopped); }); Test("failed shared cleanup prevents another module from attaching again",()=> { int starts=0; var shared=new SharedModuleResource(s=>{starts++;s.Defer(()=>throw new InvalidOperationException("native cleanup failed"));}); var first=new ResourceScope(); shared.Acquire(first); Throws(first.Dispose); using var second=new ResourceScope(); Throws(()=>shared.Acquire(second)); Check(starts==1); }); Test("explicit user validation persists across enabling and disabling",()=> { using var manager=new ModuleManager(); manager.Register(1,_=>{}); ValidationCatalog.Apply(manager,ValidationCatalog.ClientVersion); Check(StatusPresentation.Summary(manager.Status[1])=="已实测有效 · 已关闭"); Check(manager.Enable(1)); Check(StatusPresentation.Summary(manager.Status[1])=="已实测有效 · 已开启"); manager.Disable(1); Check(StatusPresentation.Summary(manager.Status[1])=="已实测有效 · 已关闭"); Check(!StatusPresentation.ShowDetail(manager.Status[1])); }); Test("untested unavailable and runtime failures have distinct wording",()=> { using var manager=new ModuleManager(); manager.Register(14,_=>{}); manager.Register(20,_=>{}); Check(StatusPresentation.Summary(manager.Status[14])=="待复测 · 已关闭"); Check(StatusPresentation.Summary(manager.Status[20])=="待实测 · 已关闭"); Check(StatusPresentation.Summary(manager.Status[8])=="暂不可用 · 已关闭"); manager.Status[14].State=ModuleState.Failed; Check(StatusPresentation.ShowDetail(manager.Status[14])); Check(StatusPresentation.Summary(manager.Status[14]).Contains("运行异常")); }); Test("validation list matches user confirmation and is version-specific",()=> { Check(ValidationCatalog.UserConfirmed.SetEquals(new[]{1,2,3,9,10,11,13,14,15,16,17,19,27,28,29})); using var manager=new ModuleManager(); manager.Register(1,_=>{}); ValidationCatalog.Apply(manager,"other-version"); Check(manager.Status[1].Adaptation!=Adaptation.Verified); manager.Register(14,_=>{}); ValidationCatalog.Apply(manager,ValidationCatalog.ClientVersion); Check(manager.Status[14].Adaptation==Adaptation.Verified); manager.Status[14].Adaptation=Adaptation.Testable; ValidationCatalog.Apply(manager,ValidationCatalog.BaselineClientVersion); Check(manager.Status[14].Adaptation==Adaptation.Verified); }); Test("height preset is inactive until applied and second click returns zero",()=> { var height=new HeightActivation(); var preset=-7f; Check(height.Applied==0 && !height.IsApplied); height.Toggle(true,preset); Check(height.Applied==-7 && height.IsApplied); preset=-3; Check(height.Applied==-7); height.Toggle(true,preset); Check(height.Applied==0 && !height.IsApplied); height.Toggle(true,preset); Check(height.Applied==-3); }); Test("height activation rejects disabled module and resets between sessions",()=> { var height=new HeightActivation(); height.Toggle(false,-7); Check(height.Applied==0); height.Toggle(true,-7); height.Reset(); Check(height.Applied==0); height.Toggle(true,float.NaN); Check(height.Applied==0); height.Toggle(true,999); Check(height.Applied==10); height.Toggle(false,-4); Check(height.Applied==0); Check(new HeightActivation().Applied==0); }); Test("service bar distinguishes prepared applied and dungeon-suppressed height",()=> { var frame=new GameFrame(true,123,false,false,new(true,1,1,3)); var prepared=ServiceBarPresentation.Build(frame,ModuleState.Enabled,ModuleState.Disabled,ModuleState.Disabled,-7,0); Check(prepared.Shown && prepared.Text.Contains("Y:SET") && prepared.Tooltip.Contains("effective 0")); var active=ServiceBarPresentation.Build(frame,ModuleState.Enabled,ModuleState.Enabled,ModuleState.Enabled,-3,-7); Check(active.Text=="IC Y:-7 D:1 F:3" && active.Tooltip.Contains("Preset -3, applied -7")); var special=ServiceBarPresentation.Build(frame with {Dungeon=new(true,1,1,10)},ModuleState.Enabled,ModuleState.Enabled,ModuleState.Enabled,-7,-7); Check(special.Text.Contains("Y:HOLD") && special.Text.Contains("F:10*") && special.Tooltip.Contains("effective 0")); Check(new[]{prepared,active,special}.All(s=>s.Text.All(c=>c<128) && s.Tooltip.All(c=>c<128))); }); Test("service bar clears stale state during logout and reports disabled or failed modules",()=> { var frame=new GameFrame(true,123,false,false,DungeonSnapshot.Empty); var outside=ServiceBarPresentation.Build(frame,ModuleState.Enabled,ModuleState.Enabled,ModuleState.Enabled,-7,-7); Check(outside.Text.Contains("Y:HOLD") && outside.Text.Contains("F:--")); var off=ServiceBarPresentation.Build(frame,ModuleState.Disabled,ModuleState.Disabled,ModuleState.Disabled,-7,0); Check(off.Text=="IC Y:OFF D:0 F:OFF"); var failed=ServiceBarPresentation.Build(frame,ModuleState.Failed,ModuleState.Disabled,ModuleState.Failed,-7,0); Check(failed.Text.Contains("Y:ERR") && failed.Text.Contains("F:ERR")); Check(!ServiceBarPresentation.Build(frame with {Ready=false},ModuleState.Enabled,ModuleState.Enabled,ModuleState.Enabled,-7,-7).Shown); }); Test("native up capture uses declared length and excludes the 32-byte send prefix",()=> { var payload=Enumerable.Range(0,28).Select(i=>(byte)i).ToArray(); var memory=PacketMemoryFixture.Up(payload); var result=NativePacketCapture.Up(memory.Read,0x1000,_=>true); Check(result.Error==null && result.Packet is {Complete:true,Opcode:838,Target:null}); Check(result.Packet!.Payload.SequenceEqual(payload)); Check(memory.Reads.SequenceEqual(new[]{((nint)0x1000,16),((nint)0x1020,28)})); result.Packet.Payload[0]=255; Check(memory.Blocks[0x1020][0]==0); }); Test("native down capture follows owned message and excludes the IPC header",()=> { byte[] payload=[1,3,5,7,9]; var memory=PacketMemoryFixture.Down(payload); var result=NativePacketCapture.Down(memory.Read,0x1000,_=>true); Check(result.Error==null && result.Packet is {Complete:true,Opcode:354,Target:0x12345678}); Check(result.Packet!.Payload.SequenceEqual(payload)); Check(memory.Reads.SequenceEqual(new[]{((nint)0x1010,8),((nint)0x2020,32),((nint)0x3000,4),((nint)0x3010,5)})); }); Test("native invalid lengths fall back without reading or allocating payloads",()=> { foreach(var length in new ulong[]{0,3,15,65553,0x100000010,ulong.MaxValue}) { var up=PacketMemoryFixture.Up([],length); var result=NativePacketCapture.Up(up.Read,0x1000,_=>true); Check(result.Packet is {Complete:false,Payload.Length:0,Issue:not null}); Check(up.Reads.Count==1); var down=PacketMemoryFixture.Down([],length); var received=NativePacketCapture.Down(down.Read,0x1000,_=>true); Check(length<4 ? received.Error!=null : received.Packet is {Complete:false,Payload.Length:0,Issue:not null}); Check(down.Reads.All(r=>r.Address!=0x3010)); } }); Test("native truncated and partial memory reads never become complete packets",()=> { var up=PacketMemoryFixture.Up([1,2],32); var down=PacketMemoryFixture.Down([1,2],32); Check(NativePacketCapture.Up(up.Read,0x1000,_=>true).Packet is {Complete:false,Payload.Length:0}); Check(NativePacketCapture.Down(down.Read,0x1000,_=>true).Packet is {Complete:false,Payload.Length:0}); up.ShortReads=true; Check(NativePacketCapture.Up(up.Read,0x1000,_=>true).Error!=null); down.Blocks.Remove(0x2020); Check(NativePacketCapture.Down(down.Read,0x1000,_=>true).Error!=null); }); Test("native filters avoid copying payload and paused buffer rejects new records",()=> { var up=PacketMemoryFixture.Up([1,2,3]); var down=PacketMemoryFixture.Down([1,2,3]); Check(NativePacketCapture.Up(up.Read,0x1000,_=>false).Packet==null && up.Reads.Count==1); Check(NativePacketCapture.Down(down.Read,0x1000,_=>false).Packet==null && down.Reads.Count==3); var buffer=new PacketBuffer(); Check(!buffer.Accepts(PacketDirection.Up)); buffer.Paused=false; buffer.DirectionFilter=PacketDirection.Up; buffer.OpcodeFilter=838; Check(buffer.Accepts(PacketDirection.Up) && buffer.Accepts(PacketDirection.Up,838)); Check(!buffer.Accepts(PacketDirection.Down) && !buffer.Accepts(PacketDirection.Up,839)); buffer.Paused=true; Check(!buffer.Add(PacketDirection.Up,838,"v",null,null,[1])); }); Test("native payload bounds include true empty payload and exact 64 KiB maximum",()=> { Check(NativePacketCapture.TryPayloadLength(16,out var empty) && empty==0); Check(NativePacketCapture.TryPayloadLength(65552,out var maximum) && maximum==65536); Check(!NativePacketCapture.TryPayloadLength(65553,out _)); var zero=PacketMemoryFixture.Down([]); Check(NativePacketCapture.Down(zero.Read,0x1000,_=>true).Packet is {Complete:true,Payload.Length:0}); var full=PacketMemoryFixture.Up(new byte[65536]); Check(NativePacketCapture.Up(full.Read,0x1000,_=>true).Packet is {Complete:true,Payload.Length:65536}); }); Test("native invalid pointers are rejected before memory access",()=> { var memory=new PacketMemoryFixture(); foreach(var pointer in new nint[]{0,-1,nint.MaxValue}) { Check(NativePacketCapture.Up(memory.Read,pointer,_=>true).Error!=null); Check(NativePacketCapture.Down(memory.Read,pointer,_=>true).Error!=null); } Check(memory.Reads.Count==0); var down=PacketMemoryFixture.Down([]); BinaryPrimitives.WriteUInt64LittleEndian(down.Blocks[0x1010],ulong.MaxValue); Check(NativePacketCapture.Down(down.Read,0x1000,_=>true).Error!=null && down.Reads.Count==1); }); Test("native downstream target normalization matches reserved IDs",()=> { Check(NativePacketCapture.NormalizeTarget(0x12345678)==0x12345678); Check(NativePacketCapture.NormalizeTarget(0x5FF80000)==0x5FF80002); Check(NativePacketCapture.NormalizeTarget(0x5FF80001)==0x5FF80001); Check(NativePacketCapture.NormalizeTarget(0xFFFFFFFF)==0x5FF80001); }); Test("capture failure reason survives export and legacy metadata remains incomplete",()=> { var path=Path.Combine(AppContext.BaseDirectory,"capture-issue-"+Guid.NewGuid()+".jsonl"); try { var buffer=new PacketBuffer {Paused=false}; buffer.Add(PacketDirection.Up,838,"v",null,null,[],false,"test failed copy"); PacketFiles.ExportAtomic(path,buffer.Snapshot()); var packet=PacketFiles.Import(path).Single(); Check(!packet.PayloadComplete && packet.CaptureIssue=="test failed copy"); var legacy=System.Text.Json.JsonSerializer.Serialize(packet with {CaptureIssue=null}); legacy=legacy.Replace(",\"CaptureIssue\":null",""); File.WriteAllText(path,legacy); var old=PacketFiles.Import(path).Single(); Check(!old.PayloadComplete && old.CaptureIssue==null); } finally { File.Delete(path); } }); Test("full-payload buffers bound total bytes across capture import and clear",()=> { var buffer=new PacketBuffer(10,16,5) {Paused=false}; Check(buffer.Add(PacketDirection.Up,1,"v",null,null,[1,2,3])); Check(buffer.Add(PacketDirection.Down,2,"v",null,null,[4,5,6])); Check(buffer.PayloadBytes==3 && buffer.Dropped==1 && buffer.Snapshot().Single().Opcode==2); Check(!buffer.Add(PacketDirection.Up,3,"v",null,null,new byte[6])); Check(buffer.PayloadBytes==3); var records=Enumerable.Range(0,4).Select(i=>new PacketRecord(i,i,DateTimeOffset.UtcNow,PacketDirection.Up,i,"v",null,null,new byte[2])).ToArray(); buffer.Replace(records); Check(buffer.PayloadBytes==4 && buffer.Dropped==2 && buffer.Snapshot().Select(p=>p.Opcode).SequenceEqual([2,3])); buffer.Clear(); Check(buffer.PayloadBytes==0 && buffer.Dropped==0); }); Test("current movement and ping payload lengths are version-bound",()=> { const string v=PacketParser.AuditedClientVersion; Check(PacketParser.Parse("UP_UpdatePositionHandler",new byte[24],v).Parsed); Check(!PacketParser.Parse("UP_UpdatePositionHandler",new byte[40],v).Parsed); Check(!PacketParser.Parse("UP_UpdatePositionHandler",new byte[23],v).Parsed); Check(PacketParser.Parse("UP_UpdatePositionHandler",new byte[40]).Parsed); Check(PacketParser.Parse("UP_PingReq",new byte[32],v).Parsed); Check(!PacketParser.Parse("UP_PingReq",new byte[28],v).Parsed); Check(PacketParser.Parse("DOWN_PingRsp",new byte[32],v).Parsed); Check(!PacketParser.Parse("DOWN_PingRsp",new byte[16],v).Parsed); var bad=new byte[24]; BinaryPrimitives.WriteSingleLittleEndian(bad.AsSpan(8),float.NaN); Check(!PacketParser.Parse("UP_UpdatePositionHandler",bad,v).Parsed); }); Test("verified PingRsp fills only missing mappings for the audited version",()=> { const string v=PacketParser.AuditedClientVersion; var map=new OpcodeMap(); map.Load($$$"""{"{{{v}}}":{"UP_PingReq":[838]}}""",v,"test"); Check(map.Resolve(v,PacketDirection.Down,338)=="DOWN_PingRsp"); Check(map.Snapshot.Source.Contains("本机核实")); map.Load($$$"""{"{{{v}}}":{"DOWN_Explicit":[338]}}""",v,"test"); Check(map.Resolve(v,PacketDirection.Down,338)=="DOWN_Explicit"); map.Load($$$"""{"{{{v}}}":{"DOWN_PingRsp":[339]}}""",v,"test"); Check(map.Resolve(v,PacketDirection.Down,338)==null); map.Load("""{"other":{"UP_PingReq":[838]}}""","other","test"); Check(map.Resolve("other",PacketDirection.Down,338)==null); Throws(()=>map.Load($$$"""{"{{{v}}}":{}}""",v,"test")); }); Test("current Ping pairs require full exact-length payload and matching echo",()=> { const string v=PacketParser.AuditedClientVersion; var map=new OpcodeMap(); map.Load($$$"""{"{{{v}}}":{"UP_PingReq":[838]}}""",v,"test"); var request=new byte[32]; var response=new byte[32]; BinaryPrimitives.WriteUInt32LittleEndian(request,14606677); BinaryPrimitives.WriteUInt32LittleEndian(response,14606677); BinaryPrimitives.WriteUInt32LittleEndian(response.AsSpan(4),333); // not RTT; do not use it as elapsed time var time=DateTimeOffset.UtcNow; var a=new PacketRecord(1,0,time,PacketDirection.Up,838,v,null,null,request); var b=new PacketRecord(2,0,time.AddMilliseconds(83),PacketDirection.Down,338,v,null,null,response); var pairs=PacketCorrelator.Ping([a,b],map); Check(pairs.Length==1 && pairs[0].Elapsed.TotalMilliseconds==83); Check(PacketCorrelator.Ping([a with {Payload=new byte[28]},b],map).Length==0); Check(PacketCorrelator.Ping([a,b with {PayloadComplete=false}],map).Length==0); Check(PacketCorrelator.Ping([a,b with {Payload=new byte[32]}],map).Length==0); }); Test("audited protocol additions preserve explicit opcode and name assignments",()=> { var down=new Dictionary{{671,"DOWN_Explicit"},{100,"DOWN_UpdateHater"}}; Check(CurrentProtocolEvidence.AddMissing("other",down)==0); Check(CurrentProtocolEvidence.AddMissing(PacketParser.AuditedClientVersion,down)==5); Check(down[671]=="DOWN_Explicit" && !down.ContainsKey(558)); Check(down[697]=="DOWN_EffectResultBasic" && down[338]=="DOWN_PingRsp"); Check(CurrentProtocolEvidence.AddMissing(PacketParser.AuditedClientVersion,down)==0); }); Test("enmity parsers respect capacities and native byte value stride",()=> { foreach(var (name,capacity) in new[]{("DOWN_UpdateHate",8),("DOWN_UpdateHater",32)}) { var payload=new byte[8+capacity*8]; payload[0]=(byte)capacity; for(var i=0;i { var payload=new byte[24]; payload[0]=1; BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(4),123); BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(12),456); payload[16]=7; var result=PacketParser.Parse("DOWN_EffectResultBasic",payload,PacketParser.AuditedClientVersion); Check(result.Parsed && result.Summary.Contains("序列=123") && result.Summary.Contains("HP=456") && result.Summary.Contains("目标序号=7")); payload[0]=2; Check(!PacketParser.Parse("DOWN_EffectResultBasic",payload,PacketParser.AuditedClientVersion).Parsed); payload[0]=0; Check(PacketParser.Parse("DOWN_EffectResultBasic",payload,PacketParser.AuditedClientVersion).Parsed); Check(!PacketParser.Parse("DOWN_EffectResultBasic",payload[..^1],PacketParser.AuditedClientVersion).Parsed); Check(!PacketParser.Parse("DOWN_EffectResultBasic",payload,"other").Parsed); }); Test("new protocol parsers bound arbitrary lengths and counts",()=> { var random=new Random(671697558); string[] names=["DOWN_UpdateHate","DOWN_UpdateHater","DOWN_EffectResultBasic"]; for(var i=0;i<1500;i++) { var payload=new byte[random.Next(0,300)]; random.NextBytes(payload); PacketParser.Parse(names[i%3],payload,PacketParser.AuditedClientVersion); } }); Test("remaining fixed packets decode only proven fields and retain 64-bit tag identity",()=> { var p=new byte[16]; p[0]=31; BinaryPrimitives.WriteUInt16LittleEndian(p.AsSpan(2),100); BinaryPrimitives.WriteUInt32LittleEndian(p.AsSpan(8),123456); Check(PacketParser.Parse("DOWN_JobGauge",p,PacketParser.AuditedClientVersion).Summary.Contains("职业 ID=31")); var info=PacketParser.Parse("DOWN_UpdateClassInfo",p,PacketParser.AuditedClientVersion); Check(info.Parsed && info.Summary.Contains("当前等级=100") && info.Summary.Contains("经验=123456")); p[0]=2; BinaryPrimitives.WriteUInt64LittleEndian(p.AsSpan(8),0x123456789ABCDEF0); var tag=PacketParser.Parse("DOWN_FirstAttack",p,PacketParser.AuditedClientVersion); Check(tag.Parsed && tag.Summary.Contains("类型=2") && tag.Summary.Contains("123456789ABCDEF0")); foreach(var name in new[]{"DOWN_JobGauge","DOWN_UpdateClassInfo","DOWN_FirstAttack"}) { Check(!PacketParser.Parse(name,p[..15],PacketParser.AuditedClientVersion).Parsed); Check(!PacketParser.Parse(name,new byte[17],PacketParser.AuditedClientVersion).Parsed); Check(!PacketParser.Parse(name,p,"other").Parsed); } }); Test("gauge evidence label preserves explicit ActorGauge mapping",()=> { var map=new Dictionary{{8,"DOWN_ActorGauge"}}; CurrentProtocolEvidence.AddMissing(PacketParser.AuditedClientVersion,map); Check(map[8]=="DOWN_ActorGauge" && map[614]=="DOWN_JobGauge"); Check(map[788]=="DOWN_UpdateClassInfo" && map[214]=="DOWN_FirstAttack"); }); Test("stage 2 upstream action, trigger and event tier parsers enforce audited sizes and capacities",()=> { const string v=PacketParser.AuditedClientVersion; var act=new byte[32]; BinaryPrimitives.WriteUInt32LittleEndian(act,7535); act[5]=1; BinaryPrimitives.WriteUInt16LittleEndian(act.AsSpan(6),180); Check(PacketParser.Parse("UP_ActionSend",act,v).Summary.Contains("请求序号=180")); Check(!PacketParser.Parse("UP_ActionSend",new byte[24],v).Parsed && !PacketParser.Parse("UP_ActionSend",act,"other").Parsed); var pos=new byte[24]; BinaryPrimitives.WriteUInt32LittleEndian(pos,3573); pos[5]=1; BinaryPrimitives.WriteUInt16LittleEndian(pos.AsSpan(6),181); BinaryPrimitives.WriteSingleLittleEndian(pos.AsSpan(12),10f); BinaryPrimitives.WriteSingleLittleEndian(pos.AsSpan(16),20f); BinaryPrimitives.WriteSingleLittleEndian(pos.AsSpan(20),30f); Check(PacketParser.Parse("UP_ActionSendPos",pos,v).Parsed); Check(!PacketParser.Parse("UP_ActionSendPos",new byte[32],v).Parsed); BinaryPrimitives.WriteSingleLittleEndian(pos.AsSpan(16),float.NaN); Check(!PacketParser.Parse("UP_ActionSendPos",pos,v).Parsed); var trig=new byte[32]; BinaryPrimitives.WriteUInt32LittleEndian(trig,3); BinaryPrimitives.WriteUInt32LittleEndian(trig.AsSpan(4),1); Check(PacketParser.Parse("UP_ClientTrigger",trig,v).Summary.Contains("指令=3")); Check(!PacketParser.Parse("UP_ClientTrigger",new byte[24],v).Parsed); foreach(var (suffix,cap,len) in new[]{("",2,16),("4",4,24),("8",8,40),("16",16,72),("32",32,136),("64",64,264),("128",128,520),("255",255,1032)}) { foreach(var prefix in new[]{"UP_EventFinish","UP_EventAction"}) { var p=new byte[len]; p[7]=(byte)cap; Check(PacketParser.Parse(prefix+suffix,p,v).Parsed); Check(!PacketParser.Parse(prefix+suffix,new byte[len-1],v).Parsed); if(cap<255) { p[7]=(byte)(cap+1); Check(!PacketParser.Parse(prefix+suffix,p,v).Parsed); } } } }); Test("stage 2 downstream combat, status and spawn parsers enforce audited sizes, counts and finite floats",()=> { const string v=PacketParser.AuditedClientVersion; Check(PacketParser.Parse("DOWN_ActorControl",new byte[24],v).Parsed && !PacketParser.Parse("DOWN_ActorControl",new byte[32],v).Parsed); Check(PacketParser.Parse("DOWN_ActorControlSelf",new byte[40],v).Parsed && !PacketParser.Parse("DOWN_ActorControlSelf",new byte[32],v).Parsed); Check(PacketParser.Parse("DOWN_ActorControlTarget",new byte[32],v).Parsed && !PacketParser.Parse("DOWN_ActorControlTarget",new byte[24],v).Parsed); Check(PacketParser.Parse("DOWN_ActorDelete",new byte[8],v).Parsed && !PacketParser.Parse("DOWN_ActorDelete",new byte[16],v).Parsed); var eff=new byte[124]; eff[0x21]=1; BinaryPrimitives.WriteUInt16LittleEndian(eff.AsSpan(0x18),190); BinaryPrimitives.WriteSingleLittleEndian(eff.AsSpan(0x10),0.6f); Check(PacketParser.Parse("DOWN_Effect",eff,v).Summary.Contains("请求序号=190")); eff[0x21]=2; Check(!PacketParser.Parse("DOWN_Effect",eff,v).Parsed); eff[0x21]=1; BinaryPrimitives.WriteSingleLittleEndian(eff.AsSpan(0x10),float.PositiveInfinity); Check(!PacketParser.Parse("DOWN_Effect",eff,v).Parsed); Check(!PacketParser.Parse("DOWN_Effect",new byte[120],v).Parsed); var er=new byte[96]; er[0]=1; er[0x19]=2; BinaryPrimitives.WriteUInt16LittleEndian(er.AsSpan(0x1E),1200); BinaryPrimitives.WriteSingleLittleEndian(er.AsSpan(0x24),15f); Check(PacketParser.Parse("DOWN_EffectResult",er,v).Summary.Contains("状态数=2")); er[0x19]=5; Check(!PacketParser.Parse("DOWN_EffectResult",er,v).Parsed); er[0x19]=1; BinaryPrimitives.WriteSingleLittleEndian(er.AsSpan(0x24),float.NaN); Check(!PacketParser.Parse("DOWN_EffectResult",er,v).Parsed); var legacyEr=new byte[92]; legacyEr[0]=1; Check(PacketParser.Parse("DOWN_EffectResult",legacyEr,"legacy").Parsed); var sl=new byte[384]; sl[0]=37; sl[1]=100; BinaryPrimitives.WriteUInt16LittleEndian(sl.AsSpan(20),48); BinaryPrimitives.WriteSingleLittleEndian(sl.AsSpan(24),30f); Check(PacketParser.Parse("DOWN_StatusEffectList",sl,v).Summary.Contains("非空槽位=1/30")); BinaryPrimitives.WriteSingleLittleEndian(sl.AsSpan(24),float.NaN); Check(!PacketParser.Parse("DOWN_StatusEffectList",sl,v).Parsed); var obj=new byte[64]; Check(PacketParser.Parse("DOWN_ObjectSpawn",obj,v).Parsed); BinaryPrimitives.WriteSingleLittleEndian(obj.AsSpan(0x34),float.NaN); Check(!PacketParser.Parse("DOWN_ObjectSpawn",obj,v).Parsed); var npc=new byte[656]; npc[0x86]=100; Check(PacketParser.Parse("DOWN_NpcSpawn",npc,v).Summary.Contains("等级=100")); BinaryPrimitives.WriteSingleLittleEndian(npc.AsSpan(0x200),float.NaN); Check(!PacketParser.Parse("DOWN_NpcSpawn",npc,v).Parsed); var random=new Random(20261012); string[] names=["UP_ActionSend","UP_ActionSendPos","UP_ClientTrigger","UP_EventFinish16","UP_EventAction32","DOWN_ActorControl","DOWN_ActorControlSelf","DOWN_ActorControlTarget","DOWN_Effect","DOWN_EffectResult","DOWN_StatusEffectList","DOWN_ActorDelete","DOWN_ObjectSpawn","DOWN_NpcSpawn"]; for(var i=0;i<1500;i++) { var payload=new byte[random.Next(0,700)]; random.NextBytes(payload); PacketParser.Parse(names[i%names.Length],payload,v); } }); Test("action correlator pairs UP_ActionSend and UP_ActionSendPos with DOWN_Effect by positive RequestId",()=> { const string v=PacketParser.AuditedClientVersion; var map=new OpcodeMap(); map.Load($$$"""{"{{{v}}}":{"UP_ActionSend":[913],"UP_ActionSendPos":[891],"DOWN_Effect":[787]}}""",v,"test"); var t=DateTimeOffset.UtcNow; var send=new byte[32]; BinaryPrimitives.WriteUInt16LittleEndian(send.AsSpan(6),179); var sendPos=new byte[24]; BinaryPrimitives.WriteUInt16LittleEndian(sendPos.AsSpan(6),180); var eff1=new byte[124]; eff1[0x21]=1; BinaryPrimitives.WriteUInt16LittleEndian(eff1.AsSpan(0x18),179); var eff2=new byte[124]; eff2[0x21]=1; BinaryPrimitives.WriteUInt16LittleEndian(eff2.AsSpan(0x18),180); var effZero=new byte[124]; effZero[0x21]=1; var records=new[]{ new PacketRecord(10,1,t,PacketDirection.Up,913,v,null,null,send), new PacketRecord(11,2,t.AddMilliseconds(50),PacketDirection.Down,787,v,null,null,effZero), new PacketRecord(12,3,t.AddMilliseconds(95),PacketDirection.Down,787,v,null,null,eff1), new PacketRecord(13,4,t.AddMilliseconds(200),PacketDirection.Up,891,v,null,null,sendPos), new PacketRecord(14,5,t.AddMilliseconds(280),PacketDirection.Down,787,v,null,null,eff2) }; var pairs=PacketCorrelator.Actions(records,map); Check(pairs.Length==2 && pairs[0].Request==10 && pairs[0].Response==12 && pairs[0].Elapsed.TotalMilliseconds==95); Check(pairs[1].Request==13 && pairs[1].Response==14 && pairs[1].Elapsed.TotalMilliseconds==80); }); Test("stage 3 M0 adaptation manifest distinguishes missing implementation, version mismatch, offset mismatch, signature issues and runtime faults",()=> { const string v=PacketParser.AuditedClientVersion; using var manager=new ModuleManager(); foreach(var f in Features.All.Where(f=>f.Id!=20)) manager.Register(f.Id,_=>{}); var okReport=AdaptationManifest.Evaluate(manager,v,_=>1,true); Check(okReport.ReadyCount==23 && okReport.BlockedCount==1 && okReport.ActiveSendReady); Check(okReport.Entries.Single(m=>m.FeatureId==20).Issue==AdaptationIssue.MissingImplementation); var baseReport=AdaptationManifest.Evaluate(manager,PacketParser.BaselineClientVersion,_=>1,true); Check(baseReport.ReadyCount==23 && baseReport.BlockedCount==1 && baseReport.ActiveSendReady); AdaptationManifest.ApplyGate(manager,okReport); Check(manager.Enable(8) && manager.Enable(36) && !manager.Enable(20)); var verReport=AdaptationManifest.Evaluate(manager,"2026.99.99.0000.0000",_=>1,true); Check(!verReport.ActiveSendReady && verReport.ActiveSendSummary.Contains("仅核对")); Check(verReport.Entries.Single(m=>m.FeatureId==8).Issue==AdaptationIssue.VersionMismatch); Check(verReport.Entries.Single(m=>m.FeatureId==36).Issue==AdaptationIssue.VersionMismatch); Check(verReport.Entries.Single(m=>m.FeatureId==1).Issue==AdaptationIssue.None); Check(verReport.Entries.Single(m=>m.FeatureId==37).Issue==AdaptationIssue.None); AdaptationManifest.ApplyGate(manager,verReport); Check(!manager.IsEnabled(8) && !manager.Enable(8) && !manager.Enable(36)); Check(manager.Enable(1) && manager.Enable(37)); AdaptationManifest.ApplyGate(manager,okReport); Check(manager.Enable(8)); var offsetReport=AdaptationManifest.Evaluate(manager,v,_=>1,false); Check(offsetReport.Entries.Single(m=>m.FeatureId==8).Issue==AdaptationIssue.OffsetMismatch); Check(offsetReport.Entries.Single(m=>m.FeatureId==14).Issue==AdaptationIssue.None); var multiSend=AdaptationManifest.Evaluate(manager,v,sig=>sig==AdaptationManifest.ActiveSendReceiverSignature ? 2 : 1,true); Check(!multiSend.ActiveSendReady && multiSend.ActiveSendSummary.Contains("多重匹配")); var missingSig=AdaptationManifest.Evaluate(manager,v,sig=>sig.StartsWith("40 53 56 41 56",StringComparison.Ordinal) ? 0 : 1,true); Check(missingSig.Entries.Single(m=>m.FeatureId==8).Issue==AdaptationIssue.SignatureMissing); manager.Status[14].State=ModuleState.Failed; manager.Status[14].Detail="hook target fault"; var faultReport=AdaptationManifest.Evaluate(manager,v,_=>1,true); var m14=faultReport.Entries.Single(m=>m.FeatureId==14); Check(m14.Issue==AdaptationIssue.RuntimeFault && m14.Summary.Contains("hook target fault")); }); Test("stage 3 M4 native send buffer layout and audited encoders roundtrip through NativePacketCapture and PacketParser",()=> { const string v=PacketParser.AuditedClientVersion; var map=new OpcodeMap(); map.Load($$$"""{"{{{v}}}":{"UP_ActionSend":[913],"UP_EventStart":[720],"UP_ClientTrigger":[963],"UP_EventFinish":[501],"UP_EventFinish4":[502],"UP_EventFinish8":[503],"UP_EventFinish16":[504],"UP_EventFinish32":[505],"UP_EventFinish64":[506],"UP_EventFinish128":[507],"UP_EventFinish255":[508],"UP_EventAction":[601],"UP_EventAction4":[602],"UP_EventAction8":[603],"UP_EventAction16":[604],"UP_EventAction32":[605],"UP_EventAction64":[606],"UP_EventAction128":[607],"UP_EventAction255":[608],"DOWN_Effect":[787],"DOWN_EventStart":[737],"DOWN_EventFinish":[217]}}""",v,"test"); var act=AuditedPacketEncoder.EncodeActionSend(map,v,7535,1,321,0x10001234); Check(act.Name=="UP_ActionSend" && act.Opcode==913 && act.Payload.Length==32 && act.ExpectedResponseOpcode==787); Check(PacketParser.Parse(act.Name,act.Payload,v).Summary.Contains("请求序号=321")); var raw=act.NativeBuffer; Check(raw.Length==NativeSendPacket.NativePrefixSize+act.Payload.Length); var mem=new PacketMemoryFixture(); mem.Blocks[0x4000]=raw[..16]; mem.Blocks[0x4020]=raw[0x20..(0x20+act.Payload.Length)]; var captured=NativePacketCapture.Up(mem.Read,0x4000,_=>true); Check(captured.Error==null && captured.Packet is {Complete:true,Opcode:913}); Check(captured.Packet!.Payload.SequenceEqual(act.Payload)); var evStart=AuditedPacketEncoder.EncodeEventStart(map,v,0x1000200030004000,0x00050001,7); Check(evStart.Payload.Length==16 && evStart.ExpectedResponseOpcode==737 && PacketParser.Parse(evStart.Name,evStart.Payload,v).Parsed); var trig=AuditedPacketEncoder.EncodeClientTrigger(map,v,3,1,2,3,4,0xABCDEF01); Check(trig.Payload.Length==32 && PacketParser.Parse(trig.Name,trig.Payload,v).Summary.Contains("指令=3")); foreach(var (count,expectedName,expectedLen) in new[]{(0,"UP_EventFinish",16),(2,"UP_EventFinish",16),(3,"UP_EventFinish4",24),(5,"UP_EventFinish8",40),(9,"UP_EventFinish16",72),(17,"UP_EventFinish32",136),(33,"UP_EventFinish64",264),(65,"UP_EventFinish128",520),(129,"UP_EventFinish255",1032)}) { var args=Enumerable.Range(1,count).Select(i=>(uint)i).ToArray(); var fin=AuditedPacketEncoder.EncodeEventFinish(map,v,0x000A0001,0,0,args); Check(fin.Name==expectedName && fin.Payload.Length==expectedLen && PacketParser.Parse(fin.Name,fin.Payload,v).Parsed); var ea=AuditedPacketEncoder.EncodeEventAction(map,v,0x000A0001,0,0,args); Check(ea.Name==expectedName.Replace("Finish","Action") && ea.Payload.Length==expectedLen && PacketParser.Parse(ea.Name,ea.Payload,v).Parsed); } Throws(()=>AuditedPacketEncoder.EncodeActionSend(map,v,1,1,0,0x10001234)); Throws(()=>AuditedPacketEncoder.EncodeEventFinish(map,v,1,0,0,new uint[256])); }); Test("stage 3 M4 ActiveSendScope tags plugin-initiated packets without affecting normal native Zone traffic",()=> { using var pipeline=new NetworkPipeline(PacketParser.AuditedClientVersion); List observed=[]; using var sub=pipeline.Subscribe(NetworkPhase.BeforeSend,obs=>observed.Add(obs.Context)); Check(pipeline.Send(()=>1,_=>new(new(913,null,new byte[32],true),null))==1); using(ActiveSendScope.Enter()) { Check(ActiveSendScope.Active); Check(pipeline.Send(()=>1,_=>new(new(913,null,new byte[32],true),null))==1); } Check(!ActiveSendScope.Active); Check(observed.Count==2); Check(observed[0].Origin==PacketOrigin.NativeZone && observed[0].Phase==NetworkPhase.BeforeSend); Check(observed[1].Origin==PacketOrigin.PluginActiveSend && observed[1].Phase==NetworkPhase.BeforeSend); }); Test("stage 3 M4 ActivePacketDispatcher enforces rate limit, queue bounds, session/territory gate, cancel semantics, response correlation and recursive pump guard",()=> { const string v=PacketParser.AuditedClientVersion; var map=new OpcodeMap(); map.Load($$$"""{"{{{v}}}":{"UP_ActionSend":[913],"UP_ClientTrigger":[963],"UP_EventStart":[720],"DOWN_Effect":[787],"DOWN_EventStart":[737]}}""",v,"test"); using var pipeline=new NetworkPipeline(v); long now=1000; var session=new SendSession(true,1108,(nint)0x2000,0x10000001); byte nextNativeResult=1; var dispatcher=new ActivePacketDispatcher(v,()=>now,maxQueue:4,minIntervalMs:100,queueTimeoutMs:1000,responseTimeoutMs:2000); using var sub=dispatcher.Attach(pipeline); byte NativeSender(ReadOnlySpan buf) { var opcode=BinaryPrimitives.ReadInt32LittleEndian(buf[..4]); var payload=buf[NativeSendPacket.NativePrefixSize..].ToArray(); return pipeline.Send(()=>nextNativeResult,_=>new(new(opcode,null,payload,true),null)); } var tx1=dispatcher.Enqueue(AuditedPacketEncoder.EncodeActionSend(map,v,7,1,501,0x10000001),session).Id; var tx2=dispatcher.Enqueue(AuditedPacketEncoder.EncodeActionSend(map,v,7,1,502,0x10000001),session).Id; var tx3=dispatcher.Enqueue(AuditedPacketEncoder.EncodeClientTrigger(map,v,3,1),session).Id; Check(dispatcher.PendingQueueCount==3); Check(dispatcher.Pump(session,NativeSender)?.Id==tx1); Check(!dispatcher.Cancel(tx1)); // already SentNative Check(dispatcher.Cancel(tx3,"test cancel")); var snap1=dispatcher.Snapshot(); Check(snap1.Single(s=>s.Id==tx1) is {State:SendTransactionState.SentNative,NativeResult:1}); Check(snap1.Single(s=>s.Id==tx2).State==SendTransactionState.Queued); Check(snap1.Single(s=>s.Id==tx3).State==SendTransactionState.Canceled); // Rate limit prevents sending tx2 before +100ms now=1050; Check(dispatcher.Pump(session,NativeSender)==null); // Confirm tx1 via DOWN_Effect with matching RequestId=501 and verify recursive Pump is forbidden inside Received callback var eff=new byte[124]; eff[0x21]=1; BinaryPrimitives.WriteUInt16LittleEndian(eff.AsSpan(0x18),501); bool recursiveRejected=false; using(var guardSub=pipeline.Subscribe(NetworkPhase.Received,_=> { try { dispatcher.Pump(session,NativeSender); } catch(InvalidOperationException) { recursiveRejected=true; } })) { pipeline.Receive(()=>1,_=>new(new(787,0x10000001,eff,true),null)); } Check(recursiveRejected); var confirmed=dispatcher.Snapshot().Single(s=>s.Id==tx1); Check(confirmed.State==SendTransactionState.Confirmed && confirmed.RoundTripMs==50 && confirmed.ResponseObservationId>0); // Advance past rate limit and send tx2, then let it time out waiting for response now=1100; Check(dispatcher.Pump(session,NativeSender)?.Id==tx2); Check(dispatcher.Snapshot().Single(s=>s.Id==tx2).State==SendTransactionState.SentNative); now=3200; dispatcher.Pump(session,NativeSender); Check(dispatcher.Snapshot().Single(s=>s.Id==tx2).State==SendTransactionState.TimedOut); // Territory change cancels queued items before native send var tx4=dispatcher.Enqueue(AuditedPacketEncoder.EncodeActionSend(map,v,7,1,503,0x10000001),session).Id; session=new SendSession(true,1109,(nint)0x2000,0x10000001); now=3400; Check(dispatcher.Pump(session,NativeSender)==null); Check(dispatcher.Snapshot().Single(s=>s.Id==tx4).State==SendTransactionState.Canceled); // Native return 0 marks transaction RejectedNative nextNativeResult=0; var tx5=dispatcher.Enqueue(AuditedPacketEncoder.EncodeActionSend(map,v,7,1,504,0x10000001),session).Id; now=3600; Check(dispatcher.Pump(session,NativeSender)?.Id==tx5); Check(dispatcher.Snapshot().Single(s=>s.Id==tx5) is {State:SendTransactionState.RejectedNative,NativeResult:0}); }); Console.WriteLine($"{passed} tests passed.");