Replace the outdoor root's single unified reverse-portal flood (whose root-level portal-side test oscillated as the chase eye grazed a doorway — the measured flood 2<->6) with retail's per-building floods. - OutdoorCellNode.Build(uint): portal-less land root; floods only itself -> full-screen OutsideView -> terrain (PortalVisibilityBuilder IsOutdoorNode seed). - PortalVisibilityBuilder.ConstructViewBuilding: per-building flood seeded at a building's own finite entrance (retail ConstructView(CBldPortal) 0x5a59a0 via DrawPortal 0x5a5ab0 / portal_draw_portals_only 0x53d870). Entrance-bounded -> consistent ~2-cell depth (measured retail cell_draw_num, handoff OPTION-A 3.4). - RetailPViewRenderer.DrawInside: when the root is the outdoor node, group nearby cells by BuildingId and merge each per-building flood into the frame before assembly; existing shells/object-list draw path unchanged. 48 m seed cutoff. - GameWindow: pass flat NearbyBuildingCells only on outdoor-node frames. Tests: +3 PortalVisibilityRobustnessTests (per-building touches ~2 cells, membership stable under the measured 36 um eye jitter). UnifiedFloodTests retired (its subject, the unified flood from the outdoor node, is removed); surviving full-screen-OutsideView coverage moved to OutdoorCellNodeTests. App Rendering 207/207, Core movement 14/14. Conformance-verified sound; the grazing-doorway flap is the visual acceptance test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
52 lines
2.4 KiB
C#
52 lines
2.4 KiB
C#
using System;
|
|
using System.Numerics;
|
|
using AcDream.App.Rendering;
|
|
using Xunit;
|
|
|
|
namespace AcDream.App.Tests.Rendering;
|
|
|
|
/// <summary>
|
|
/// The OUTDOOR render root (R-A2, 2026-06-08): a portal-less <see cref="LoadedCell"/> whose only job is
|
|
/// to root the render outdoors so the flood seeds OutsideView FULL-SCREEN (terrain draws). Buildings are
|
|
/// flooded SEPARATELY per-building (<see cref="PortalVisibilityBuilder.ConstructViewBuilding"/>), so the
|
|
/// node carries NO reverse portals — the pre-R-A2 reverse-portal unified flood (which oscillated at the
|
|
/// doorway) is gone. Its tests moved here; the old <c>UnifiedFloodTests</c> was retired.
|
|
/// </summary>
|
|
public class OutdoorCellNodeTests
|
|
{
|
|
[Fact]
|
|
public void Build_ReturnsPortallessOutdoorRoot()
|
|
{
|
|
uint outdoorId = 0xA9B40031;
|
|
var node = OutdoorCellNode.Build(outdoorId);
|
|
|
|
Assert.Equal(outdoorId, node.CellId);
|
|
Assert.True(node.IsOutdoorNode); // the flag PortalVisibilityBuilder keys the full-screen OutsideView on
|
|
Assert.True(node.SeenOutside);
|
|
Assert.Equal(Matrix4x4.Identity, node.WorldTransform);
|
|
Assert.Empty(node.Portals); // R-A2: no reverse portals into buildings (buildings flood per-building)
|
|
}
|
|
|
|
[Fact]
|
|
public void Build_OutdoorRoot_SeedsFullScreenOutsideView_OnlyNodeVisible()
|
|
{
|
|
// The load-bearing outdoor-terrain behavior: rooting at the IsOutdoorNode node seeds OutsideView
|
|
// with the FULL-SCREEN NDC quad, so ClipFrameAssembler yields TerrainMode != Skip and
|
|
// DrawLandscapeThroughOutsideView draws terrain/sky/scenery everywhere. The portal-less node
|
|
// resolves to exactly {node} — no interior cells flooded from the root (those come per-building).
|
|
var node = OutdoorCellNode.Build(0xA9B40031);
|
|
|
|
var eye = new Vector3(0, -3, 1);
|
|
var view = Matrix4x4.CreateLookAt(eye, new Vector3(0, 5, 1), Vector3.UnitZ);
|
|
var proj = Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 3f, 16f / 9f, 1f, 5000f);
|
|
|
|
var frame = PortalVisibilityBuilder.Build(node, eye, _ => null, view * proj);
|
|
|
|
Assert.Equal(new[] { 0xA9B40031u }, frame.OrderedVisibleCells); // only the node
|
|
Assert.False(frame.OutsideView.IsEmpty);
|
|
Assert.Equal(-1f, frame.OutsideView.MinX, 3);
|
|
Assert.Equal(-1f, frame.OutsideView.MinY, 3);
|
|
Assert.Equal(1f, frame.OutsideView.MaxX, 3);
|
|
Assert.Equal(1f, frame.OutsideView.MaxY, 3);
|
|
}
|
|
}
|