refactor(world): own live environment state
Move the DAT sky, selected day group, world clock, weather, AdminEnvirons bridge, and debug cycles into a one-shot WorldEnvironmentController while preserving GameWindow's public aliases and accepted startup/session/render order. Correct the named retail citations and register the remaining environment audio and fog/radar gaps. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
557eb7ef6b
commit
d09e246d3a
19 changed files with 606 additions and 368 deletions
|
|
@ -88,6 +88,41 @@ public sealed class GameWindowSlice8BoundaryTests
|
|||
Assert.DoesNotContain("PrepareResources(", postStart, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorldEnvironment_IsOwnedAndGameWindowOnlyComposesItsTypedEdges()
|
||||
{
|
||||
string source = GameWindowSource();
|
||||
string load = MethodBody(
|
||||
"private void OnLoad()",
|
||||
"private AcDream.App.Net.LiveSessionHost");
|
||||
string sessionFactory = MethodBody(
|
||||
"private AcDream.App.Net.LiveSessionEventRouter CreateLiveSessionEventRouter(",
|
||||
"private AcDream.App.Net.LiveInventorySessionBindings");
|
||||
|
||||
Assert.Contains(
|
||||
"private readonly AcDream.App.World.WorldEnvironmentController _worldEnvironment =",
|
||||
source,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("_worldEnvironment.Initialize(region!);", load, StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(
|
||||
sessionFactory,
|
||||
"new AcDream.App.Net.LiveEnvironmentSessionSink(",
|
||||
"_worldEnvironment.ApplyAdminEnvirons,",
|
||||
"_worldEnvironment.SynchronizeFromServer)");
|
||||
Assert.DoesNotContain("_loadedSkyDesc", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("_loadedSkyDayIndex", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("private void RefreshSkyForCurrentDay()", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("private void OnEnvironChanged(", source, StringComparison.Ordinal);
|
||||
|
||||
string cycleTime = MethodBody(
|
||||
"private void CycleTimeOfDay()",
|
||||
"private void CycleWeather()");
|
||||
AssertAppearsInOrder(
|
||||
cycleTime,
|
||||
"_worldEnvironment.CycleTimeOfDay();",
|
||||
"_debugVm?.AddToast(message);");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InputAction_PreservesRetailAcceptedPriority()
|
||||
{
|
||||
|
|
|
|||
187
tests/AcDream.App.Tests/World/WorldEnvironmentControllerTests.cs
Normal file
187
tests/AcDream.App.Tests/World/WorldEnvironmentControllerTests.cs
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Tests.World;
|
||||
|
||||
[Collection(WorldEnvironmentControllerCollection.Name)]
|
||||
public sealed class WorldEnvironmentControllerTests
|
||||
{
|
||||
[Fact]
|
||||
public void Initialize_InstallsDatDayGroupAndSeedsOfflineNoon()
|
||||
{
|
||||
List<string> log = [];
|
||||
var controller = CreateController(log);
|
||||
LoadedSkyDesc sky = SingleGroupSky("Sunny", begin: 0f);
|
||||
|
||||
controller.Initialize(sky, zeroTimeOfYear: null);
|
||||
|
||||
Assert.Same(sky.DayGroups[0], controller.ActiveDayGroup);
|
||||
Assert.Equal(WeatherKind.Clear, controller.Weather.Kind);
|
||||
Assert.Equal(1.0, controller.WorldTime.TickSize);
|
||||
Assert.InRange(controller.DayFraction, 0.499f, 0.501f);
|
||||
Assert.Contains(log, line => line.Contains("loaded Region 0x13000000", StringComparison.Ordinal));
|
||||
Assert.Contains(log, line => line.Contains("DayGroup[0] \"Sunny\"", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SynchronizeFromServer_RefreshesAtMostOnceWithinSameDay()
|
||||
{
|
||||
List<string> log = [];
|
||||
var controller = CreateController(log);
|
||||
controller.Initialize(SingleGroupSky("Cloudy", begin: 0f), zeroTimeOfYear: null);
|
||||
|
||||
controller.SynchronizeFromServer(100_000.0);
|
||||
int selectedAfterFirstSync = log.Count(IsDayGroupSelection);
|
||||
controller.SynchronizeFromServer(100_000.0);
|
||||
|
||||
Assert.Equal(selectedAfterFirstSync, log.Count(IsDayGroupSelection));
|
||||
Assert.Equal(WeatherKind.Overcast, controller.Weather.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initialize_IsOneShotAndRetainsFirstPublishedEnvironment()
|
||||
{
|
||||
var controller = CreateController([]);
|
||||
LoadedSkyDesc first = SingleGroupSky("Sunny", begin: 0f);
|
||||
controller.Initialize(first, zeroTimeOfYear: null);
|
||||
|
||||
InvalidOperationException error = Assert.Throws<InvalidOperationException>(
|
||||
() => controller.Initialize(
|
||||
SingleGroupSky("Cloudy", begin: 0f),
|
||||
zeroTimeOfYear: 3600.0));
|
||||
|
||||
Assert.Contains("one-shot", error.Message, StringComparison.Ordinal);
|
||||
Assert.Same(first.DayGroups[0], controller.ActiveDayGroup);
|
||||
Assert.Equal(WeatherKind.Clear, controller.Weather.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initialize_WithoutGameTimeRestoresDocumentedFallbackOrigin()
|
||||
{
|
||||
double previous = DerethDateTime.OriginOffsetTicks;
|
||||
try
|
||||
{
|
||||
DerethDateTime.SetOriginOffsetFromDat(3600.0);
|
||||
var controller = CreateController([]);
|
||||
|
||||
controller.Initialize(SingleGroupSky("Sunny", begin: 0f), zeroTimeOfYear: null);
|
||||
|
||||
Assert.Equal(
|
||||
DerethDateTime.DayFractionOriginOffsetTicks,
|
||||
DerethDateTime.OriginOffsetTicks);
|
||||
Assert.InRange(controller.DayFraction, 0.499f, 0.501f);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DerethDateTime.SetOriginOffsetFromDat(previous);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0x00u, EnvironOverride.None)]
|
||||
[InlineData(0x01u, EnvironOverride.RedFog)]
|
||||
[InlineData(0x06u, EnvironOverride.BlackFog2)]
|
||||
public void ApplyAdminEnvirons_FogValuesSetStickyOverride(
|
||||
uint raw,
|
||||
EnvironOverride expected)
|
||||
{
|
||||
List<string> log = [];
|
||||
var controller = CreateController(log);
|
||||
|
||||
controller.ApplyAdminEnvirons(raw);
|
||||
|
||||
Assert.Equal(expected, controller.Weather.Override);
|
||||
Assert.Contains(log, line => line.Contains(expected.ToString(), StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyAdminEnvirons_SoundValuePreservesWeatherAndLogsRetailName()
|
||||
{
|
||||
List<string> log = [];
|
||||
var controller = CreateController(log);
|
||||
controller.Weather.Override = EnvironOverride.GreenFog;
|
||||
|
||||
controller.ApplyAdminEnvirons(0x78u);
|
||||
|
||||
Assert.Equal(EnvironOverride.GreenFog, controller.Weather.Override);
|
||||
Assert.Contains(log, line => line.Contains("Thunder3Sound", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CycleTimeOfDay_PreservesAcceptedFiveStepSequence()
|
||||
{
|
||||
var controller = CreateController([]);
|
||||
|
||||
Assert.Equal("Time override = 0.00", controller.CycleTimeOfDay());
|
||||
Assert.Equal(0f, controller.DayFraction);
|
||||
Assert.Equal("Time override = 0.25", controller.CycleTimeOfDay());
|
||||
Assert.Equal(0.25f, controller.DayFraction);
|
||||
Assert.Equal("Time override = 0.50", controller.CycleTimeOfDay());
|
||||
Assert.Equal(0.5f, controller.DayFraction);
|
||||
Assert.Equal("Time override = 0.75", controller.CycleTimeOfDay());
|
||||
Assert.Equal(0.75f, controller.DayFraction);
|
||||
Assert.Equal("Time override cleared", controller.CycleTimeOfDay());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CycleWeather_PreservesAcceptedOrder()
|
||||
{
|
||||
var controller = CreateController([]);
|
||||
|
||||
Assert.Equal("Weather = Overcast", controller.CycleWeather());
|
||||
Assert.Equal(WeatherKind.Overcast, controller.Weather.Kind);
|
||||
Assert.Equal("Weather = Rain", controller.CycleWeather());
|
||||
Assert.Equal(WeatherKind.Rain, controller.Weather.Kind);
|
||||
Assert.Equal("Weather = Snow", controller.CycleWeather());
|
||||
Assert.Equal(WeatherKind.Snow, controller.Weather.Kind);
|
||||
Assert.Equal("Weather = Storm", controller.CycleWeather());
|
||||
Assert.Equal(WeatherKind.Storm, controller.Weather.Kind);
|
||||
Assert.Equal("Weather = Clear", controller.CycleWeather());
|
||||
Assert.Equal(WeatherKind.Clear, controller.Weather.Kind);
|
||||
}
|
||||
|
||||
private static WorldEnvironmentController CreateController(List<string> log) =>
|
||||
new(
|
||||
new WorldTimeService(SkyStateProvider.Default()),
|
||||
new WeatherSystem(),
|
||||
log.Add);
|
||||
|
||||
private static LoadedSkyDesc SingleGroupSky(string name, float begin)
|
||||
{
|
||||
var keyframe = new SkyKeyframe(
|
||||
Begin: begin,
|
||||
SunHeadingDeg: 0f,
|
||||
SunPitchDeg: 90f,
|
||||
DirColor: Vector3.One,
|
||||
DirBright: 1f,
|
||||
AmbColor: Vector3.One,
|
||||
AmbBright: 1f,
|
||||
FogColor: Vector3.Zero,
|
||||
FogDensity: 0f);
|
||||
var group = new DayGroupData
|
||||
{
|
||||
ChanceOfOccur = 100f,
|
||||
Name = name,
|
||||
SkyTimes =
|
||||
[
|
||||
new DatSkyKeyframeData { Keyframe = keyframe },
|
||||
],
|
||||
};
|
||||
return new LoadedSkyDesc
|
||||
{
|
||||
TickSize = 0.8,
|
||||
LightTickSize = 0.2,
|
||||
DayGroups = [group],
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsDayGroupSelection(string line) =>
|
||||
line.Contains("→ DayGroup[", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[CollectionDefinition(Name, DisableParallelization = true)]
|
||||
public sealed class WorldEnvironmentControllerCollection
|
||||
{
|
||||
public const string Name = "World environment process-global calendar";
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue