226 lines
10 KiB
C#
226 lines
10 KiB
C#
using System.Collections.Generic;
|
|
using System.Numerics;
|
|
using AcDream.App.Rendering;
|
|
using DatReaderWriter;
|
|
using DatReaderWriter.DBObjs;
|
|
using DatReaderWriter.Options;
|
|
using Xunit;
|
|
|
|
namespace AcDream.App.Tests.Rendering;
|
|
|
|
/// <summary>
|
|
/// Sanctuary cathedral seam captured 2026-08-27. The stationary player is in
|
|
/// F4180104 while chase-camera zoom crosses the coincident outside portals at
|
|
/// world Y=48 and the viewer root switches between two separate buildings:
|
|
/// B3={0103,0104,0105} and B4={0106..0111}. The opposite cathedral half must
|
|
/// remain available through the interior-root building look-in on both sides.
|
|
/// </summary>
|
|
[Trait("Lane", "InstalledDat")]
|
|
public sealed class SanctuaryPortalSeamTests
|
|
{
|
|
private const uint Landblock = 0xF4180000u;
|
|
private const uint Cell0104 = Landblock | 0x0104u;
|
|
private const uint Cell0106 = Landblock | 0x0106u;
|
|
|
|
private static Matrix4x4 ViewProjection(Vector3 eye)
|
|
{
|
|
// Derived from the two exact flap-sweep points. Extending their zoom
|
|
// ray reaches the stable chase target at the player's head.
|
|
var target = new Vector3(36.033f, 49.638f, 171.353f);
|
|
var view = Matrix4x4.CreateLookAt(eye, target, Vector3.UnitZ);
|
|
var projection = Matrix4x4.CreatePerspectiveFieldOfView(
|
|
1.2f, 893f / 522f, 1f, 5000f);
|
|
return view * projection;
|
|
}
|
|
|
|
private static IReadOnlyList<LoadedCell> Cells(
|
|
Dictionary<uint, LoadedCell> cells,
|
|
uint firstLow,
|
|
uint lastLow)
|
|
{
|
|
var result = new List<LoadedCell>();
|
|
for (uint low = firstLow; low <= lastLow; low++)
|
|
result.Add(cells[Landblock | low]);
|
|
return result;
|
|
}
|
|
|
|
[Fact]
|
|
public void CapturedZoomHandoff_BothRootsBuildTheExactOppositeCathedralSeed()
|
|
{
|
|
string? datDir = CornerFloodReplayTests.ResolveDatDir();
|
|
if (datDir is null)
|
|
{
|
|
Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory; see docs/release-gate.md.");
|
|
}
|
|
|
|
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
|
LandBlockInfo info = Assert.IsType<LandBlockInfo>(dats.Get<LandBlockInfo>(Landblock | 0xfffeu));
|
|
Dictionary<uint, LoadedCell> cells =
|
|
Issue120ReciprocalPingPongTests.LoadAllInteriorCells(dats, Landblock);
|
|
LoadedCell? Lookup(uint id) => cells.TryGetValue(id, out LoadedCell? cell) ? cell : null;
|
|
|
|
IReadOnlyList<LoadedCell> building3 = Cells(cells, 0x0103u, 0x0105u);
|
|
IReadOnlyList<LoadedCell> building4 = Cells(cells, 0x0106u, 0x0111u);
|
|
Assert.Equal(0x01001FB2u, info.Buildings[3].ModelId);
|
|
Assert.Equal(0x01001FB3u, info.Buildings[4].ModelId);
|
|
|
|
var captures = new[]
|
|
{
|
|
new
|
|
{
|
|
Name = "near/root0104",
|
|
Eye = new Vector3(32.742317f, 48.034306f, 172.447845f),
|
|
Root = cells[Cell0104],
|
|
Opposite = building4,
|
|
ExpectedOppositeCell = Cell0106,
|
|
ExpectedSeedPortal = 2,
|
|
OppositeBuildingIndex = 4,
|
|
},
|
|
new
|
|
{
|
|
Name = "far/root0106",
|
|
Eye = new Vector3(32.308865f, 47.823200f, 172.592255f),
|
|
Root = cells[Cell0106],
|
|
Opposite = building3,
|
|
ExpectedOppositeCell = Cell0104,
|
|
ExpectedSeedPortal = 0,
|
|
OppositeBuildingIndex = 3,
|
|
},
|
|
};
|
|
|
|
foreach (var capture in captures)
|
|
{
|
|
Matrix4x4 viewProjection = ViewProjection(capture.Eye);
|
|
PortalVisibilityFrame main = PortalVisibilityBuilder.Build(
|
|
capture.Root, capture.Eye, Lookup, viewProjection);
|
|
PortalVisibilityFrame lookIn = PortalVisibilityBuilder.ConstructViewBuilding(
|
|
capture.Opposite,
|
|
capture.Eye,
|
|
Lookup,
|
|
viewProjection,
|
|
maxSeedDistance: float.PositiveInfinity,
|
|
seedRegion: main.OutsideView.Polygons);
|
|
|
|
Assert.Contains(capture.ExpectedOppositeCell, lookIn.OrderedVisibleCells);
|
|
Assert.True(
|
|
lookIn.CellViews.TryGetValue(capture.ExpectedOppositeCell, out CellView? view)
|
|
&& view.Polygons.Count > 0,
|
|
$"{capture.Name} must retain a clipped aperture for the opposite cathedral half");
|
|
ExteriorPortalSeed seed = Assert.Single(lookIn.ExteriorSeedPortals);
|
|
Assert.Equal(capture.ExpectedOppositeCell, seed.CellId);
|
|
Assert.Equal(capture.ExpectedSeedPortal, seed.PortalIndex);
|
|
Assert.NotEmpty(seed.View.Polygons);
|
|
|
|
using ClipFrame clipFrame = ClipFrame.NoClip();
|
|
ClipFrameAssembly assembly = ClipFrameAssembler.Assemble(
|
|
clipFrame,
|
|
main);
|
|
ClipFrameAssembler.AppendLookInFrames(
|
|
clipFrame,
|
|
[lookIn],
|
|
assembly);
|
|
ClipViewSlice[] nestedSlices =
|
|
assembly.LookInCellToViewSlices[
|
|
new LookInClipCell(0, capture.ExpectedOppositeCell)];
|
|
Assert.Equal(view.Polygons.Count, nestedSlices.Length);
|
|
Assert.All(nestedSlices, slice => Assert.True(slice.Slot > 0));
|
|
|
|
Assert.Contains(
|
|
info.Buildings[capture.OppositeBuildingIndex].Portals,
|
|
portal => portal.OtherCellId == (capture.ExpectedOppositeCell & 0xffffu)
|
|
&& portal.OtherPortalId == capture.ExpectedSeedPortal);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void ReportedLateralTransition_BothRootsKeepTheOppositeFacadePortal()
|
|
{
|
|
string? datDir = CornerFloodReplayTests.ResolveDatDir();
|
|
if (datDir is null)
|
|
Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory.");
|
|
|
|
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
|
Dictionary<uint, LoadedCell> cells =
|
|
Issue120ReciprocalPingPongTests.LoadAllInteriorCells(dats, Landblock);
|
|
LoadedCell? Lookup(uint id) => cells.TryGetValue(id, out LoadedCell? cell) ? cell : null;
|
|
|
|
static (PortalVisibilityFrame Main, PortalVisibilityFrame LookIn) Build(
|
|
Vector3 eye,
|
|
Vector3 target,
|
|
LoadedCell root,
|
|
IReadOnlyList<LoadedCell> opposite,
|
|
Func<uint, LoadedCell?> lookup)
|
|
{
|
|
Matrix4x4 viewProjection = Matrix4x4.CreateLookAt(eye, target, Vector3.UnitZ)
|
|
* Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 3f, 1.6f, 0.1f, 5000f);
|
|
PortalVisibilityFrame main = PortalVisibilityBuilder.Build(
|
|
root, eye, lookup, viewProjection);
|
|
PortalVisibilityFrame lookIn = PortalVisibilityBuilder.ConstructViewBuilding(
|
|
opposite, eye, lookup, viewProjection, seedRegion: main.OutsideView.Polygons);
|
|
return (main, lookIn);
|
|
}
|
|
|
|
(PortalVisibilityFrame Main, PortalVisibilityFrame LookIn) from0106 = Build(
|
|
new Vector3(31.189594f, 46.280170f, 171.783646f),
|
|
new Vector3(32.787731f, 46.275948f, 171.354993f),
|
|
cells[Cell0106],
|
|
Cells(cells, 0x0103u, 0x0105u),
|
|
Lookup);
|
|
(PortalVisibilityFrame Main, PortalVisibilityFrame LookIn) from0104 = Build(
|
|
new Vector3(31.198704f, 49.733287f, 171.783646f),
|
|
new Vector3(32.796841f, 49.729065f, 171.354993f),
|
|
cells[Cell0104],
|
|
Cells(cells, 0x0106u, 0x0111u),
|
|
Lookup);
|
|
|
|
Assert.Equal([Cell0106, Landblock | 0x010Fu], from0106.Main.OrderedVisibleCells);
|
|
Assert.Equal([Cell0104], from0106.LookIn.OrderedVisibleCells);
|
|
ExteriorPortalSeed seed0104 = Assert.Single(from0106.LookIn.ExteriorSeedPortals);
|
|
Assert.Equal(Cell0104, seed0104.CellId);
|
|
Assert.Equal(0, seed0104.PortalIndex);
|
|
Assert.NotEmpty(seed0104.View.Polygons);
|
|
|
|
Assert.Equal([Cell0104], from0104.Main.OrderedVisibleCells);
|
|
Assert.Equal([Cell0106, Landblock | 0x010Fu], from0104.LookIn.OrderedVisibleCells);
|
|
ExteriorPortalSeed seed0106 = Assert.Single(from0104.LookIn.ExteriorSeedPortals);
|
|
Assert.Equal(Cell0106, seed0106.CellId);
|
|
Assert.Equal(2, seed0106.PortalIndex);
|
|
Assert.NotEmpty(seed0106.View.Polygons);
|
|
}
|
|
|
|
[Fact]
|
|
public void ReportedExactSteamSeam_Root0104KeepsTheOppositeCathedralPortal()
|
|
{
|
|
string? datDir = CornerFloodReplayTests.ResolveDatDir();
|
|
if (datDir is null)
|
|
Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory.");
|
|
|
|
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
|
Dictionary<uint, LoadedCell> cells =
|
|
Issue120ReciprocalPingPongTests.LoadAllInteriorCells(dats, Landblock);
|
|
LoadedCell? Lookup(uint id) => cells.TryGetValue(id, out LoadedCell? cell) ? cell : null;
|
|
|
|
// Live ACDREAM_PROBE_CELL capture for the user-reported stationary
|
|
// frame at player [32.792290, 48.000618, 169.804993]. The chase eye is
|
|
// only ~4 mm across the coincident 0104/0106 exterior portal plane.
|
|
var eye = new Vector3(31.189665f, 48.004852f, 171.784988f);
|
|
var target = new Vector3(32.792290f, 48.000618f, 171.354993f);
|
|
Matrix4x4 viewProjection = Matrix4x4.CreateLookAt(eye, target, Vector3.UnitZ)
|
|
* Matrix4x4.CreatePerspectiveFieldOfView(1.2f, 1555f / 1019f, 1f, 5000f);
|
|
|
|
PortalVisibilityFrame main = PortalVisibilityBuilder.Build(
|
|
cells[Cell0104], eye, Lookup, viewProjection);
|
|
PortalVisibilityFrame lookIn = PortalVisibilityBuilder.ConstructViewBuilding(
|
|
Cells(cells, 0x0106u, 0x0111u),
|
|
eye,
|
|
Lookup,
|
|
viewProjection,
|
|
seedRegion: main.OutsideView.Polygons);
|
|
|
|
Assert.Contains(Cell0106, lookIn.OrderedVisibleCells);
|
|
ExteriorPortalSeed seed = Assert.Single(lookIn.ExteriorSeedPortals);
|
|
Assert.Equal(Cell0106, seed.CellId);
|
|
Assert.Equal(2, seed.PortalIndex);
|
|
Assert.NotEmpty(seed.View.Polygons);
|
|
}
|
|
}
|