acdream/tests/AcDream.Core.Tests/Rendering/CellGraphRootTests.cs
Erik 573c5559a0 test(render): Stage 3 T3.4 — CellGraphRootTests (6 tests)
New test file: tests/AcDream.Core.Tests/Rendering/CellGraphRootTests.cs.

Tests 1-3: render-branch predicates (rootSeenOutside, playerInsideCell, renderSky):
  RootSelection_OutdoorRoot_NullCurrCell_SeenOutsideDefaultsToTrue
  RootSelection_BuildingInterior_SeenOutside_SkyRenderedAndSunKept
  RootSelection_Dungeon_NoSeenOutside_SkyNotRenderedAndSunZeroed

Tests 4-6: CellGraph.FindVisibleChildCell:
  FindVisibleChildCell_PlayerCellContains_ReturnsPlayerCell
  FindVisibleChildCell_StabListContains_ReturnsNeighbour
  FindVisibleChildCell_NeitherContains_ReturnsNull

All 6 pass. Core suite: 12 pre-existing failures (same baseline), 1276 passing.
App suite: 160/160 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 15:37:19 +02:00

208 lines
8.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// CellGraphRootTests.cs — Stage 3 (2026-06-02): unit tests for render-root
// selection logic and CellGraph.FindVisibleChildCell.
//
// Tests 13: render-branch predicates. These exercise the formulas in
// GameWindow.OnRender directly (Stage 3):
// rootSeenOutside = physicsRoot?.SeenOutside ?? true
// playerInsideCell = cameraInsideCell && !rootSeenOutside
// renderSky = !cameraInsideCell || rootSeenOutside
//
// Retail anchors:
// CellManager::ChangePosition @ 0x004559B0 (pseudo_c:94649) — landscape kept
// live iff seen_outside; sun zeroed when inside a sealed interior.
// SmartBox::RenderNormalMode @ 0x00453aa0 (pseudo_c:92635) — sky gate.
//
// Tests 46: CellGraph.FindVisibleChildCell (retail find_visible_child_cell
// @ 0x0052dc50, pseudo_c:311397).
using System.Collections.Generic;
using System.Numerics;
using AcDream.Core.World.Cells;
using Xunit;
namespace AcDream.Core.Tests.Rendering;
public class CellGraphRootTests
{
// ------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------
/// <summary>
/// Synthetic EnvCell with an identity transform and axis-aligned bounds so
/// PointInCell returns true for points inside [min, max].
/// seenOutside = false → sealed dungeon; true → building interior/exterior.
/// </summary>
private static EnvCell MakeEnvCell(uint id, Vector3 min, Vector3 max, bool seenOutside = false)
=> new EnvCell(
id,
Matrix4x4.Identity,
Matrix4x4.Identity,
min, max,
portals: new List<CellPortal>(),
stabList: new List<uint>(),
seenOutside: seenOutside,
containmentBsp: null);
/// <summary>
/// EnvCell with an explicit stab list (used by FindVisibleChildCell tests).
/// </summary>
private static EnvCell MakeEnvCellWithStab(uint id, Vector3 min, Vector3 max,
IReadOnlyList<uint> stabList, bool seenOutside = false)
=> new EnvCell(
id,
Matrix4x4.Identity,
Matrix4x4.Identity,
min, max,
portals: new List<CellPortal>(),
stabList: stabList,
seenOutside: seenOutside,
containmentBsp: null);
// ------------------------------------------------------------------
// Predicate helpers — mirror the formulas in GameWindow.OnRender (Stage 3)
// ------------------------------------------------------------------
private static bool RootSeenOutside(EnvCell? physicsRoot) => physicsRoot?.SeenOutside ?? true;
// Retail CellManager::ChangePosition (0x004559B0): sun/sun-ambient zeroed
// when inside a sealed interior (cameraInsideCell=true, seen_outside=false).
// Building interiors with seen_outside keep the sun; outdoor root always keeps it.
private static bool PlayerInsideCell(bool cameraInsideCell, bool rootSeenOutside)
=> cameraInsideCell && !rootSeenOutside;
// Stage 3 sky gate: visible unless inside a sealed dungeon.
private static bool RenderSky(bool cameraInsideCell, bool rootSeenOutside)
=> !cameraInsideCell || rootSeenOutside;
// ------------------------------------------------------------------
// Test 1: outdoor root (null CurrCell) — seen_outside=true, sky rendered,
// player NOT considered inside a cell.
// ------------------------------------------------------------------
[Fact]
public void RootSelection_OutdoorRoot_NullCurrCell_SeenOutsideDefaultsToTrue()
{
// When physicsRoot==null (pre-spawn, or outdoor landcell) the ?? true
// gives rootSeenOutside=true; cameraInsideCell=false (no cell).
bool rootSeenOutside = RootSeenOutside(null);
bool cameraInsideCell = false; // no indoor root → ComputeVisibilityFromRoot returns null
bool playerInsideCell = PlayerInsideCell(cameraInsideCell, rootSeenOutside);
bool renderSky = RenderSky(cameraInsideCell, rootSeenOutside);
Assert.True(rootSeenOutside, "outdoor null root → rootSeenOutside=true");
Assert.False(playerInsideCell, "outdoor → playerInsideCell=false (sun kept live)");
Assert.True(renderSky, "outdoor → sky rendered");
}
// ------------------------------------------------------------------
// Test 2: building interior (seen_outside=true) — sky rendered (interim
// full-screen until Stage 4 clips it), sun kept live.
// ------------------------------------------------------------------
[Fact]
public void RootSelection_BuildingInterior_SeenOutside_SkyRenderedAndSunKept()
{
// A cottage cell: seen_outside=true (exit portal exists).
var physicsRoot = MakeEnvCell(0xA9B40170u, Vector3.Zero, new Vector3(10, 10, 10),
seenOutside: true);
bool rootSeenOutside = RootSeenOutside(physicsRoot);
bool cameraInsideCell = true; // inside a cell → ComputeVisibilityFromRoot non-null
bool playerInsideCell = PlayerInsideCell(cameraInsideCell, rootSeenOutside);
bool renderSky = RenderSky(cameraInsideCell, rootSeenOutside);
Assert.True(rootSeenOutside, "building interior seen_outside=true");
Assert.False(playerInsideCell, "seen_outside=true → sun kept live");
Assert.True(renderSky, "seen_outside=true → sky rendered (Stage 4 will clip to doorway)");
}
// ------------------------------------------------------------------
// Test 3: sealed dungeon (seen_outside=false) — sky suppressed, sun zeroed.
// ------------------------------------------------------------------
[Fact]
public void RootSelection_Dungeon_NoSeenOutside_SkyNotRenderedAndSunZeroed()
{
// A dungeon cell: seen_outside=false (no exit portal reachable).
var physicsRoot = MakeEnvCell(0x01D90100u, Vector3.Zero, new Vector3(10, 10, 10),
seenOutside: false);
bool rootSeenOutside = RootSeenOutside(physicsRoot);
bool cameraInsideCell = true; // inside a cell → non-null visibility result
bool playerInsideCell = PlayerInsideCell(cameraInsideCell, rootSeenOutside);
bool renderSky = RenderSky(cameraInsideCell, rootSeenOutside);
Assert.False(rootSeenOutside, "dungeon seen_outside=false");
Assert.True(playerInsideCell, "sealed dungeon → playerInsideCell=true (sun zeroed)");
Assert.False(renderSky, "sealed dungeon → sky suppressed");
}
// ------------------------------------------------------------------
// Test 4: FindVisibleChildCell — query inside the root cell → returns root.
// ------------------------------------------------------------------
[Fact]
public void FindVisibleChildCell_PlayerCellContains_ReturnsPlayerCell()
{
var graph = new CellGraph();
var root = MakeEnvCell(0xA9B40170u, Vector3.Zero, new Vector3(10, 10, 10));
graph.Add(root);
var point = new Vector3(5, 5, 5); // inside root's bounds
var result = graph.FindVisibleChildCell(root.Id, point);
Assert.NotNull(result);
Assert.Equal(root.Id, result!.Id);
}
// ------------------------------------------------------------------
// Test 5: FindVisibleChildCell — query inside a stab-list neighbour → returns that cell.
// ------------------------------------------------------------------
[Fact]
public void FindVisibleChildCell_StabListContains_ReturnsNeighbour()
{
var graph = new CellGraph();
// Root at [0,10]. Neighbour B at [20,30]. Root's stab list includes B.
uint rootId = 0xA9B40170u;
uint neighId = 0xA9B40171u;
var neigh = MakeEnvCell(neighId, new Vector3(20, 0, 0), new Vector3(30, 10, 10));
var root = MakeEnvCellWithStab(rootId, Vector3.Zero, new Vector3(10, 10, 10),
stabList: new List<uint> { neighId });
graph.Add(root);
graph.Add(neigh);
var point = new Vector3(25, 5, 5); // inside neighbour, outside root
var result = graph.FindVisibleChildCell(rootId, point);
Assert.NotNull(result);
Assert.Equal(neighId, result!.Id);
}
// ------------------------------------------------------------------
// Test 6: FindVisibleChildCell — query outside all cells → null.
// ------------------------------------------------------------------
[Fact]
public void FindVisibleChildCell_NeitherContains_ReturnsNull()
{
var graph = new CellGraph();
uint rootId = 0xA9B40170u;
var root = MakeEnvCellWithStab(rootId, Vector3.Zero, new Vector3(10, 10, 10),
stabList: new List<uint>());
graph.Add(root);
var pointFarAway = new Vector3(999, 999, 999); // outside all cells
var result = graph.FindVisibleChildCell(rootId, pointFarAway);
Assert.Null(result);
}
}