fix(content): S1 review round - retail default sides shape, mask hoist, upload order
Campaign OVERHAUL S1 review fixes (two Opus lens reviews, findings verified by the lead against the decomp): - a raw sides_type outside 0/1/2 constructs retail's default single-side shape (ConstructMesh @0x0059DFA0 loop bounds default to 1) instead of dropping the polygon; still counted as a data anomaly (corpus has none); - the positive-surface stippling mask OR runs once per polygon before the degenerate-fan guard, as retail's count loop does (pseudo-C 426859-426866); - untextured slots keep their mask accounting but bake no texture and no vertices (contract §9 item 3); the dev pak shrinks by 114 KB; - failed surface-override / Surface / texture-dependency lookups are attempted and logged once per slot, not once per candidate; - cell-shell batches upload in ascending source surface index, retail's built-EnvCell subset draw order (ConstructMesh attribute-range scan, DrawMesh @0x0059D4A0); ordinary GfxObj meshes keep storage order; - CellMesh.HasDrawableGeometry documented as the admission rule without texture-dependency resolution (a conservative superset of emission); - the stippling/surface equivalence sweep's cell half is pinned at zero again; InAscendingSurfaceOrder is marked bake/upload/test-only; - plan §5: reviewer findings are verified by the lead, one skeptic at most for a blocking finding, never more than five agents per step. Three new Content tests pin the mask hoist, the vertex-free untextured slot, and the single-side fallback. Content 213/213, Core Meshing and Conformance green, App hermetic 6,757/6,757, Release build 0/0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
0840d5fb77
commit
e2543d0ef0
10 changed files with 265 additions and 73 deletions
|
|
@ -497,6 +497,91 @@ public sealed class MeshExtractorSolidFaceExtractionTests
|
|||
Assert.All(mesh.Vertices, vertex => Assert.Equal(Vector2.Zero, vertex.UV));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrepareCellStructMeshData_DegeneratePolygon_StillOrsPositiveSurfaceMask()
|
||||
{
|
||||
// ConstructMesh @0x0059DFA0 count loop (pseudo-C 426859-426866): the
|
||||
// positive-surface stippling OR runs once per polygon BEFORE the
|
||||
// num_pts branch, so a two-vertex polygon that builds no fan still
|
||||
// stamps bit 1 on its positive slot. Slot 0 gets its geometry from
|
||||
// the textured quad (stippling 0, so no bit from it) and its mask bit
|
||||
// only from the degenerate polygon.
|
||||
var dats = new FakeMeshExtractorDats();
|
||||
RegisterCellTexturedSurface(dats);
|
||||
var cellStruct = BuildQuadCellStruct(RetailCullMode.Landblock);
|
||||
cellStruct.Polygons[1] = new Polygon
|
||||
{
|
||||
SidesType = RetailCullMode.Landblock,
|
||||
Stippling = StipplingType.NoPos,
|
||||
PosSurface = 0,
|
||||
NegSurface = -1,
|
||||
VertexIds = [0, 1],
|
||||
};
|
||||
var extractor = new MeshExtractor(dats, NullLogger.Instance, sideStagedSink: null);
|
||||
|
||||
ObjectMeshData mesh = Assert.IsType<ObjectMeshData>(
|
||||
extractor.PrepareCellStructMeshData(
|
||||
id: 1, cellStruct, surfaceOverrides: [2], Matrix4x4.Identity, CancellationToken.None));
|
||||
|
||||
TextureBatchData batch = Assert.Single(Assert.Single(mesh.TextureBatches).Value);
|
||||
Assert.Equal(6, batch.Indices.Count);
|
||||
Assert.Equal(1, batch.RetailSurfaceMask);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrepareCellStructMeshData_UntexturedSlot_EmitsNoVertices()
|
||||
{
|
||||
// An untextured slot fails built-EnvCell admission (contract §4).
|
||||
// Retail constructs its geometry and never draws it; the prepared
|
||||
// package does not carry vertices that can never draw (contract §9
|
||||
// item 3). Slot 0 is textured, slot 1 is Base1Solid: only the four
|
||||
// vertices of the textured quad may reach the output.
|
||||
var dats = new FakeMeshExtractorDats();
|
||||
RegisterCellTexturedSurface(dats);
|
||||
dats.Register(SolidSurfaceId, new Surface
|
||||
{
|
||||
Type = SurfaceType.Base1Solid,
|
||||
ColorValue = new ColorARGB { Alpha = 255, Red = 64, Green = 96, Blue = 128 },
|
||||
});
|
||||
var cellStruct = BuildQuadCellStruct(RetailCullMode.Landblock);
|
||||
cellStruct.Polygons[1] = new Polygon
|
||||
{
|
||||
SidesType = RetailCullMode.Landblock,
|
||||
PosSurface = 1,
|
||||
NegSurface = -1,
|
||||
VertexIds = [0, 1, 2, 3],
|
||||
};
|
||||
var extractor = new MeshExtractor(dats, NullLogger.Instance, sideStagedSink: null);
|
||||
|
||||
ObjectMeshData mesh = Assert.IsType<ObjectMeshData>(
|
||||
extractor.PrepareCellStructMeshData(
|
||||
id: 1, cellStruct, surfaceOverrides: [2, 1], Matrix4x4.Identity, CancellationToken.None));
|
||||
|
||||
TextureBatchData batch = Assert.Single(Assert.Single(mesh.TextureBatches).Value);
|
||||
Assert.Equal(0, batch.SourceSurfaceIndex);
|
||||
Assert.Equal(4, mesh.Vertices.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrepareCellStructMeshData_SidesValueOutsideRetailSet_ConstructsSingleSideFan()
|
||||
{
|
||||
// ConstructMesh @0x0059DFA0 initializes both loop bounds to 1 and
|
||||
// widens them only on an exact 1 / 2, so an anomalous raw value
|
||||
// draws as ST_SINGLE rather than vanishing (S1 review finding).
|
||||
var dats = new FakeMeshExtractorDats();
|
||||
RegisterCellTexturedSurface(dats);
|
||||
var cellStruct = BuildQuadCellStruct((RetailCullMode)7);
|
||||
var extractor = new MeshExtractor(dats, NullLogger.Instance, sideStagedSink: null);
|
||||
|
||||
ObjectMeshData mesh = Assert.IsType<ObjectMeshData>(
|
||||
extractor.PrepareCellStructMeshData(
|
||||
id: 1, cellStruct, surfaceOverrides: [2], Matrix4x4.Identity, CancellationToken.None));
|
||||
|
||||
TextureBatchData batch = Assert.Single(Assert.Single(mesh.TextureBatches).Value);
|
||||
Assert.Equal(6, batch.Indices.Count);
|
||||
Assert.Equal(4, mesh.Vertices.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrepareCellStructMeshData_TwoSlotsSameSurfaceDid_RemainTwoDistinctSubsets()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue