acdream/tests/AcDream.App.Tests/Rendering/WorldSceneRendererTests.cs
Erik fdc4fd496d fix(ui): gate — no void frames around the login wormhole; vitals icons centered
The login tunnel now covers from the first world-facing frame (the
sky-void backdrop can never present pre-tunnel) and holds through an
atomic tunnel-to-world swap at reveal completion — the void is
structurally unreachable on both edges, pinned by frame-sequence tests
across WorldSceneRenderer/WorldRevealCoordinator/LocalPlayerTeleport-
Controller/RuntimeWorldTransitState. Vitals detail icons draw at their
authored centered offsets in both stacked and side-by-side layouts.
Implemented and live-probed by the fix agent; finalized by the lead
after the agent parked post-verification (gates re-run green:
App 5512/3, Runtime 1747/0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 12:30:51 +02:00

798 lines
28 KiB
C#

using System.Numerics;
using System.Reflection;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Selection;
using AcDream.App.Rendering.Vfx;
using AcDream.App.Streaming;
using AcDream.Core.Rendering;
using AcDream.Core.World;
using AcDream.Runtime;
using AcDream.Runtime.World;
namespace AcDream.App.Tests.Rendering;
public sealed class WorldSceneRendererTests
{
[Fact]
public void PortalViewport_PublishesEmptySelectionFrameAndSkipsWorldOwners()
{
var rig = new Rig(portalVisible: true, waitingForLogin: false, clipRoot: null);
WorldRenderFrameOutcome result = rig.Renderer.Render(default);
Assert.Equal(default, result);
Assert.Equal(["selection:begin", "selection:complete"], rig.Calls);
}
[Fact]
public void QuiescedGeneration_PublishesEmptySelectionFrameAndSkipsEveryWorldOwner()
{
var transit = new RuntimeWorldTransitState();
var availability = new WorldGenerationAvailabilityState(transit);
RuntimeWorldTransitTestDriver.BeginPortal(
transit,
0x11340021u);
var rig = new Rig(
portalVisible: false,
waitingForLogin: false,
clipRoot: null,
availability: availability);
WorldRenderFrameOutcome result = rig.Renderer.Render(default);
Assert.Equal(default, result);
Assert.Equal(["selection:begin", "selection:complete"], rig.Calls);
}
[Fact]
public void LoginWait_IsOwnedByTheFrameGate_NotByARendererShortCircuit()
{
// Gate-fix round (2026-08-17): the sky-only waiting skip is DELETED.
// Live pre-world frames present the portal-viewport frame shape —
// LocalPlayerTeleportRenderStateSource folds IsWaitingForLogin into
// PortalViewportVisible (retail's pre-player gameplay screen draws
// NO world: black behind the UI, never a sky-only backdrop), so in
// production this renderer NEVER runs while waiting. One gate
// computes the frame's visibility and this phase only enforces it: a
// waiting flag without the portal cover (unreachable in production)
// draws the ordinary flat world rather than re-implementing a second
// gate here.
var rig = new Rig(portalVisible: false, waitingForLogin: true, clipRoot: null);
WorldRenderFrameOutcome result = rig.Renderer.Render(default);
Assert.True(result.NormalWorldDrawn);
Assert.True(rig.Frames.WaitingForLogin);
Assert.Contains("flat:terrain", rig.Calls);
// The production waiting frame: the portal cover is set, the world
// is skipped whole, and only the selection frame brackets run.
var covered = new Rig(portalVisible: true, waitingForLogin: true, clipRoot: null);
Assert.Equal(default, covered.Renderer.Render(default));
Assert.Equal(["selection:begin", "selection:complete"], covered.Calls);
}
[Fact]
public void FlatWorld_PreservesTerrainEntityParticleWeatherAndCompletionOrder()
{
var rig = new Rig(portalVisible: false, waitingForLogin: false, clipRoot: null);
WorldRenderFrameOutcome result = rig.Renderer.Render(default);
Assert.Equal(new WorldRenderFrameOutcome(3, 8, true), result);
Assert.Equal(
[
"selection:begin",
"alpha:begin",
"frame:build",
"passes:begin",
"flat:clip",
"flat:sky",
"flat:terrain",
"flat:entities",
"frame:clear-cells",
"passes:disable-clip",
"particles:global",
"flat:weather",
"diagnostics:signature:OutdoorRoot",
"diagnostics:draw",
"alpha:end",
"visibility:mark",
"visibility:complete",
"selection:complete",
],
rig.Calls);
Assert.Equal(rig.Foundation, rig.Frames.Foundation);
Assert.False(rig.Frames.WaitingForLogin);
Assert.Same(rig.DayGroup, rig.Frames.ActiveDayGroup);
Assert.Equal(rig.Foundation, rig.Passes.SkyFoundation);
Assert.Equal(rig.Foundation, rig.Passes.WeatherFoundation);
Assert.Same(rig.DayGroup, rig.Passes.SkyDayGroup);
Assert.Same(rig.DayGroup, rig.Passes.WeatherDayGroup);
Assert.Equal(rig.DayFraction, rig.Passes.SkyDayFraction);
Assert.Equal(rig.DayFraction, rig.Passes.WeatherDayFraction);
}
[Fact]
public void PViewWorld_UsesOnePViewProductAndPublishesItsDrawableCells()
{
var root = new LoadedCell
{
CellId = 0x01010001u,
IsOutdoorNode = false,
};
var rig = new Rig(portalVisible: false, waitingForLogin: false, clipRoot: root);
WorldRenderFrameOutcome result = rig.Renderer.Render(default);
Assert.True(result.NormalWorldDrawn);
Assert.Equal(
[
"selection:begin",
"alpha:begin",
"frame:build",
"passes:begin",
"pview:draw",
"visibility:mark",
"frame:observe-cells",
"diagnostics:pview",
"passes:disable-clip",
"particles:none",
"diagnostics:signature:RetailPViewInside",
"diagnostics:draw",
"alpha:end",
"visibility:mark",
"visibility:complete",
"selection:complete",
],
rig.Calls);
Assert.DoesNotContain("flat:clip", rig.Calls);
Assert.DoesNotContain("flat:terrain", rig.Calls);
Assert.DoesNotContain("flat:entities", rig.Calls);
Assert.NotNull(rig.PView.LastInput);
Assert.Same(rig.DayGroup, rig.PView.LastInput!.ActiveDayGroup);
Assert.Equal(rig.DayFraction, rig.PView.LastInput.DayFraction);
Assert.Equal(rig.Foundation.Sky, rig.PView.LastInput.SkyKeyframe);
Assert.True(rig.PView.LastInput.RenderWeather);
Assert.Equal(root.CellId, rig.PView.LastInput.ViewerCellId);
Assert.Equal(0x01010002u, rig.PView.LastInput.PlayerCellId);
Assert.Equal(4, rig.PView.LastInput.RenderRadius);
}
[Fact]
public void PViewWorld_ReusesOneSynchronousFrameInputAcrossFrames()
{
var root = new LoadedCell
{
CellId = 0x01010001u,
IsOutdoorNode = false,
};
var rig = new Rig(
portalVisible: false,
waitingForLogin: false,
clipRoot: root);
rig.Renderer.Render(default);
RetailPViewFrameInput first = Assert.IsType<RetailPViewFrameInput>(
rig.PView.LastInput);
rig.Calls.Clear();
rig.Renderer.Render(default);
Assert.Same(first, rig.PView.LastInput);
Assert.Same(root, rig.PView.LastInput!.RootCell);
Assert.Equal(rig.DayFraction, rig.PView.LastInput.DayFraction);
Assert.Contains("pview:draw", rig.Calls);
}
[Fact]
public void OutdoorPView_UsesPViewOwnersForPostWorldParticlesWithoutFlatWeather()
{
var root = new LoadedCell
{
CellId = 0x01010001u,
IsOutdoorNode = true,
};
var rig = new Rig(portalVisible: false, waitingForLogin: false, clipRoot: root);
WorldRenderFrameOutcome result = rig.Renderer.Render(default);
Assert.True(result.NormalWorldDrawn);
Assert.Contains("particles:pviewScoped+unattached", rig.Calls);
Assert.Same(rig.PView.OutdoorSceneParticleEntityIds, rig.Passes.ParticleOwners);
Assert.Equal([0xCAFEu], rig.Passes.ParticleOwners);
Assert.DoesNotContain("flat:weather", rig.Calls);
}
[Fact]
public void SealedInteriorPView_DisablesWeatherAndPropagatesEnvironmentOverride()
{
var root = new LoadedCell
{
CellId = 0x01010001u,
IsOutdoorNode = false,
};
var rig = new Rig(
portalVisible: false,
waitingForLogin: false,
clipRoot: root,
playerSeenOutside: false,
environOverride: EnvironOverride.BlueFog);
rig.Renderer.Render(default);
Assert.NotNull(rig.PView.LastInput);
Assert.False(rig.PView.LastInput!.RenderWeather);
Assert.True(rig.PView.LastInput.EnvironOverrideActive);
}
[Fact]
public void PViewFailure_AbortsPortalStateAndRecoversOnTheNextFrame()
{
var root = new LoadedCell
{
CellId = 0x01010001u,
IsOutdoorNode = false,
};
var rig = new Rig(portalVisible: false, waitingForLogin: false, clipRoot: root);
rig.PView.ThrowOnDraw = true;
Assert.Throws<InvalidOperationException>(() => rig.Renderer.Render(default));
Assert.Equal(
[
"selection:begin",
"alpha:begin",
"frame:build",
"passes:begin",
"pview:draw",
"pview:abort",
"passes:abort",
"visibility:abort",
"alpha:abort",
"selection:abort",
],
rig.Calls);
rig.Calls.Clear();
rig.PView.ThrowOnDraw = false;
Assert.True(rig.Renderer.Render(default).NormalWorldDrawn);
Assert.Contains("selection:complete", rig.Calls);
}
[Fact]
public void DrawFailure_DoesNotPublishPartialAlphaSelectionOrParticleFrames()
{
var rig = new Rig(portalVisible: false, waitingForLogin: false, clipRoot: null);
rig.Passes.ThrowOnTerrain = true;
Assert.Throws<InvalidOperationException>(() => rig.Renderer.Render(default));
Assert.Equal(
[
"selection:begin",
"alpha:begin",
"frame:build",
"passes:begin",
"flat:clip",
"flat:sky",
"flat:terrain",
"passes:abort",
"visibility:abort",
"alpha:abort",
"selection:abort",
],
rig.Calls);
rig.Calls.Clear();
rig.Passes.ThrowOnTerrain = false;
WorldRenderFrameOutcome recovered = rig.Renderer.Render(default);
Assert.True(recovered.NormalWorldDrawn);
Assert.Contains("alpha:end", rig.Calls);
Assert.Contains("selection:complete", rig.Calls);
}
[Fact]
public void AbortFailure_IsAggregatedAfterEveryOtherFrameOwnerIsAborted()
{
var rig = new Rig(portalVisible: false, waitingForLogin: false, clipRoot: null);
rig.Passes.ThrowOnTerrain = true;
rig.Passes.ThrowOnAbort = true;
AggregateException failure = Assert.Throws<AggregateException>(
() => rig.Renderer.Render(default));
Assert.Contains(
failure.InnerExceptions,
error => error is InvalidOperationException
&& error.Message == "terrain failed");
Assert.Contains(
failure.InnerExceptions,
error => error is InvalidOperationException
&& error.Message == "pass abort failed");
Assert.Contains("visibility:abort", rig.Calls);
Assert.Contains("alpha:abort", rig.Calls);
Assert.Contains("selection:abort", rig.Calls);
rig.Calls.Clear();
rig.Passes.ThrowOnTerrain = false;
rig.Passes.ThrowOnAbort = false;
Assert.True(rig.Renderer.Render(default).NormalWorldDrawn);
}
[Fact]
public void GameWindow_DelegatesWorldRenderingWithoutOwningDrawBranches()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"FrameRootComposition.cs"));
Assert.Contains("new WorldSceneRenderer(", source);
Assert.Contains("new RenderFrameOrchestrator(", source);
Assert.DoesNotContain("_worldSceneRenderer", source);
Assert.DoesNotContain("_retailPViewRenderer.DrawInside(", source);
Assert.DoesNotContain("_retailAlphaQueue.BeginFrame();", source);
Assert.DoesNotContain("_terrainDrawDiagnostics!.Begin();", source);
Assert.DoesNotContain("SkipWorldGeometry:", source);
}
[Fact]
public void ExtractedWorldOwners_RetainNoWindowDelegateOrBorrowedPViewProduct()
{
Type[] ownerTypes =
[
typeof(WorldSceneRenderer),
typeof(WorldScenePassExecutor),
typeof(WorldSceneDiagnosticsController),
];
Type[] borrowedTypes =
[
typeof(RetailPViewFrameResult),
typeof(PortalVisibilityFrame),
typeof(ClipFrameAssembly),
];
foreach (Type owner in ownerTypes)
{
foreach (FieldInfo field in owner.GetFields(
BindingFlags.Instance
| BindingFlags.Public
| BindingFlags.NonPublic))
{
Assert.False(
typeof(GameWindow).IsAssignableFrom(field.FieldType),
$"{owner.Name}.{field.Name} retains GameWindow.");
Assert.False(
typeof(Delegate).IsAssignableFrom(field.FieldType),
$"{owner.Name}.{field.Name} retains a delegate callback.");
Assert.DoesNotContain(field.FieldType, borrowedTypes);
}
}
}
private sealed class Rig
{
public Rig(
bool portalVisible,
bool waitingForLogin,
LoadedCell? clipRoot,
bool? playerSeenOutside = null,
EnvironOverride environOverride = EnvironOverride.None,
IWorldGenerationAvailability? availability = null)
{
Calls = [];
DayGroup = new DayGroupData { Name = "sentinel-day-group" };
DayFraction = 0.375f;
Foundation = new RenderFrameFoundation(
portalVisible,
new SkyKeyframe(
0.25f,
35f,
45f,
new Vector3(0.1f, 0.2f, 0.3f),
0.8f,
new Vector3(0.4f, 0.5f, 0.6f),
0.7f,
new Vector3(0.7f, 0.8f, 0.9f),
0.2f),
new AtmosphereSnapshot(
WeatherKind.Clear,
1f,
new Vector3(0.1f, 0.2f, 0.3f),
1f,
100f,
FogMode.Linear,
0f,
environOverride));
var foundation = new FoundationSource(Foundation);
var login = new LoginSource(waitingForLogin);
var sky = new SkySource(DayGroup, DayFraction);
var frame = CreateFrame(
clipRoot,
playerSeenOutside ?? clipRoot is not null);
Frames = new FrameBuilder(Calls, frame);
var selection = new SelectionFrame(Calls);
var alpha = new AlphaFrame(Calls);
var visibility = new ParticleVisibility(Calls);
PView = new PViewRenderer(Calls);
Passes = new PassExecutor(Calls);
var diagnostics = new Diagnostics(Calls);
Renderer = new WorldSceneRenderer(
foundation,
login,
sky,
Frames,
new EntitySource(),
selection,
alpha,
visibility,
PView,
new PViewCells(),
Passes,
new WorldRenderRangeState(4, 12),
diagnostics,
availability);
}
public List<string> Calls { get; }
public RenderFrameFoundation Foundation { get; }
public DayGroupData DayGroup { get; }
public float DayFraction { get; }
public FrameBuilder Frames { get; }
public PViewRenderer PView { get; }
public PassExecutor Passes { get; }
public WorldSceneRenderer Renderer { get; }
}
private sealed class FoundationSource(RenderFrameFoundation foundation) :
IRenderFrameFoundationSource
{
public RenderFrameFoundation Foundation { get; } = foundation;
}
private sealed class LoginSource(bool waiting) : IRenderLoginStateSource
{
public bool IsWaitingForLogin { get; } = waiting;
}
private sealed class SkySource(
DayGroupData activeDayGroup,
float dayFraction) : IWorldSceneSkyStateSource
{
public DayGroupData? ActiveDayGroup { get; } = activeDayGroup;
public float DayFraction { get; } = dayFraction;
}
private sealed class FrameBuilder(List<string> calls, WorldRenderFrame frame) :
IWorldRenderFrameBuilder
{
public RenderFrameFoundation Foundation { get; private set; }
public bool WaitingForLogin { get; private set; }
public DayGroupData? ActiveDayGroup { get; private set; }
public WorldRenderFrame Build(
in RenderFrameFoundation foundation,
bool waitingForLogin,
DayGroupData? activeDayGroup)
{
calls.Add("frame:build");
Foundation = foundation;
WaitingForLogin = waitingForLogin;
ActiveDayGroup = activeDayGroup;
return frame;
}
public void ObserveDrawableCells(IReadOnlySet<uint> drawableCells) =>
calls.Add("frame:observe-cells");
public void ClearDrawableCells() => calls.Add("frame:clear-cells");
}
private sealed class EntitySource : IWorldSceneEntitySource
{
public IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
IReadOnlyList<WorldEntity> Entities,
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> LandblockEntries =>
Array.Empty<(uint, Vector3, Vector3, IReadOnlyList<WorldEntity>,
IReadOnlyDictionary<uint, WorldEntity>?)>();
public IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)> LandblockBounds =>
Array.Empty<(uint, Vector3, Vector3)>();
}
private sealed class SelectionFrame(List<string> calls) : IWorldSceneSelectionFrame
{
public void BeginFrame() => calls.Add("selection:begin");
public void CompleteFrame() => calls.Add("selection:complete");
public void AbortFrame() => calls.Add("selection:abort");
}
private sealed class AlphaFrame(List<string> calls) : IWorldSceneAlphaFrame
{
public void BeginFrame() => calls.Add("alpha:begin");
public void EndFrame() => calls.Add("alpha:end");
public void AbortFrame() => calls.Add("alpha:abort");
}
private sealed class ParticleVisibility(List<string> calls) :
IWorldSceneParticleVisibility
{
public void MarkVisibleCells(HashSet<uint> cellIds) =>
calls.Add("visibility:mark");
public void CompleteFrame() => calls.Add("visibility:complete");
public void AbortFrame() => calls.Add("visibility:abort");
}
private sealed class PViewRenderer : IWorldScenePViewRenderer
{
private readonly List<string> _calls;
private readonly RetailPViewFrameResult _interiorResult;
private readonly RetailPViewFrameResult _outdoorResult;
public PViewRenderer(List<string> calls)
{
_calls = calls;
_interiorResult = new RetailPViewFrameResult().Reset(
new PortalVisibilityFrame(),
new ClipFrameAssembly(),
[],
new InteriorEntityPartition.Result());
var outdoorPortalFrame = new PortalVisibilityFrame();
outdoorPortalFrame.OutsideView.Add(new ViewPolygon(
[
new Vector2(-1f, -1f),
new Vector2(1f, -1f),
new Vector2(1f, 1f),
new Vector2(-1f, 1f),
]));
_outdoorResult = new RetailPViewFrameResult().Reset(
outdoorPortalFrame,
ClipFrameAssembler.Assemble(ClipFrame.NoClip(), outdoorPortalFrame),
[],
new InteriorEntityPartition.Result());
}
public IReadOnlySet<uint> OutdoorSceneParticleEntityIds { get; } =
new HashSet<uint> { 0xCAFEu };
public RetailPViewFrameInput? LastInput { get; private set; }
public bool ThrowOnDraw { get; set; }
public RetailPViewFrameResult DrawInside(RetailPViewFrameInput input)
{
_calls.Add("pview:draw");
LastInput = input;
if (ThrowOnDraw)
throw new InvalidOperationException("pview failed");
return input.RootCell.IsOutdoorNode ? _outdoorResult : _interiorResult;
}
public void AbortFrame() => _calls.Add("pview:abort");
}
private sealed class PViewCells : IRetailPViewCellSource
{
public LoadedCell? Find(uint cellId) => null;
}
private sealed class PassExecutor(List<string> calls) : IWorldScenePassExecutor
{
public bool ThrowOnTerrain { get; set; }
public bool ThrowOnAbort { get; set; }
public HashSet<uint>? TerrainVisibleCellIds { get; } = [0x01010001u];
public RenderFrameFoundation? SkyFoundation { get; private set; }
public RenderFrameFoundation? WeatherFoundation { get; private set; }
public DayGroupData? SkyDayGroup { get; private set; }
public DayGroupData? WeatherDayGroup { get; private set; }
public float SkyDayFraction { get; private set; }
public float WeatherDayFraction { get; private set; }
public IReadOnlySet<uint>? ParticleOwners { get; private set; }
public void BeginFrame() => calls.Add("passes:begin");
public void PrepareFlatWorldClip() => calls.Add("flat:clip");
public void DrawFlatSky(
in WorldCameraFrame camera,
in RenderFrameFoundation foundation,
DayGroupData? activeDayGroup,
float dayFraction)
{
calls.Add("flat:sky");
SkyFoundation = foundation;
SkyDayGroup = activeDayGroup;
SkyDayFraction = dayFraction;
}
public void DrawFlatTerrain(in WorldCameraFrame camera, uint? playerLandblockId)
{
calls.Add("flat:terrain");
if (ThrowOnTerrain)
throw new InvalidOperationException("terrain failed");
}
public void DrawFlatEntities(
in WorldCameraFrame camera,
IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
IReadOnlyList<WorldEntity> Entities,
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> entries,
uint? playerLandblockId,
HashSet<uint> animatedEntityIds) => calls.Add("flat:entities");
public string DrawPostWorldParticles(
LoadedCell? clipRoot,
ClipFrameAssembly? clipAssembly,
in WorldCameraFrame camera,
IReadOnlySet<uint> outdoorOwnerIds,
string currentSignature)
{
ParticleOwners = outdoorOwnerIds;
string kind = clipRoot switch
{
null => "global",
{ IsOutdoorNode: true } => currentSignature == "none"
? "unattached"
: currentSignature + "+unattached",
_ => currentSignature,
};
calls.Add($"particles:{kind}");
return kind;
}
public void DrawFlatWeather(
in WorldCameraFrame camera,
in RenderFrameFoundation foundation,
DayGroupData? activeDayGroup,
float dayFraction)
{
calls.Add("flat:weather");
WeatherFoundation = foundation;
WeatherDayGroup = activeDayGroup;
WeatherDayFraction = dayFraction;
}
public void DisableClipDistances() => calls.Add("passes:disable-clip");
public void AbortFrame()
{
calls.Add("passes:abort");
if (ThrowOnAbort)
throw new InvalidOperationException("pass abort failed");
}
}
private sealed class Diagnostics(List<string> calls) : IWorldSceneDiagnostics
{
public CameraCellResolution CameraCellResolution => CameraCellResolution.None;
public void EmitPViewInput(
PortalVisibilityFrame portalFrame,
Matrix4x4 viewProjection,
LoadedCell clipRoot,
Vector3 cameraPosition,
Vector3 playerPosition) => calls.Add("diagnostics:pview");
public void EmitRenderSignature(
string branch,
LoadedCell? clipRoot,
LoadedCell? viewerRoot,
LoadedCell? playerRoot,
uint viewerCellId,
uint playerCellId,
bool playerIndoorGate,
bool cameraInsideCell,
bool renderSky,
bool drawSkyThisFrame,
bool terrainDrawn,
TerrainClipMode terrainClipMode,
bool skyDrawn,
bool depthClear,
bool outdoorSceneryDrawn,
int liveDynamicDrawnCount,
string sceneParticles,
RetailPViewFrameResult? pviewResult,
Vector3 cameraPosition,
Vector3 playerPosition) =>
calls.Add($"diagnostics:signature:{branch}");
public WorldSceneDiagnosticOutcome DrawAndPublish(
in WorldCameraFrame camera,
IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax)> bounds)
{
calls.Add("diagnostics:draw");
return new WorldSceneDiagnosticOutcome(3, 8);
}
}
private static WorldRenderFrame CreateFrame(
LoadedCell? clipRoot,
bool playerSeenOutside)
{
var camera = new FlyCamera { Position = new Vector3(1f, 2f, 3f) };
var cameraFrame = new WorldCameraFrame(
camera,
camera.Projection,
camera.View * camera.Projection,
default,
Matrix4x4.Identity,
camera.Position);
var roots = new WorldRootFrame(
PlayerRoot: null,
PlayerSeenOutside: playerSeenOutside,
ViewerCellId: clipRoot?.CellId ?? 0u,
ViewerEyePosition: camera.Position,
PlayerViewPosition: camera.Position,
ViewerRoot: clipRoot,
CameraInsideCell: clipRoot is not null,
RootSeenOutside: true,
PlayerInsideCell: false,
PlayerLandblockId: null,
RenderCenterLandblockX: 0,
RenderCenterLandblockY: 0,
PlayerCellId: 0x01010002u,
PlayerIndoorGate: false);
return new WorldRenderFrame(
cameraFrame,
roots,
new WorldBuildingFrame(null, Array.Empty<LoadedCell>()),
[]);
}
private static int CountOccurrences(string source, string value)
{
int count = 0;
int cursor = 0;
while ((cursor = source.IndexOf(value, cursor, StringComparison.Ordinal)) >= 0)
{
count++;
cursor += value.Length;
}
return count;
}
private static string FindRepoRoot()
{
DirectoryInfo? directory = new(AppContext.BaseDirectory);
while (directory is not null)
{
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
return directory.FullName;
directory = directory.Parent;
}
throw new DirectoryNotFoundException("Could not find AcDream.slnx.");
}
}