using System.Text.Json;
using System.IO.Pipes;
using XFEEDToolBox.Core.Galaxy;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public sealed class GalaxyPointOverridesTests
{
static JsonElement Page(long generation=9,int offset=0,int navOffset=0,int total=1,int navTotal=1,bool known=true,long id=1,int process=42)=>JsonSerializer.SerializeToElement(new{
processId=process,sourceGeneration=generation,capturedAtTick=123UL,scope="published-renderer-d50+local-navroute",navRouteViewMode=(int?)1,
d50Complete=known,navRouteComplete=known,overrideOffset=offset,overrideTotal=total,navRouteOffset=navOffset,navRouteTotal=navTotal,
overrides=total>offset?new[]{new{systemAddress=id.ToString(),minimumExtra=1f,farFade=false,pending=false,position=new[]{5d,5d,1005d},name=(string?)null,starClass="G",originalAppearance=(object?)null}}:[],
navRouteMembers=navTotal>navOffset?new[]{id.ToString()}:[]});
[Test] public void CurrentPublishedPresenceIsIndependentOfTheConsumedPendingBitAndUnknownIsNotAbsent()
{
var known=NativeGalaxyCatalog.ReadPointOverridesPages([Page()]);
Check.Equal(true,known.OverridePresence(1)!.Value);Check.Equal(false,known.OverridePresence(2)!.Value);
Check.Equal(true,known.Membership(1)!.Value);Check.Equal(false,known.Membership(2)!.Value);
Check.That(known.TryGetOverride(1,out var row)&&row is{Pending:false,MinimumExtra:1,FarFade:false,Name:null,System:null});
var unknown=NativeGalaxyCatalog.ReadPointOverridesPages([Page(total:0,navTotal:0,known:false)]);
Check.That(unknown.OverridePresence(1) is null&&unknown.Membership(1) is null&&unknown.OverridePresence(0) is null);
}
[Test] public void PagesRetainOneSnapshotAndNeverUsePartialArraysToProveAbsence()
{
var first=Page(total:2,navTotal:2);var partial=NativeGalaxyCatalog.ReadPointOverridesPages([first]);
Check.That(!partial.D50Complete&&!partial.NavRouteComplete&&partial.OverridePresence(2) is null&&partial.Membership(2) is null);
var all=NativeGalaxyCatalog.ReadPointOverridesPages([first,Page(offset:1,navOffset:1,total:2,navTotal:2,id:2)]);
Check.That(all.D50Complete&&all.NavRouteComplete&&all.Membership(2)==true&&all.Membership(3)==false);
Check.Throws<InvalidDataException>(()=>NativeGalaxyCatalog.ReadPointOverridesPages([first,Page(generation:10,offset:1,navOffset:1,total:2,navTotal:2,id:2)]));
Check.Throws<InvalidDataException>(()=>NativeGalaxyCatalog.ReadPointOverridesPages([first,Page(offset:1,navOffset:1,total:2,navTotal:2,id:1)]));
}
[Test] public void D50ForcesOnlyItsExactPublishedPositionAndDoesNotBypassStrictFilter()
{
var row=new GalaxyOriginalPointOverride((long)(GalaxyCellQueryPlan.Encode(0,0,0,100)|(1UL<<44)),1,false,false,new(5,5,1005),"Recorded","G",null);
var points=new GalaxyOriginalPointOverrides(42,9,123,0,true,true,[row],[]);
var system=row.System!;var camera=GalaxyCamera.Create(new(5,5,5),1600,900,0,0,4);
var selection=new GalaxyCatalogueSelection(GalaxyOriginalCatalogueLod.Create(new(5,5,5),1,10,.001,1,0),new(0,0,0)){PointOverrides=points};
Check.That(selection.Contains(system)&&selection.IsForced(system));
Check.That(!selection.Contains(system with{Position=new(6,5,1005)}));
Check.That(!camera.ProjectForCatalogue(system.Position).IsVisible&&selection.ApplyTo(camera).ProjectForCatalogue(system.Position).IsVisible);
var physical=system with{OriginalAppearance=new(0xffffffff,null,null,0,4,.4f,.2f,false)};
Check.Equal(.4f,(selection with{SmallMode=false}).OriginalSize(physical)!.Value);
var strict=GalaxyMapFilter.Default with{Mode=GalaxyMapMode.Map,Values=new HashSet<string>(),IncludeUnknown=false};
Check.That(!strict.Matches(system));
Check.Equal(.4f,(selection with{PointOverrides=null,SmallMode=false}).OriginalSize(physical)!.Value);
}
[Test] public void OnlyCurrentKnownD50AbsenceCanReachFreshNavRouteFlags()
{
var row=new GalaxyOriginalPointOverride(1,1,false,false,new(0,0,0),"Recorded","G",null);
var system=row.System! with{OriginalAppearance=new(0xffffffff,null,null,0,4,.4f,.2f,false)
{NavRouteMembership=false,NavRouteMembershipMode=0,NavRouteViewMode=0}};
var descriptor=GalaxyOriginalCatalogueLod.Create(new(0,0,0),1,10,.001,1,0);
GalaxyCatalogueSelection Selection(bool d50,bool nav,IEnumerable<GalaxyOriginalPointOverride> rows,IEnumerable<long> members,int view=0)=>
new(descriptor,new(0,0,0)){NavRouteViewMode=0,NavRouteMembershipMode=0,
PointOverrides=new(42,9,123,view,d50,nav,rows,members)};
Check.That(Selection(true,true,[row],[]).TryFrameFadeFlags(system,out var bypass,out var far)&&!bypass&&!far);
Check.That(Selection(true,true,[],[]).TryFrameFadeFlags(system,out bypass,out far)&&bypass&&far);
// Fresh member=true wins over the cached row's explicit false flag.
Check.That(Selection(true,true,[],[1]).TryFrameFadeFlags(system,out bypass,out far)&&!bypass&&far);
Check.That(!Selection(false,true,[],[1]).TryFrameFadeFlags(system,out _,out _));
Check.That(!Selection(true,false,[],[]).TryFrameFadeFlags(system,out _,out _));
Check.That(!Selection(true,true,[],[],1).TryFrameFadeFlags(system,out _,out _));
Check.That(!Selection(true,true,[row],[]).TryFrameFadeFlags(system with{Position=new(1,0,0)},out _,out _));
}
[Test] public async Task ProfileFetchJoinsPointPagesFromTheSameGenerationBeforePublishing()
{
var process=Random.Shared.Next(170000000,180000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(6));
var serve=Task.Run(async()=>
{
for(var page=0;page<2;page++)
{
await using var server=new NamedPipeServerStream($"XFEEDToolBox.Navigation.{process}",PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous);
await server.WaitForConnectionAsync(stop.Token);var bytes=new byte[65536];var count=await server.ReadAsync(bytes,stop.Token);
using var request=JsonDocument.Parse(bytes.AsMemory(0,count));
Check.Equal(page==0?"galaxy.catalogue.profile":"galaxy.catalogue.profile.result",request.RootElement.GetProperty("op").GetString());
if(page==1){Check.Equal(1,request.RootElement.GetProperty("overrideOffset").GetInt32());Check.Equal(1,request.RootElement.GetProperty("navRouteOffset").GetInt32());}
var points=Page(offset:page,navOffset:page,total:2,navTotal:2,id:page+1,process:process);
var result=new{generation=9L,done=true,cancelled=false,error="",profile=new{origin=new[]{0d,0d,0d},referenceKind="camera-eye",
navRouteViewMode=1,navRouteMembershipMode=1,pointOverrides=points}};
await server.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(new{ok=true,result}),stop.Token);await server.WriteAsync(new byte[]{10},stop.Token);
}
});
var profile=await new NativeGalaxyCatalog(process).CatalogueProfileAsync(new(0,0,0),new(0,0,0),stop.Token);await serve;
Check.That(profile.PointOverrides is{D50Complete:true,NavRouteComplete:true,SourceGeneration:9} points&&
points.Overrides.Count==2&&points.Membership(2)==true&&points.OverridePresence(3)==false);
}
[Test] public async Task SlowAuxiliaryPageDoesNotHoldBasicLodAndSupersedingViewDiscardsItsCompletion()
{
var process=Random.Shared.Next(180000000,190000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(6));
var auxiliaryStarted=new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var releaseAuxiliary=new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
NamedPipeServerStream Server()=>new($"XFEEDToolBox.Navigation.{process}",PipeDirection.InOut,NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Byte,PipeOptions.Asynchronous);
async Task<JsonElement> Request(NamedPipeServerStream server)
{
await server.WaitForConnectionAsync(stop.Token);var bytes=new byte[65536];var count=0;
while(count<bytes.Length)
{
var read=await server.ReadAsync(bytes.AsMemory(count),stop.Token);if(read==0)throw new EndOfStreamException();count+=read;
try{using var json=JsonDocument.Parse(bytes.AsMemory(0,count));return json.RootElement.Clone();}
catch(JsonException){}
}
throw new InvalidDataException("Fixture request too large.");
}
async Task Reply(NamedPipeServerStream server,int generation,JsonElement points)
{
var result=new{generation,done=true,cancelled=false,error="",profile=new{origin=new[]{0d,0d,0d},queryCentre=new[]{0d,0d,0d},
surfaceDensity=1d,qualityStarCount=100u,runtimeRadiusMultiplier=1d,sparseExpansion=1d,smallMode=false,originalViewMode=0,
debugEnabled=false,referenceKind="camera-eye",navRouteViewMode=1,navRouteMembershipMode=1,pointOverrides=points}};
await server.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(new{ok=true,result}),stop.Token);await server.WriteAsync(new byte[]{10},stop.Token);
}
var initialServer=Task.Run(async()=>
{
await using var server=Server();var request=await Request(server);
Check.Equal("galaxy.catalogue.profile",request.GetProperty("op").GetString());
await Reply(server,9,Page(total:2,navTotal:2,process:process));
});
var client=new NativeGalaxyCatalog(process);
var initial=await client.CatalogueProfileFirstPageAsync(new(0,0,0),new(0,0,0),stop.Token);await initialServer;
Check.That(initial.HasLodInputs&&initial.PointOverrides is{D50Complete:false,NavRouteComplete:false});
var slowServer=Task.Run(async()=>
{
await using var server=Server();var request=await Request(server);
Check.Equal("galaxy.catalogue.profile.result",request.GetProperty("op").GetString());
auxiliaryStarted.SetResult();await releaseAuxiliary.Task.WaitAsync(stop.Token);
await Reply(server,9,Page(offset:1,navOffset:1,total:2,navTotal:2,id:2,process:process));
});
var pending=client.CompletePointOverridesAsync(initial,stop.Token);await auxiliaryStarted.Task.WaitAsync(stop.Token);
try
{
var nextServer=Task.Run(async()=>
{
await using var server=Server();var request=await Request(server);
Check.Equal("galaxy.catalogue.profile",request.GetProperty("op").GetString());
await Reply(server,10,Page(generation:10,total:0,navTotal:0,process:process));
});
var next=await client.CatalogueProfileFirstPageAsync(new(1,0,0),new(1,0,0),stop.Token);await nextServer;
// The slow page is still withheld. Both basic LOD results have
// already returned, and the optional reader holds neither gate.
Check.That(next.HasLodInputs&&next.PointOverrides?.SourceGeneration==10&&!pending.IsCompleted);
}
finally{releaseAuxiliary.TrySetResult();}
Check.That(await pending is null);await slowServer;
}
}
using System.Text.Json;
using System.IO.Pipes;
using XFEEDToolBox.Core.Galaxy;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public sealed class GalaxyPointOverridesTests
{
static JsonElement Page(long generation=9,int offset=0,int navOffset=0,int total=1,int navTotal=1,bool known=true,long id=1,int process=42)=>JsonSerializer.SerializeToElement(new{
processId=process,sourceGeneration=generation,capturedAtTick=123UL,scope="published-renderer-d50+local-navroute",navRouteViewMode=(int?)1,
d50Complete=known,navRouteComplete=known,overrideOffset=offset,overrideTotal=total,navRouteOffset=navOffset,navRouteTotal=navTotal,
overrides=total>offset?new[]{new{systemAddress=id.ToString(),minimumExtra=1f,farFade=false,pending=false,position=new[]{5d,5d,1005d},name=(string?)null,starClass="G",originalAppearance=(object?)null}}:[],
navRouteMembers=navTotal>navOffset?new[]{id.ToString()}:[]});
[Test] public void CurrentPublishedPresenceIsIndependentOfTheConsumedPendingBitAndUnknownIsNotAbsent()
{
var known=NativeGalaxyCatalog.ReadPointOverridesPages([Page()]);
Check.Equal(true,known.OverridePresence(1)!.Value);Check.Equal(false,known.OverridePresence(2)!.Value);
Check.Equal(true,known.Membership(1)!.Value);Check.Equal(false,known.Membership(2)!.Value);
Check.That(known.TryGetOverride(1,out var row)&&row is{Pending:false,MinimumExtra:1,FarFade:false,Name:null,System:null});
var unknown=NativeGalaxyCatalog.ReadPointOverridesPages([Page(total:0,navTotal:0,known:false)]);
Check.That(unknown.OverridePresence(1) is null&&unknown.Membership(1) is null&&unknown.OverridePresence(0) is null);
}
[Test] public void PagesRetainOneSnapshotAndNeverUsePartialArraysToProveAbsence()
{
var first=Page(total:2,navTotal:2);var partial=NativeGalaxyCatalog.ReadPointOverridesPages([first]);
Check.That(!partial.D50Complete&&!partial.NavRouteComplete&&partial.OverridePresence(2) is null&&partial.Membership(2) is null);
var all=NativeGalaxyCatalog.ReadPointOverridesPages([first,Page(offset:1,navOffset:1,total:2,navTotal:2,id:2)]);
Check.That(all.D50Complete&&all.NavRouteComplete&&all.Membership(2)==true&&all.Membership(3)==false);
Check.Throws<InvalidDataException>(()=>NativeGalaxyCatalog.ReadPointOverridesPages([first,Page(generation:10,offset:1,navOffset:1,total:2,navTotal:2,id:2)]));
Check.Throws<InvalidDataException>(()=>NativeGalaxyCatalog.ReadPointOverridesPages([first,Page(offset:1,navOffset:1,total:2,navTotal:2,id:1)]));
}
[Test] public void D50ForcesOnlyItsExactPublishedPositionAndDoesNotBypassStrictFilter()
{
var row=new GalaxyOriginalPointOverride((long)(GalaxyCellQueryPlan.Encode(0,0,0,100)|(1UL<<44)),1,false,false,new(5,5,1005),"Recorded","G",null);
var points=new GalaxyOriginalPointOverrides(42,9,123,0,true,true,[row],[]);
var system=row.System!;var camera=GalaxyCamera.Create(new(5,5,5),1600,900,0,0,4);
var selection=new GalaxyCatalogueSelection(GalaxyOriginalCatalogueLod.Create(new(5,5,5),1,10,.001,1,0),new(0,0,0)){PointOverrides=points};
Check.That(selection.Contains(system)&&selection.IsForced(system));
Check.That(!selection.Contains(system with{Position=new(6,5,1005)}));
Check.That(!camera.ProjectForCatalogue(system.Position).IsVisible&&selection.ApplyTo(camera).ProjectForCatalogue(system.Position).IsVisible);
var physical=system with{OriginalAppearance=new(0xffffffff,null,null,0,4,.4f,.2f,false)};
Check.Equal(.4f,(selection with{SmallMode=false}).OriginalSize(physical)!.Value);
var strict=GalaxyMapFilter.Default with{Mode=GalaxyMapMode.Map,Values=new HashSet<string>(),IncludeUnknown=false};
Check.That(!strict.Matches(system));
Check.Equal(.4f,(selection with{PointOverrides=null,SmallMode=false}).OriginalSize(physical)!.Value);
}
[Test] public void OnlyCurrentKnownD50AbsenceCanReachFreshNavRouteFlags()
{
var row=new GalaxyOriginalPointOverride(1,1,false,false,new(0,0,0),"Recorded","G",null);
var system=row.System! with{OriginalAppearance=new(0xffffffff,null,null,0,4,.4f,.2f,false)
{NavRouteMembership=false,NavRouteMembershipMode=0,NavRouteViewMode=0}};
var descriptor=GalaxyOriginalCatalogueLod.Create(new(0,0,0),1,10,.001,1,0);
GalaxyCatalogueSelection Selection(bool d50,bool nav,IEnumerable<GalaxyOriginalPointOverride> rows,IEnumerable<long> members,int view=0)=>
new(descriptor,new(0,0,0)){NavRouteViewMode=0,NavRouteMembershipMode=0,
PointOverrides=new(42,9,123,view,d50,nav,rows,members)};
Check.That(Selection(true,true,[row],[]).TryFrameFadeFlags(system,out var bypass,out var far)&&!bypass&&!far);
Check.That(Selection(true,true,[],[]).TryFrameFadeFlags(system,out bypass,out far)&&bypass&&far);
// Fresh member=true wins over the cached row's explicit false flag.
Check.That(Selection(true,true,[],[1]).TryFrameFadeFlags(system,out bypass,out far)&&!bypass&&far);
Check.That(!Selection(false,true,[],[1]).TryFrameFadeFlags(system,out _,out _));
Check.That(!Selection(true,false,[],[]).TryFrameFadeFlags(system,out _,out _));
Check.That(!Selection(true,true,[],[],1).TryFrameFadeFlags(system,out _,out _));
Check.That(!Selection(true,true,[row],[]).TryFrameFadeFlags(system with{Position=new(1,0,0)},out _,out _));
}
[Test] public async Task ProfileFetchJoinsPointPagesFromTheSameGenerationBeforePublishing()
{
var process=Random.Shared.Next(170000000,180000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(6));
var serve=Task.Run(async()=>
{
for(var page=0;page<2;page++)
{
await using var server=new NamedPipeServerStream($"XFEEDToolBox.Navigation.{process}",PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous);
await server.WaitForConnectionAsync(stop.Token);var bytes=new byte[65536];var count=await server.ReadAsync(bytes,stop.Token);
using var request=JsonDocument.Parse(bytes.AsMemory(0,count));
Check.Equal(page==0?"galaxy.catalogue.profile":"galaxy.catalogue.profile.result",request.RootElement.GetProperty("op").GetString());
if(page==1){Check.Equal(1,request.RootElement.GetProperty("overrideOffset").GetInt32());Check.Equal(1,request.RootElement.GetProperty("navRouteOffset").GetInt32());}
var points=Page(offset:page,navOffset:page,total:2,navTotal:2,id:page+1,process:process);
var result=new{generation=9L,done=true,cancelled=false,error="",profile=new{origin=new[]{0d,0d,0d},referenceKind="camera-eye",
navRouteViewMode=1,navRouteMembershipMode=1,pointOverrides=points}};
await server.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(new{ok=true,result}),stop.Token);await server.WriteAsync(new byte[]{10},stop.Token);
}
});
var profile=await new NativeGalaxyCatalog(process).CatalogueProfileAsync(new(0,0,0),new(0,0,0),stop.Token);await serve;
Check.That(profile.PointOverrides is{D50Complete:true,NavRouteComplete:true,SourceGeneration:9} points&&
points.Overrides.Count==2&&points.Membership(2)==true&&points.OverridePresence(3)==false);
}
[Test] public async Task SlowAuxiliaryPageDoesNotHoldBasicLodAndSupersedingViewDiscardsItsCompletion()
{
var process=Random.Shared.Next(180000000,190000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(6));
var auxiliaryStarted=new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var releaseAuxiliary=new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
NamedPipeServerStream Server()=>new($"XFEEDToolBox.Navigation.{process}",PipeDirection.InOut,NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Byte,PipeOptions.Asynchronous);
async Task<JsonElement> Request(NamedPipeServerStream server)
{
await server.WaitForConnectionAsync(stop.Token);var bytes=new byte[65536];var count=0;
while(count<bytes.Length)
{
var read=await server.ReadAsync(bytes.AsMemory(count),stop.Token);if(read==0)throw new EndOfStreamException();count+=read;
try{using var json=JsonDocument.Parse(bytes.AsMemory(0,count));return json.RootElement.Clone();}
catch(JsonException){}
}
throw new InvalidDataException("Fixture request too large.");
}
async Task Reply(NamedPipeServerStream server,int generation,JsonElement points)
{
var result=new{generation,done=true,cancelled=false,error="",profile=new{origin=new[]{0d,0d,0d},queryCentre=new[]{0d,0d,0d},
surfaceDensity=1d,qualityStarCount=100u,runtimeRadiusMultiplier=1d,sparseExpansion=1d,smallMode=false,originalViewMode=0,
debugEnabled=false,referenceKind="camera-eye",navRouteViewMode=1,navRouteMembershipMode=1,pointOverrides=points}};
await server.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(new{ok=true,result}),stop.Token);await server.WriteAsync(new byte[]{10},stop.Token);
}
var initialServer=Task.Run(async()=>
{
await using var server=Server();var request=await Request(server);
Check.Equal("galaxy.catalogue.profile",request.GetProperty("op").GetString());
await Reply(server,9,Page(total:2,navTotal:2,process:process));
});
var client=new NativeGalaxyCatalog(process);
var initial=await client.CatalogueProfileFirstPageAsync(new(0,0,0),new(0,0,0),stop.Token);await initialServer;
Check.That(initial.HasLodInputs&&initial.PointOverrides is{D50Complete:false,NavRouteComplete:false});
var slowServer=Task.Run(async()=>
{
await using var server=Server();var request=await Request(server);
Check.Equal("galaxy.catalogue.profile.result",request.GetProperty("op").GetString());
auxiliaryStarted.SetResult();await releaseAuxiliary.Task.WaitAsync(stop.Token);
await Reply(server,9,Page(offset:1,navOffset:1,total:2,navTotal:2,id:2,process:process));
});
var pending=client.CompletePointOverridesAsync(initial,stop.Token);await auxiliaryStarted.Task.WaitAsync(stop.Token);
try
{
var nextServer=Task.Run(async()=>
{
await using var server=Server();var request=await Request(server);
Check.Equal("galaxy.catalogue.profile",request.GetProperty("op").GetString());
await Reply(server,10,Page(generation:10,total:0,navTotal:0,process:process));
});
var next=await client.CatalogueProfileFirstPageAsync(new(1,0,0),new(1,0,0),stop.Token);await nextServer;
// The slow page is still withheld. Both basic LOD results have
// already returned, and the optional reader holds neither gate.
Check.That(next.HasLodInputs&&next.PointOverrides?.SourceGeneration==10&&!pending.IsCompleted);
}
finally{releaseAuxiliary.TrySetResult();}
Check.That(await pending is null);await slowServer;
}
}