From 015b660d0f56f35a5f4b6007194402b8d95ee90e Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 2 Sep 2026 21:10:16 +0200 Subject: [PATCH] test(render): pin cell-shell batch-to-index-segment pairing under reorder Uploads a cell-shell mesh whose storage order is the reverse of its surface order and asserts each uploaded batch's FirstIndex reads back its own indices from the arena. Guards the G1 regression fixed at 8c6563ca. Co-Authored-By: Claude Fable 5.1 --- .../Wb/MeshPipelineDeviceSeamTests.cs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/AcDream.App.Tests/Rendering/Wb/MeshPipelineDeviceSeamTests.cs b/tests/AcDream.App.Tests/Rendering/Wb/MeshPipelineDeviceSeamTests.cs index 48cdb2c5..98b66f65 100644 --- a/tests/AcDream.App.Tests/Rendering/Wb/MeshPipelineDeviceSeamTests.cs +++ b/tests/AcDream.App.Tests/Rendering/Wb/MeshPipelineDeviceSeamTests.cs @@ -232,6 +232,64 @@ public sealed class MeshPipelineDeviceSeamTests System.Runtime.InteropServices.MemoryMarshal.Cast(indexBytes).ToArray()); } + /// + /// Campaign OVERHAUL S1 (G1 FAIL 2026-09-02). Cell-shell batches upload in + /// ascending source surface index (retail's built-EnvCell subset order, + /// ConstructMesh @0x0059DFA0 / DrawMesh @0x0059D4A0), which differs from + /// the (Width,Height,Format) storage order the index segments used to be + /// filled in. The first fix ordered only the batch list, so batch i took + /// segment i's index range from a different batch: magenta walls, + /// stretched textures, missing faces in every dungeon. This pins that each + /// uploaded batch's FirstIndex points at ITS OWN indices in the arena, with + /// a fixture whose storage order is the reverse of its surface order. + /// + [Fact] + public void CellShellBatchesKeepTheirOwnIndexRangesWhenReorderedBySurfaceIndex() + { + using var device = new RecordingGpuDevice(); + using ObjectMeshManager manager = Build(device, modernPath: true); + GlobalMeshBuffer arena = manager.GlobalBuffer!; + + static AcDream.Content.TextureBatchData CellBatch(int slot, uint surfaceId, int size, ushort[] indices) => new() + { + Key = new AcDream.Content.TextureKey { SurfaceId = surfaceId }, + TextureData = new byte[size * size * 4], + Indices = [.. indices], + IsCellShell = true, + SourceSurfaceIndex = slot, + CullMode = DatReaderWriter.Enums.CullMode.Clockwise, + }; + + // Two storage groups (two texture sizes) so dictionary insertion order + // puts slot 5 first; retail order is slot 2 first. + var mesh = new AcDream.Content.ObjectMeshData + { + ObjectId = 0x1_0000_0000UL | 0xF4180104UL, + Vertices = new VertexPositionNormalTexture[6], + TextureBatches = + { + [(8, 8, Chorizite.Core.Render.Enums.TextureFormat.RGBA8)] = [CellBatch(5, 0x08000005u, 8, [0, 1, 2])], + [(16, 16, Chorizite.Core.Render.Enums.TextureFormat.RGBA8)] = [CellBatch(2, 0x08000002u, 16, [3, 4, 5])], + }, + }; + + ObjectRenderData data = Assert.IsType(manager.UploadMeshData(mesh)); + + Assert.Equal(2, data.Batches.Count); + Assert.Equal(0x08000002u, data.Batches[0].Key.SurfaceId); // ascending surface index wins + Assert.Equal(0x08000005u, data.Batches[1].Key.SurfaceId); + + foreach (ObjectRenderBatch batch in data.Batches) + { + ushort[] expected = batch.Key.SurfaceId == 0x08000002u ? [3, 4, 5] : [0, 1, 2]; + var bytes = new byte[batch.IndexCount * sizeof(ushort)]; + arena.IndexStore!.Read((long)batch.FirstIndex * sizeof(ushort), bytes); + Assert.Equal( + expected, + System.Runtime.InteropServices.MemoryMarshal.Cast(bytes).ToArray()); + } + } + /// /// #429 allocation gate (I1 style). Completing a prepared mesh on the /// render thread must allocate near its retained pick-copy size