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
|
|
@ -30,12 +30,18 @@ public static class CellMesh
|
|||
/// ported from <c>D3DPolyRender::ConstructMesh</c> @0x0059DFA0, contract
|
||||
/// §3.4) targets a surface slot whose resolved <c>Surface.Type</c> is
|
||||
/// TEXTURED (<see cref="RetailUntexturedSurfacePolicy.IsUntextured"/>
|
||||
/// is false) — exactly the condition under which
|
||||
/// <c>MeshExtractor.PrepareCellStructMeshData</c> would emit at least
|
||||
/// one subset into the prepared package: retail's built-EnvCell draw
|
||||
/// admission is <c>(Surface.Type & (BASE1_IMAGE|BASE1_CLIPMAP)) != 0</c>
|
||||
/// is false) — retail's built-EnvCell draw admission
|
||||
/// <c>(Surface.Type & (BASE1_IMAGE|BASE1_CLIPMAP)) != 0</c>
|
||||
/// (<c>RenderDeviceD3D::DrawEnvCell</c> @0x0059F170 →
|
||||
/// <c>D3DPolyRender::DrawMesh(..., arg4=1)</c> @0x0059D4A0, contract §4).
|
||||
/// <c>D3DPolyRender::DrawMesh(..., arg4=1)</c> @0x0059D4A0, contract §4),
|
||||
/// evaluated without resolving the surface's texture dependency chain.
|
||||
/// It is therefore a conservative superset of what
|
||||
/// <c>MeshExtractor.PrepareCellStructMeshData</c> actually emits: the
|
||||
/// extractor additionally drops a textured slot whose SurfaceTexture,
|
||||
/// RenderSurface, palette, or pixel format cannot be resolved (an
|
||||
/// extractor-side quarantine outcome, logged there), which this predicate
|
||||
/// still reports as drawable. Missing texture data is a DAT defect, not a
|
||||
/// retail admission decision, so the predicate follows the admission rule.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Side candidates come only from <c>CPolygon::sides_type</c>
|
||||
|
|
|
|||
|
|
@ -148,22 +148,33 @@ public static class CellStructSideCandidates
|
|||
/// branch returns a span over a static readonly array.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Unknown/out-of-range values (anything other than 0, 1, or 2) yield
|
||||
/// zero candidates. The installed DAT corpus and the retail header
|
||||
/// define only 0/1/2 (contract §3.4 closing paragraph); retail's own
|
||||
/// branching would fall through to the single-side shape for other
|
||||
/// values, but the port deliberately does not invent that fourth public
|
||||
/// semantic — an unrecognized raw value stays data for the caller to
|
||||
/// quarantine/report, not a silently-guessed geometry shape.
|
||||
/// Any value other than 1 or 2 takes the single-side shape. That is
|
||||
/// retail's literal branching, not a guess: <c>ConstructMesh</c>
|
||||
/// initializes both loop bounds to 1 and widens the side bound only on
|
||||
/// <c>sides_type == 2</c> and the copy bound only on
|
||||
/// <c>sides_type == 1</c> (pseudo-C 426837-426842 and 427058-427066;
|
||||
/// Ghidra <c>iStack_44 = 1; local_5c = 1; if (*piVar6 == 2) ...;
|
||||
/// if (*piVar6 == 1) ...</c>). The installed corpus contains only 0/1/2
|
||||
/// (pinned by the S1 installed-DAT scan); callers should still count a
|
||||
/// raw value outside that set as a data anomaly for diagnostics, but the
|
||||
/// geometry it produces is retail's, so no divergence row is needed.
|
||||
/// </remarks>
|
||||
public static ReadOnlySpan<CellStructSideCandidate> GetCandidates(int rawSidesType) => rawSidesType switch
|
||||
{
|
||||
0 => SingleCandidates,
|
||||
1 => DoubleCandidates,
|
||||
2 => BothCandidates,
|
||||
_ => ReadOnlySpan<CellStructSideCandidate>.Empty,
|
||||
_ => SingleCandidates,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// True for the three retail-defined <c>SidesType</c> values
|
||||
/// (<c>ST_SINGLE</c>=0, <c>ST_DOUBLE</c>=1, <c>ST_BOTH</c>=2,
|
||||
/// acclient.h:7372). Anything else is authored-data corruption that
|
||||
/// <see cref="GetCandidates"/> still renders as retail would (single
|
||||
/// side); callers use this only to report the anomaly.
|
||||
/// </summary>
|
||||
public static bool IsRetailDefinedSidesType(int rawSidesType) => rawSidesType is 0 or 1 or 2;
|
||||
|
||||
/// <summary>
|
||||
/// Retail's exact triangle-fan vertex index order for one triangle
|
||||
/// within a fan, per contract §3.4's "Fan index order" column: forward
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue