Document upright world planes; mod documentation

This commit is contained in:
cthangover committed 2026-10-29 09:26:19 +01:00
1 parent 8bb951f7f7
commit 5267c91cea
54 files changed
+1620

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
namespace Cthangover.Mods.TileRender.Tests
{
using System;
/// <summary>Registry fixture with no Godot allocation or dependency on an implementation mod.</summary>
internal class StubTileRendererFactory : ITileRendererFactory
{
public string Id { get; }
public StubTileRendererFactory(string id) { Id = id; }
public ITileProjection Projection(int size) => throw new NotSupportedException();
public ITileWorldPresentation Create() => throw new NotSupportedException();
}
}
+31
View File
@@ -0,0 +1,31 @@
namespace Cthangover.Mods.TileRender.Tests
{
using Cthangover.Core.Mods.Testing;
/// <summary>Explicit selection, ambiguity and owner-sensitive unregister never depend on discovery order.</summary>
public class TileRendererRegistryTests
{
private readonly TileRendererRegistry registry = new();
private readonly StubTileRendererFactory square = new("square");
private void Missing() => registry.Resolve("missing");
private void Ambiguous() => registry.Resolve(null);
private void Duplicate() => registry.Register(square);
[ModTest("registry/selection_collision_and_unload")]
static void SelectionCollisionAndUnload(ModTestContext ctx)
{
var test = new TileRendererRegistryTests();
test.registry.Register(test.square);
ModAssert.True(ReferenceEquals(test.square, test.registry.Resolve(null)), "single implementation may be inferred");
ModAssert.Throws(test.Missing, "explicit missing ID must not fall back", true);
ModAssert.Throws(test.Duplicate, "duplicate ID is rejected", true);
var iso = new StubTileRendererFactory("iso");
test.registry.Register(iso);
ModAssert.Throws(test.Ambiguous, "multiple implementations require selection", true);
ModAssert.True(ReferenceEquals(iso, test.registry.Resolve("iso")), "explicit selection");
test.registry.Unregister(new StubTileRendererFactory("iso"));
ModAssert.True(ReferenceEquals(iso, test.registry.Resolve("iso")), "a different owner cannot delete a factory");
test.registry.Unregister(iso);
ModAssert.True(ReferenceEquals(test.square, test.registry.Resolve(null)), "remaining implementation stays available");
}
}
}
+41
View File
@@ -0,0 +1,41 @@
namespace Cthangover.Mods.TileRender.Tests
{
using System.Linq;
using Cthangover.Core.Mods.Testing;
using Godot;
/// <summary>One edit rebuilds its affected halo, and cached GPU resources have explicit ownership.</summary>
public class TileSurfaceCacheTests
{
private static Texture2D ThirdTexture(TileSurfaceCache cache)
{
foreach (var chunk in cache.Chunks) if (chunk.Cells.Position.X == 64) return chunk.Texture;
return null;
}
[ModTest("surfaces/cache_reuses_unchanged_chunks_and_invalidates_halo")]
static void CacheReusesUnchangedChunksAndInvalidatesHalo(ModTestContext ctx)
{
var pixels = new byte[16 * 8 * 4];
for (var i = 0; i < pixels.Length; i++) pixels[i] = 255;
using var image = Image.CreateFromData(16, 8, false, Image.Format.Rgba8, pixels);
using var texture = ImageTexture.CreateFromImage(image);
using var cache = new TileSurfaceCache();
var field = new TileSurfaceField { Width = 65, Height = 3, TileSize = 4, PatchSize = 8,
Materials = new int[65 * 3], Diffuse = Enumerable.Repeat(true, 65 * 3).ToArray() };
cache.Update(field, texture);
ModAssert.Equal(3, cache.LastBuiltChunks, "cold build has three chunks");
var last = ThirdTexture(cache);
cache.Update(field, texture);
ModAssert.Equal(0, cache.LastBuiltChunks, "camera/unchanged data generate no pixels");
field.Materials[31] = 1;
cache.Update(field, texture);
ModAssert.Equal(2, cache.LastBuiltChunks, "an edit beside a chunk boundary invalidates both neighbours");
ModAssert.True(ReferenceEquals(last, ThirdTexture(cache)), "unaffected third texture is preserved");
field.Materials[0] = 1;
cache.Update(field, texture);
ModAssert.Equal(1, cache.LastBuiltChunks, "interior edit regenerates only one chunk");
cache.Dispose();
ModAssert.Equal(0, cache.TextureBytes, "explicit disposal releases the cache's budget");
}
}
}
+41
View File
@@ -0,0 +1,41 @@
namespace Cthangover.Mods.TileRender.Tests
{
using Cthangover.Core.Mods.Testing;
using Godot;
/// <summary>Chunk edges, world phase and diagonal blending must match the full reference texture byte-for-byte.</summary>
public class TileSurfaceRasterizerTests
{
[ModTest("surfaces/homogeneous_field_preserves_every_patch_byte")]
static void HomogeneousPatch(ModTestContext ctx)
{
var atlas = new byte[16 * 16 * 4];
for (var i = 0; i < atlas.Length; i++) atlas[i] = (byte)(i * 73 % 256);
foreach (var size in new[] { 8, 32 })
foreach (var diffuse in new[] { false, true })
{
var field = new TileSurfaceField { Width = 3, Height = 2, TileSize = size, PatchSize = 16,
Materials = new int[6], Diffuse = new[] { diffuse, diffuse, diffuse, diffuse, diffuse, diffuse } };
var pixels = TileSurfaceRasterizer.Render(field, atlas, 16, new Rect2I(0, 0, 3, 2));
for (var y = 0; y < 2 * size; y++) for (var x = 0; x < 3 * size; x++) for (var c = 0; c < 4; c++)
ModAssert.Equal(atlas[((y % 16) * 16 + x % 16) * 4 + c], pixels[(y * 3 * size + x) * 4 + c], "exact source sample including alpha");
}
}
[ModTest("surfaces/chunks_equal_monolithic_pixels")]
static void ChunksEqualMonolithicPixels(ModTestContext ctx)
{
var field = new TileSurfaceField { Width = 4, Height = 3, TileSize = 8, PatchSize = 16,
Materials = new[] { 0, 0, 1, 1, 0, -1, 1, 1, 1, 0, 0, 1 },
Diffuse = new[] { true, true, true, true, true, true, true, false, true, true, true, true } };
var atlas = new byte[32 * 16 * 4];
for (var y = 0; y < 16; y++) for (var x = 0; x < 32; x++)
{ var i = (y * 32 + x) * 4; atlas[i] = (byte)(x * 7); atlas[i + 1] = (byte)(y * 15); atlas[i + 3] = 255; }
var full = TileSurfaceRasterizer.Render(field, atlas, 32, new Rect2I(0, 0, 4, 3));
for (var cy = 0; cy < 3; cy++) for (var cx = 0; cx < 4; cx++)
{
var part = TileSurfaceRasterizer.Render(field, atlas, 32, new Rect2I(cx, cy, 1, 1));
for (var y = 0; y < 8; y++) for (var x = 0; x < 8; x++) for (var c = 0; c < 4; c++)
ModAssert.Equal(full[((cy * 8 + y) * 32 + cx * 8 + x) * 4 + c], part[(y * 8 + x) * 4 + c], "chunk including halo and global patch phase");
}
}
}
}