Revert "Campaign V slice V4a" - it lost world multisampling
This reverts ceec3bc4. Two independent reasons, either sufficient.
The rendering regression. The slice deleted TextRenderGlStateScope, which
saved GL_MULTISAMPLE and GL_SAMPLE_ALPHA_TO_COVERAGE on entry, disabled them
for the text pass, and restored them on exit (TextRenderGlStateScope.cs:111-112
and 153-154 at the parent commit). Its replacement bakes that state into the
text pipeline but nothing restores it, and GlGpuPassEncoder.Dispose does not
either. Every world renderer is still raw GL at this point in the campaign, so
from the first UI frame onward the world drew with multisampling disabled.
The offline pixel gate caught it: 1,791 of 563,200 compared pixels differed,
0.318% against a 0.001 threshold. The commit message attributed this to
wall-clock-driven ambient animation shifting phase, and committed through the
failure. That explanation does not survive its own control: capturing twice at
the reverted-to commit differs by 19 pixels and twice at the slice's own commit
by 8, while base-versus-head differs by 1,791 - a 224x gap that no shared-noise
source explains. An amplified difference image settles it visually: the changed
pixels are the silhouette edges of every tree, building and rock, with terrain
interiors, water and the entire UI untouched. That is the signature of losing
edge antialiasing, not of animated sprites.
This is the exact failure mode two existing memory notes already warn about -
a mid-frame renderer must set every GL state it uses rather than inherit it,
and issue #52's lesson that a rendering migration must audit per-pass GL state
before declaring itself done.
The scope. The brief was three small leaf renderers plus additive frame-
lifecycle wiring, roughly ten files. The commit changed 334 files with 3,665
insertions and 3,845 deletions, including 323 public-to-internal visibility
conversions across the App assembly, 55 test files, two retired conformance
tests, and a self-described temporary escape hatch for bridging raw-GL viewport
textures. Even without the regression, that is not separable into the part
worth keeping and the part worth dropping.
Reverting rather than patching because the good work here - the RHI frame
lifecycle wiring and a genuine render-state-cache staleness fix - is small
enough to redo cleanly against a tightened spec, while untangling it from 300+
files of unrelated churn is not.
Post-revert: Release build clean, App suite back to 3,843 passed / 3 skipped,
offline pixel gate passing at 19 differing pixels.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ceec3bc440
commit
9aaf97e785
334 changed files with 3841 additions and 3661 deletions
|
|
@ -1,11 +1,11 @@
|
|||
// CellVisibility.cs — portal-based interior cell visibility system.
|
||||
// CellVisibility.cs — portal-based interior cell visibility system.
|
||||
//
|
||||
// Stage 3 (2026-06-02): FindCameraCell + grace-frame AABB fallback deleted.
|
||||
// The physics membership answer (CellGraph.CurrCell) is now the mandatory root;
|
||||
// ComputeVisibilityFromRoot(null, …) returns null (outdoor root) rather than
|
||||
// ComputeVisibilityFromRoot(null, …) returns null (outdoor root) rather than
|
||||
// falling back to an independent AABB position resolve. This matches retail's
|
||||
// CellManager::ChangePosition (0x004559B0) which does not re-derive the cell
|
||||
// from a static position — it reads the swept transition-owned CurrCell.
|
||||
// from a static position — it reads the swept transition-owned CurrCell.
|
||||
//
|
||||
// This file is intentionally free of GL / rendering types. It depends only on
|
||||
// System.Numerics so it can be unit-tested without a GPU context.
|
||||
|
|
@ -23,7 +23,7 @@ namespace AcDream.App.Rendering;
|
|||
/// A loaded EnvCell with portal connectivity and spatial data, used by
|
||||
/// <see cref="CellVisibility"/> for portal-traversal visibility decisions.
|
||||
/// </summary>
|
||||
internal sealed class LoadedCell
|
||||
public sealed class LoadedCell
|
||||
{
|
||||
/// <summary>Full 32-bit cell ID, e.g. 0xA9B40105.</summary>
|
||||
public uint CellId;
|
||||
|
|
@ -87,7 +87,7 @@ internal sealed class LoadedCell
|
|||
public uint? BuildingId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Phase U.4c: the stab_list PVS as full (landblock-prefixed) cell ids — retail
|
||||
/// Phase U.4c: the stab_list PVS as full (landblock-prefixed) cell ids — retail
|
||||
/// CEnvCell.stab_list (acclient.h ~30925), the stable set of cells potentially
|
||||
/// visible from this cell, precomputed by the AC content tools. Refreshed only at
|
||||
/// hydration (= retail's per-cell-entry grab_visible_cells, decomp:311878).
|
||||
|
|
@ -98,7 +98,7 @@ internal sealed class LoadedCell
|
|||
public IReadOnlyList<uint> VisibleCells = System.Array.Empty<uint>();
|
||||
|
||||
/// <summary>
|
||||
/// Phase U.4c: retail CEnvCell.seen_outside (acclient.h ~30925) — this cell sees
|
||||
/// Phase U.4c: retail CEnvCell.seen_outside (acclient.h ~30925) — this cell sees
|
||||
/// the exterior (an exit portal is reachable from it). Retail gates the landscape
|
||||
/// data + draw decision on the camera cell's value (RenderNormalMode decomp:92649,
|
||||
/// grab_visible_cells decomp:311878). The stable anchor for the terrain-draw test.
|
||||
|
|
@ -107,7 +107,7 @@ internal sealed class LoadedCell
|
|||
|
||||
/// <summary>
|
||||
/// Render unification (2026-06-07): true for the synthetic OUTDOOR cell node built by
|
||||
/// <see cref="OutdoorCellNode.Build"/> — the outdoor world modelled as a flood-graph cell whose
|
||||
/// <see cref="OutdoorCellNode.Build"/> — the outdoor world modelled as a flood-graph cell whose
|
||||
/// shell is the landscape. <see cref="PortalVisibilityBuilder.Build"/> seeds OutsideView
|
||||
/// full-screen when the root carries this flag (so terrain/sky/scenery draw as the node's shell).
|
||||
/// An explicit flag, not a cell-id heuristic: interior EnvCell ids are >= 0x100 in production but
|
||||
|
|
@ -123,19 +123,19 @@ internal sealed class LoadedCell
|
|||
/// <see cref="OtherPortalId"/> is the dat's reciprocal back-link: the index of
|
||||
/// the portal WITHIN the neighbour cell's portal list that points back through
|
||||
/// this same opening. Retail indexes the reciprocal directly via this field
|
||||
/// (<c>arg2->other_portal_id</c>, decomp:433557) rather than scanning — which
|
||||
/// (<c>arg2->other_portal_id</c>, decomp:433557) rather than scanning — which
|
||||
/// is what lets a cell with TWO portals to the same neighbour resolve each
|
||||
/// opening against its OWN reciprocal polygon instead of the first match.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal readonly record struct CellPortalInfo(
|
||||
public readonly record struct CellPortalInfo(
|
||||
ushort OtherCellId, ushort PolygonId, ushort Flags, ushort OtherPortalId);
|
||||
|
||||
/// <summary>
|
||||
/// Clip plane derived from a portal polygon, in cell-local space.
|
||||
/// Plane equation: Normal.X*x + Normal.Y*y + Normal.Z*z + D = 0.
|
||||
/// </summary>
|
||||
internal struct PortalClipPlane
|
||||
public struct PortalClipPlane
|
||||
{
|
||||
/// <summary>Plane normal (cell-local space, unit length).</summary>
|
||||
public Vector3 Normal;
|
||||
|
|
@ -146,8 +146,8 @@ internal struct PortalClipPlane
|
|||
/// <summary>
|
||||
/// Which half-space is "inside" this cell (the side from which you look outward
|
||||
/// through the portal):
|
||||
/// 0 → camera dot-product must be >= 0 (positive half-space is inside)
|
||||
/// 1 → camera dot-product must be <= 0 (negative half-space is inside)
|
||||
/// 0 → camera dot-product must be >= 0 (positive half-space is inside)
|
||||
/// 1 → camera dot-product must be <= 0 (negative half-space is inside)
|
||||
/// Determined from cell centroid position relative to the portal plane.
|
||||
/// Ported from ACME EnvCellManager.cs ~line 404.
|
||||
/// </summary>
|
||||
|
|
@ -155,12 +155,12 @@ internal struct PortalClipPlane
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase U.4c flap probe (diagnostic — OBSOLETE as of Stage 3). Previously tracked
|
||||
/// Phase U.4c flap probe (diagnostic — OBSOLETE as of Stage 3). Previously tracked
|
||||
/// which branch of FindCameraCell (now deleted) resolved the camera cell. Retained
|
||||
/// for binary compatibility with the [flap-cam] probe log site in GameWindow.cs that
|
||||
/// still prints <see cref="LastCameraCellResolution"/> (always None post-Stage 3).
|
||||
/// </summary>
|
||||
internal enum CameraCellResolution
|
||||
public enum CameraCellResolution
|
||||
{
|
||||
/// <summary>No cell contains the eye (outdoors), or not yet resolved.</summary>
|
||||
None,
|
||||
|
|
@ -171,14 +171,14 @@ internal enum CameraCellResolution
|
|||
/// <summary>The eye is inside a cell found by the full brute-force scan.</summary>
|
||||
BruteForce,
|
||||
/// <summary>The eye is inside NO cell, but the previous cell is kept alive for a
|
||||
/// few grace frames — the "stale root" case the flap probe watches for.</summary>
|
||||
/// few grace frames — the "stale root" case the flap probe watches for.</summary>
|
||||
Grace,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a portal-based visibility BFS from the camera cell.
|
||||
/// </summary>
|
||||
internal sealed class VisibilityResult
|
||||
public sealed class VisibilityResult
|
||||
{
|
||||
/// <summary>Full cell IDs (e.g. 0x01D90105) that should be rendered this frame.</summary>
|
||||
public HashSet<uint> VisibleCellIds { get; init; } = new();
|
||||
|
|
@ -207,7 +207,7 @@ internal sealed class VisibilityResult
|
|||
/// Ported faithfully from ACME's EnvCellManager.cs portal-visibility region.
|
||||
/// Constants and control flow match the ACME implementation.
|
||||
/// </summary>
|
||||
internal sealed class CellVisibility
|
||||
public sealed class CellVisibility
|
||||
{
|
||||
// ------------------------------------------------------------------
|
||||
// Constants (ACME ground-truth values)
|
||||
|
|
@ -234,7 +234,7 @@ internal sealed class CellVisibility
|
|||
public VisibilityResult? LastVisibilityResult { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stage 3 (2026-06-02): always <see cref="CameraCellResolution.None"/> — the FindCameraCell
|
||||
/// Stage 3 (2026-06-02): always <see cref="CameraCellResolution.None"/> — the FindCameraCell
|
||||
/// AABB grace-frame resolver was deleted; the physics membership answer is the sole root.
|
||||
/// Retained for the [flap-cam] probe log line in GameWindow.cs.
|
||||
/// </summary>
|
||||
|
|
@ -292,7 +292,7 @@ internal sealed class CellVisibility
|
|||
/// <summary>
|
||||
/// Phase A8 (2026-05-28): enumerates the loaded cells that belong to a
|
||||
/// landblock prefix. Used by <c>LandblockRenderPublisher</c> when building
|
||||
/// the per-landblock <c>BuildingRegistry</c> — the per-frame
|
||||
/// the per-landblock <c>BuildingRegistry</c> — the per-frame
|
||||
/// <c>drainedCells</c> dict misses cells loaded on prior frames, so the
|
||||
/// stamping loop in <see cref="Wb.BuildingLoader.Build"/> needs access to
|
||||
/// every cell currently in the landblock to ensure <c>BuildingId</c> is set.
|
||||
|
|
@ -356,15 +356,15 @@ internal sealed class CellVisibility
|
|||
/// <summary>
|
||||
/// UCG W2/Stage 3: compute visibility from a supplied root cell (the physics membership
|
||||
/// answer). When <paramref name="root"/> is null (pre-spawn, or player outside all indoor
|
||||
/// cells), returns <c>null</c> — the caller interprets null as the outdoor root (no portal
|
||||
/// cells), returns <c>null</c> — the caller interprets null as the outdoor root (no portal
|
||||
/// frame, everything slot 0, terrain ungated). The legacy AABB FindCameraCell fallback is
|
||||
/// deleted as of Stage 3; <see cref="CellGraph.CurrCell"/> is the sole authority.
|
||||
/// Retail anchor: CellManager::ChangePosition @ 0x004559B0 reads the transition-owned
|
||||
/// curr_cell — it does NOT re-derive from a static position.
|
||||
/// curr_cell — it does NOT re-derive from a static position.
|
||||
/// </summary>
|
||||
/// <param name="root">
|
||||
/// The render-registered <see cref="LoadedCell"/> that physics determined the player is inside,
|
||||
/// or null when pre-spawn or the player is in an outdoor landcell. Null → outdoor root path.
|
||||
/// or null when pre-spawn or the player is in an outdoor landcell. Null → outdoor root path.
|
||||
/// </param>
|
||||
/// <param name="fallbackPos">
|
||||
/// Used as the viewer position for the portal-side test in the BFS when root is non-null.
|
||||
|
|
@ -389,13 +389,13 @@ internal sealed class CellVisibility
|
|||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// FindCameraCell — DELETED in Stage 3 (2026-06-02)
|
||||
// FindCameraCell — DELETED in Stage 3 (2026-06-02)
|
||||
// ------------------------------------------------------------------
|
||||
// The AABB + grace-frame camera-cell resolver was removed. Production code
|
||||
// now exclusively uses ComputeVisibilityFromRoot(root, …) where root is the
|
||||
// now exclusively uses ComputeVisibilityFromRoot(root, …) where root is the
|
||||
// transition-owned CellGraph.CurrCell (set by ResolveCellId/Stage 2 physics).
|
||||
// Retail anchor: CellManager::ChangePosition (0x004559B0) reads curr_cell
|
||||
// from the sweep — it never re-derives from a static position.
|
||||
// from the sweep — it never re-derives from a static position.
|
||||
//
|
||||
// GetVisibleCells (used by ComputeVisibility below for test compatibility)
|
||||
// still uses the brute-force AABB scan internally.
|
||||
|
|
@ -471,12 +471,12 @@ internal sealed class CellVisibility
|
|||
|
||||
/// <summary>
|
||||
/// UCG W2: BFS visibility traversal from a pre-resolved root cell.
|
||||
/// The root is the correct membership answer (supplied by the caller —
|
||||
/// The root is the correct membership answer (supplied by the caller —
|
||||
/// physics CurrCell via <see cref="ComputeVisibilityFromRoot"/>, or AABB
|
||||
/// scan via <see cref="GetVisibleCells"/> for test compat).
|
||||
///
|
||||
/// The BFS body is byte-identical to the original GetVisibleCells
|
||||
/// implementation — only root acquisition was extracted out.
|
||||
/// implementation — only root acquisition was extracted out.
|
||||
/// </summary>
|
||||
private VisibilityResult? GetVisibleCellsFromRoot(LoadedCell cameraCell, Vector3 cameraPos)
|
||||
{
|
||||
|
|
@ -499,7 +499,7 @@ internal sealed class CellVisibility
|
|||
{
|
||||
var portal = cell.Portals[i];
|
||||
|
||||
// Exit portal → outdoor terrain should be visible.
|
||||
// Exit portal → outdoor terrain should be visible.
|
||||
if (portal.OtherCellId == 0xFFFF)
|
||||
{
|
||||
result.HasExitPortalVisible = true;
|
||||
|
|
@ -522,8 +522,8 @@ internal sealed class CellVisibility
|
|||
var localCam = Vector3.Transform(cameraPos, cell.InverseWorldTransform);
|
||||
float dot = Vector3.Dot(plane.Normal, localCam) + plane.D;
|
||||
|
||||
// InsideSide == 0 → inside is positive half-space; reject if dot < -ε.
|
||||
// InsideSide == 1 → inside is negative half-space; reject if dot > ε.
|
||||
// InsideSide == 0 → inside is positive half-space; reject if dot < -ε.
|
||||
// InsideSide == 1 → inside is negative half-space; reject if dot > ε.
|
||||
// Source: ACME EnvCellManager.cs lines 1458-1459.
|
||||
if (plane.InsideSide == 0 && dot < -PointInCellEpsilon)
|
||||
continue;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue