T1 (fused BR-2/3): retail frame order - dynamics last, punch+seal, shell chop deleted
The complete retail drawing order in one installment (per the amended plan: every installment is a COMPLETE retail behavior - the half-ported punch of88be519is re-landed here WITH the ordering that makes it correct): static world (sky/terrain/weather/shells/scenery) -> aperture depth writes (interior SEAL at true depth / outdoor+look-in PUNCH to far-Z; PortalDepthMaskRenderer, DrawPortalPolyInternal Ghidra 0x0059bc90) -> interior cells WHOLE, far-to-near, drawn once (DrawCells Loop 2, Ghidra 0x005a4840; use_built_mesh pc:427905) -> per-cell STATIC object lists -> ALL dynamics LAST (DrawDynamicsLast), depth-tested, never hard-clipped InteriorEntityPartition: new contract - every ServerGuid != 0 entity goes to Dynamics regardless of cell (indoor/outdoor/unresolved/hidden); ByCell carries only dat-baked indoor statics of visible cells; Outdoor renamed OutdoorStatic. Fixes the audit's livedynamic-invisible-under-interior-roots divergence as a side effect (live entities are never dropped by the visibility set; culling is T3's viewcone). DELETED (retail has no counterpart): the gl_ClipDistance shell chop (927fd8fenable +9ce335eoutdoor scoping + UseShellClipRouting + the per-slice shell loop + clipShells param) - retail never clips cell geometry; aperture exactness = punch/seal + z-buffer + this order. The old per-slice scissored AABB depth clear is replaced by retail's single gated full clear (ClearDepthForInterior). The interior-root LiveDynamic top-up draw and the look-in's dynamics involvement are gone (one last pass, no double-draws). Closes at the T5 gate (expected): #114 (chop deleted), the char-eaten-by- doorway regression (ordering), outdoor interiors-through-doorways (punch); #108's render half (seal) - its membership half stays re-attributed. Suites: build green, App 226 green (partition tests rewritten to the T1 contract), Core 1398 + 4 pre-existing #99-era + 1 skip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1e5db94f0e
commit
579c8b06bc
4 changed files with 234 additions and 200 deletions
|
|
@ -6,6 +6,19 @@ using Xunit;
|
|||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// T1 (fused BR-2/3) partition contract — retail draw order: static world
|
||||
/// first, every server-spawned DYNAMIC in the frame's single LAST pass.
|
||||
/// <list type="bullet">
|
||||
/// <item>ALL ServerGuid != 0 entities land in <c>Dynamics</c> regardless of
|
||||
/// cell (indoor, outdoor, unresolved, even non-visible cells — retail never
|
||||
/// drops a live entity for visibility-set reasons; culling is the
|
||||
/// viewcone's job, T3).</item>
|
||||
/// <item><c>ByCell</c> carries only dat-baked indoor statics of VISIBLE
|
||||
/// cells (drawn with their cell).</item>
|
||||
/// <item><c>OutdoorStatic</c> carries shells/scenery (the world pass).</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class InteriorEntityPartitionTests
|
||||
{
|
||||
private const uint CellA = 0xA9B40170;
|
||||
|
|
@ -30,7 +43,7 @@ public class InteriorEntityPartitionTests
|
|||
(IReadOnlyDictionary<uint, WorldEntity>?)null) };
|
||||
|
||||
[Fact]
|
||||
public void Partitions_LiveAndStaticEntities_ByCellOutdoorAndFallback()
|
||||
public void AllServerSpawned_GoToDynamics_StaticsSplitByCellAndOutdoor()
|
||||
{
|
||||
var unresolvedLive = Ent(1, serverGuid: 0x5000000A, parentCell: null);
|
||||
var liveNpcInCell = Ent(2, serverGuid: 0x80001234, parentCell: CellA);
|
||||
|
|
@ -43,22 +56,25 @@ public class InteriorEntityPartitionTests
|
|||
var result = InteriorEntityPartition.Partition(
|
||||
visible, OneLb(0xA9B4FFFF, unresolvedLive, liveNpcInCell, staticA, staticB, scenery, liveOutdoor));
|
||||
|
||||
Assert.Single(result.LiveDynamic);
|
||||
Assert.Contains(unresolvedLive, result.LiveDynamic);
|
||||
// Every server-spawned entity is a dynamic — drawn in the last pass.
|
||||
Assert.Equal(3, result.Dynamics.Count);
|
||||
Assert.Contains(unresolvedLive, result.Dynamics);
|
||||
Assert.Contains(liveNpcInCell, result.Dynamics);
|
||||
Assert.Contains(liveOutdoor, result.Dynamics);
|
||||
|
||||
Assert.Equal(2, result.ByCell[CellA].Count);
|
||||
Assert.Contains(liveNpcInCell, result.ByCell[CellA]);
|
||||
// Indoor statics ride with their (visible) cell.
|
||||
Assert.Single(result.ByCell[CellA]);
|
||||
Assert.Contains(staticA, result.ByCell[CellA]);
|
||||
Assert.Single(result.ByCell[CellB]);
|
||||
Assert.Contains(staticB, result.ByCell[CellB]);
|
||||
|
||||
Assert.Equal(2, result.Outdoor.Count);
|
||||
Assert.Contains(scenery, result.Outdoor);
|
||||
Assert.Contains(liveOutdoor, result.Outdoor);
|
||||
// Outdoor statics (shells/scenery) ride with the world pass.
|
||||
Assert.Single(result.OutdoorStatic);
|
||||
Assert.Contains(scenery, result.OutdoorStatic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndoorEntity_InNonVisibleCell_IsDropped()
|
||||
public void HiddenCell_DropsStatics_ButNeverDynamics()
|
||||
{
|
||||
var staticHidden = Ent(3, serverGuid: 0, parentCell: HiddenCell);
|
||||
var liveHidden = Ent(4, serverGuid: 0x80001234, parentCell: HiddenCell);
|
||||
|
|
@ -67,9 +83,14 @@ public class InteriorEntityPartitionTests
|
|||
var result = InteriorEntityPartition.Partition(
|
||||
visible, OneLb(0xA9B4FFFF, staticHidden, liveHidden));
|
||||
|
||||
// A static in a non-flooded cell is not drawn this frame…
|
||||
Assert.False(result.ByCell.ContainsKey(HiddenCell));
|
||||
Assert.Empty(result.Outdoor);
|
||||
Assert.Empty(result.LiveDynamic);
|
||||
Assert.Empty(result.OutdoorStatic);
|
||||
|
||||
// …but a LIVE entity is never dropped by the visibility set (the old
|
||||
// contract dropped it — the audit's livedynamic-invisible divergence).
|
||||
Assert.Single(result.Dynamics);
|
||||
Assert.Contains(liveHidden, result.Dynamics);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -85,5 +106,7 @@ public class InteriorEntityPartitionTests
|
|||
new HashSet<uint> { CellA }, OneLb(0xA9B4FFFF, noMesh));
|
||||
|
||||
Assert.False(result.ByCell.ContainsKey(CellA));
|
||||
Assert.Empty(result.Dynamics);
|
||||
Assert.Empty(result.OutdoorStatic);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue