File size: 4,631 Bytes
3ce1a40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
using Repoverse.Core.Bridge;
using Repoverse.Core.Generation;
using Repoverse.Core.Persistence;
using Repoverse.Core.Voxels;

namespace Repoverse.Core.Tests;

public class BridgeTests
{
    private static WorldSession Session(int radius = 0) => new(ManifestBuilder.Build(TestRepos.Slice(),new Seed(42)),radius);
    private static List<MeshPacket> Settle(WorldSession s,double x,double z)
    {
        var meshes=new List<MeshPacket>();
        for(int i=0;i<100;i++) {
            var p=s.Focus(x,z); meshes.AddRange(p.Chunks);
            if(p.Remaining==0)return meshes;
        }
        throw new Exception("Stream did not settle");
    }
    [Fact] public void Streaming_is_bounded_and_unloads_old_meshes()
    {
        var s=Session(); var first=Settle(s,0,0);
        Assert.NotEmpty(first);
        Assert.Equal(WorldGenerator.MaxChunkY+1,s.Resident);
        var moved=s.Focus(1000,1000);
        Assert.Equal(first.Select(m=>m.Id).Distinct().Order(),moved.Unloaded.Order());
        for(int i=0;i<20;i++) {
            Settle(s,i*500,i*500);
            Assert.InRange(s.Resident,1,WorldGenerator.MaxChunkY+1);
        }
    }
    [Fact] public void Edits_remesh_and_survive_unload_and_save_restore()
    {
        var s=Session();Settle(s,0,0);
        s.Edit(2,15,2,new Voxel(BlockType.Wall).Packed);
        Assert.NotEmpty(s.Focus(0,0).Chunks);
        s.SetPlayer(new PlayerState {X=2,Y=16,Z=2,Selection="repoverse://snapkitty/site"});
        var saved=s.Capture();
        Settle(s,2000,2000);Settle(s,0,0);
        Assert.Equal(BlockType.Wall,s.Sample(2,15,2)!.Value.Type);
        var restored=Session();restored.Restore(saved);Settle(restored,0,0);
        Assert.Equal(saved.Player,restored.Player);
        Assert.Equal(BlockType.Wall,restored.Sample(2,15,2)!.Value.Type);
        Assert.Throws<InvalidDataException>(()=>restored.Restore(saved with {WorldSeed="other"}));
    }
    [Fact] public void Mesh_packets_have_outward_core_winding_and_passable_ladders()
    {
        var c=new Chunk(new(0,0,0));c[1,1,1]=new Voxel(BlockType.Wall);c[4,1,1]=new Voxel(BlockType.Ladder);
        var packet=WorldSession.Encode(GreedyMesher.Build(c));
        Assert.Contains(packet.Sections,s=>!s.Collision && (s.Key&255)==(int)BlockType.Ladder);
        var wall=packet.Sections.Single(s=>(s.Key&255)==(int)BlockType.Wall);
        Assert.Equal(24,wall.Vertices.Length);Assert.Equal(36,wall.Indices.Length);
        for(int i=0;i<wall.Indices.Length;i+=3) {
            System.Numerics.Vector3 V(int n) {var v=wall.Vertices[n];return new(v[0],v[1],v[2]);}
            var a=V(wall.Indices[i]);var b=V(wall.Indices[i+1]);var d=V(wall.Indices[i+2]);
            Assert.True(System.Numerics.Vector3.Dot(System.Numerics.Vector3.Cross(b-a,d-a),(a+b+d)/3-new System.Numerics.Vector3(1.5f))>0);
        }
    }
    [Fact] public void Adjacent_arrivals_remesh_boundaries()
    {
        var s=Session(1); var seen=new Dictionary<string,int>();
        for(int i=0;i<100;i++) {
            var p=s.Focus(0,0,1);
            foreach(var c in p.Chunks)seen[c.Id]=seen.GetValueOrDefault(c.Id)+1;
            if(p.Remaining==0)break;
        }
        Assert.Contains(seen.Values,n=>n>1);
        Assert.Equal(9*(WorldGenerator.MaxChunkY+1),s.Resident);
    }
    [Fact] public void Invalid_coordinates_are_rejected()
    {
        var s=Session();Assert.Throws<ArgumentException>(()=>s.Focus(double.NaN,0));
        Assert.Throws<ArgumentException>(()=>s.SetPlayer(new PlayerState{Y=double.PositiveInfinity}));
        Assert.Throws<ArgumentException>(()=>s.Edit(0,0,0,0));
    }
    [Fact] public void Source_is_paged_and_restricted_to_manifest_artifacts()
    {
        var dir=Path.Combine(Path.GetTempPath(),Guid.NewGuid().ToString());Directory.CreateDirectory(dir);
        try {
            var manifest=ManifestBuilder.Build([TestRepos.Make("source",files:["README.md","../escape.txt"])],new Seed(1));
            File.WriteAllLines(Path.Combine(dir,"README.md"),Enumerable.Range(0,80).Select(i=>$"line {i}"));
            var source=new LocalSourceReader(manifest,"snapkitty/source",dir);
            var page=source.Read("snapkitty/source","README.md",32);
            Assert.Equal("line 32",page.Lines[0]);Assert.Equal(32,page.Lines.Length);Assert.True(page.HasMore);
            Assert.Throws<ArgumentException>(()=>source.Read("someone/else","README.md",0));
            Assert.Throws<ArgumentException>(()=>source.Read("snapkitty/source",".git/config",0));
            Assert.Throws<ArgumentException>(()=>source.Read("snapkitty/source","../escape.txt",0));
        } finally {Directory.Delete(dir,true);}
    }
}