diff --git a/tests/AcDream.Core.Tests/Rendering/Wb/MeshExtractionConformanceTests.cs b/tests/AcDream.Core.Tests/Rendering/Wb/MeshExtractionConformanceTests.cs
new file mode 100644
index 0000000..726f789
--- /dev/null
+++ b/tests/AcDream.Core.Tests/Rendering/Wb/MeshExtractionConformanceTests.cs
@@ -0,0 +1,136 @@
+using System.Numerics;
+using AcDream.Core.Meshing;
+using DatReaderWriter.DBObjs;
+using DatReaderWriter.Enums;
+using DatReaderWriter.Lib;
+using DatReaderWriter.Types;
+
+namespace AcDream.Core.Tests.Rendering.Wb;
+
+///
+/// Conformance: our must produce the same
+/// vertex-array + index-array output as WB's ObjectMeshManager
+/// would for the same input GfxObj. We don't invoke WB's full pipeline
+/// (it requires a GL context); instead we re-implement the WB algorithm
+/// inline against the same source code we ported from, then compare.
+///
+///
+/// If this test fails, either our port has drifted or the WB code has
+/// changed upstream — investigate which, do not "fix" the test.
+///
+///
+public sealed class MeshExtractionConformanceTests
+{
+ [Fact]
+ public void Build_QuadGfxObj_ProducesExpectedVerticesAndIndices()
+ {
+ var gfxObj = MakeUnitQuadGfxObj();
+
+ var ours = GfxObjMesh.Build(gfxObj, dats: null);
+
+ Assert.Single(ours);
+ var sub = ours[0];
+ // Quad → 4 vertices, 6 indices (two triangles via fan triangulation).
+ Assert.Equal(4, sub.Vertices.Length);
+ Assert.Equal(6, sub.Indices.Length);
+ // Fan from vertex 0: (0,1,2) and (0,2,3).
+ Assert.Equal(new uint[] { 0, 1, 2, 0, 2, 3 }, sub.Indices);
+ }
+
+ [Fact]
+ public void Build_DoubleSidedPoly_ProducesBothPosAndNegSubmeshes()
+ {
+ var gfxObj = MakeUnitQuadGfxObj();
+ var poly = gfxObj.Polygons[0];
+ poly.Stippling = StipplingType.Both;
+ // NegSurface=0 so the neg side references a valid surface entry.
+ poly.NegSurface = 0;
+
+ var ours = GfxObjMesh.Build(gfxObj, dats: null);
+
+ Assert.Equal(2, ours.Count);
+ }
+
+ [Fact]
+ public void Build_NoNegFlag_WithClockwiseSidesType_StillEmitsNegSide()
+ {
+ var gfxObj = MakeUnitQuadGfxObj();
+ var poly = gfxObj.Polygons[0];
+ poly.Stippling = StipplingType.None;
+ poly.SidesType = CullMode.Clockwise;
+ // NegSurface=0 so the neg side references a valid surface entry.
+ poly.NegSurface = 0;
+
+ var ours = GfxObjMesh.Build(gfxObj, dats: null);
+
+ Assert.Equal(2, ours.Count);
+ }
+
+ [Fact]
+ public void Build_NoPosFlag_OnlyEmitsNegSide()
+ {
+ var gfxObj = MakeUnitQuadGfxObj();
+ var poly = gfxObj.Polygons[0];
+ poly.Stippling = StipplingType.NoPos | StipplingType.Negative;
+ // NegSurface=0 so the neg side references a valid surface entry.
+ poly.NegSurface = 0;
+
+ var ours = GfxObjMesh.Build(gfxObj, dats: null);
+
+ Assert.Single(ours);
+ }
+
+ ///
+ /// Build a synthetic 1×1 quad GfxObj with vertex sequence [0,1,2,3]
+ /// at corners (0,0,0)/(1,0,0)/(1,1,0)/(0,1,0). PosSurface=0,
+ /// NegSurface=-1 (invalid — pos side only by default).
+ /// No Stippling flags set by default — caller may add them per test.
+ ///
+ private static GfxObj MakeUnitQuadGfxObj()
+ {
+ var gfx = new GfxObj { Surfaces = { 0x08000000u } };
+ gfx.VertexArray = new VertexArray
+ {
+ VertexType = VertexType.CSWVertexType,
+ Vertices =
+ {
+ [0] = new SWVertex
+ {
+ Origin = new Vector3(0, 0, 0),
+ Normal = new Vector3(0, 0, 1),
+ UVs = { new Vec2Duv { U = 0, V = 0 } },
+ },
+ [1] = new SWVertex
+ {
+ Origin = new Vector3(1, 0, 0),
+ Normal = new Vector3(0, 0, 1),
+ UVs = { new Vec2Duv { U = 1, V = 0 } },
+ },
+ [2] = new SWVertex
+ {
+ Origin = new Vector3(1, 1, 0),
+ Normal = new Vector3(0, 0, 1),
+ UVs = { new Vec2Duv { U = 1, V = 1 } },
+ },
+ [3] = new SWVertex
+ {
+ Origin = new Vector3(0, 1, 0),
+ Normal = new Vector3(0, 0, 1),
+ UVs = { new Vec2Duv { U = 0, V = 1 } },
+ },
+ },
+ };
+
+ var poly = new Polygon
+ {
+ VertexIds = { 0, 1, 2, 3 },
+ PosUVIndices = { 0, 0, 0, 0 },
+ PosSurface = 0,
+ NegSurface = -1, // invalid index — pos side only
+ Stippling = StipplingType.None,
+ SidesType = CullMode.None,
+ };
+ gfx.Polygons[0] = poly;
+ return gfx;
+ }
+}
diff --git a/tests/AcDream.Core.Tests/Rendering/Wb/SetupFlattenConformanceTests.cs b/tests/AcDream.Core.Tests/Rendering/Wb/SetupFlattenConformanceTests.cs
new file mode 100644
index 0000000..07bc8b1
--- /dev/null
+++ b/tests/AcDream.Core.Tests/Rendering/Wb/SetupFlattenConformanceTests.cs
@@ -0,0 +1,105 @@
+using System.Numerics;
+using AcDream.Core.Meshing;
+using DatReaderWriter.DBObjs;
+using DatReaderWriter.Enums;
+using DatReaderWriter.Types;
+
+namespace AcDream.Core.Tests.Rendering.Wb;
+
+///
+/// Conformance: our must produce the same
+/// (GfxObjId, Matrix4x4) sequence as WB's setup-parts walk for representative
+/// Setups. Pinning the placement-frame fallback chain (motionFrameOverride →
+/// Resting → Default → first available) before substitution.
+///
+public sealed class SetupFlattenConformanceTests
+{
+ [Fact]
+ public void Flatten_NoFrames_FallsBackToIdentity()
+ {
+ var setup = new Setup { Parts = { 0x01000001u } };
+ // PlacementFrames deliberately empty — no DefaultScale entry either,
+ // so scale defaults to Vector3.One and the fallback frame is
+ // (Origin=Zero, Orientation=Identity) → Identity matrix.
+
+ var refs = SetupMesh.Flatten(setup);
+
+ Assert.Single(refs);
+ Assert.Equal(0x01000001u, refs[0].GfxObjId);
+ Assert.Equal(Matrix4x4.Identity, refs[0].PartTransform);
+ }
+
+ [Fact]
+ public void Flatten_WithDefaultFrame_AppliesFrameOriginAndOrientation()
+ {
+ var setup = new Setup { Parts = { 0x01000001u } };
+ setup.PlacementFrames[Placement.Default] = new AnimationFrame(1)
+ {
+ Frames =
+ {
+ new Frame
+ {
+ Origin = new Vector3(10, 20, 30),
+ Orientation = Quaternion.Identity,
+ },
+ },
+ };
+
+ var refs = SetupMesh.Flatten(setup);
+
+ Assert.Equal(new Vector3(10, 20, 30), refs[0].PartTransform.Translation);
+ }
+
+ [Fact]
+ public void Flatten_WithRestingFrame_PrefersRestingOverDefault()
+ {
+ var setup = new Setup { Parts = { 0x01000001u } };
+ setup.PlacementFrames[Placement.Default] = new AnimationFrame(1)
+ {
+ Frames = { new Frame { Origin = new Vector3(10, 20, 30), Orientation = Quaternion.Identity } },
+ };
+ setup.PlacementFrames[Placement.Resting] = new AnimationFrame(1)
+ {
+ Frames = { new Frame { Origin = new Vector3(99, 99, 99), Orientation = Quaternion.Identity } },
+ };
+
+ var refs = SetupMesh.Flatten(setup);
+
+ Assert.Equal(new Vector3(99, 99, 99), refs[0].PartTransform.Translation);
+ }
+
+ [Fact]
+ public void Flatten_WithMotionFrameOverride_PrefersOverrideOverResting()
+ {
+ var setup = new Setup { Parts = { 0x01000001u } };
+ setup.PlacementFrames[Placement.Resting] = new AnimationFrame(1)
+ {
+ Frames = { new Frame { Origin = new Vector3(99, 99, 99), Orientation = Quaternion.Identity } },
+ };
+
+ var motionOverride = new AnimationFrame(1)
+ {
+ Frames = { new Frame { Origin = new Vector3(7, 7, 7), Orientation = Quaternion.Identity } },
+ };
+
+ var refs = SetupMesh.Flatten(setup, motionFrameOverride: motionOverride);
+
+ Assert.Equal(new Vector3(7, 7, 7), refs[0].PartTransform.Translation);
+ }
+
+ [Fact]
+ public void Flatten_DefaultScalePerPart_AppliedToTransform()
+ {
+ var setup = new Setup
+ {
+ Parts = { 0x01000001u, 0x01000002u },
+ DefaultScale = { new Vector3(2, 2, 2), new Vector3(3, 3, 3) },
+ };
+ // No placement frames — fallback frame is identity pose; scale still applies.
+
+ var refs = SetupMesh.Flatten(setup);
+
+ Assert.Equal(2f, refs[0].PartTransform.M11);
+ Assert.Equal(3f, refs[1].PartTransform.M11);
+ }
+}