using System.Collections.Generic; using System.Numerics; using AcDream.Core.World; using DatReaderWriter.DBObjs; using DatReaderWriter.Enums; using DatReaderWriter.Types; using Xunit; namespace AcDream.Core.Tests.World; public sealed class SkyDescLoaderTests { /// /// Hand-build a Region with a minimal sky descriptor to feed the /// loader without needing real dat bytes. The LoadFromRegion /// separator exists precisely for this — keeps the parsing logic /// testable independent of DatCollection. /// private static Region MakeRegion(float dirBright, byte rBgrOrder) { var region = new Region(); region.PartsMask = PartsMask.HasSkyInfo; var sky = new SkyDesc { TickSize = 1.0, LightTickSize = 2.0, }; var dg = new DayGroup { ChanceOfOccur = 1.0f, }; var time = new SkyTimeOfDay { Begin = 0.5f, DirBright = dirBright, DirHeading = 180f, DirPitch = 70f, DirColor = new ColorARGB { Blue = 0, Green = 0, Red = rBgrOrder, Alpha = 255 }, AmbBright = 0.4f, AmbColor = new ColorARGB { Blue = 100, Green = 100, Red = 100, Alpha = 255 }, MinWorldFog = 120f, MaxWorldFog = 400f, WorldFogColor = new ColorARGB { Blue = 50, Green = 50, Red = 50, Alpha = 255 }, WorldFog = 1, // Linear }; dg.SkyTime.Add(time); sky.DayGroups.Add(dg); region.SkyInfo = sky; return region; } [Fact] public void LoadFromRegion_ConvertsFogFields() { var region = MakeRegion(dirBright: 1.5f, rBgrOrder: 200); var loaded = SkyDescLoader.LoadFromRegion(region); Assert.NotNull(loaded); Assert.Equal(1.0, loaded!.TickSize); Assert.Single(loaded.DayGroups); var grp = loaded.DayGroups[0]; Assert.Single(grp.SkyTimes); var kf = grp.SkyTimes[0].Keyframe; Assert.Equal(120f, kf.FogStart); Assert.Equal(400f, kf.FogEnd); Assert.Equal(FogMode.Linear, kf.FogMode); } [Fact] public void LoadFromRegion_CapturesSkyObjectPesId() { var region = MakeRegion(dirBright: 1.0f, rBgrOrder: 255); var dg = region.SkyInfo!.DayGroups[0]; dg.SkyObjects.Add(new SkyObject { BeginTime = 0f, EndTime = 1f, DefaultGfxObjectId = 0x01004C44u, DefaultPesObjectId = 0x3300042Cu, Properties = 0x05, }); var loaded = SkyDescLoader.LoadFromRegion(region); Assert.NotNull(loaded); var obj = Assert.Single(loaded!.DayGroups[0].SkyObjects); Assert.Equal(0x01004C44u, obj.GfxObjId); Assert.Equal(0x3300042Cu, obj.PesObjectId); Assert.True(obj.IsPostScene); } [Fact] public void LoadFromRegion_SunColor_UsesRetailSunVectorMagnitude() { // The loader stores DirColor and DirBright RAW. The SunColor property // composes them via |sunVec| per retail's UpdateLightsInternal at // 0x59b57c (decomp 424118) — the diffuse magnitude is sqrt(x²+y²+z²) // where the sun vector is built from heading/pitch/brightness with // Y unscaled by brightness (decomp 261352). // // For this region: H=180°, P=70°, B=1.5 // sunVec = (sin(180)*1.5*cos(70), cos(70), 1.5*sin(70)) // = (0, 0.342, 1.410) // |sunVec| = sqrt(0 + 0.117 + 1.988) = 1.4509 // DirColor.X = 200/255 = 0.7843 // SunColor.X = 0.7843 × 1.4509 = 1.138 var region = MakeRegion(dirBright: 1.5f, rBgrOrder: 200); var loaded = SkyDescLoader.LoadFromRegion(region); Assert.NotNull(loaded); var kf = loaded!.DayGroups[0].SkyTimes[0].Keyframe; Assert.InRange(kf.SunColor.X, 1.13f, 1.15f); } [Fact] public void LoadFromRegion_NoSkyInfo_ReturnsNull() { var region = new Region { PartsMask = 0 }; Assert.Null(SkyDescLoader.LoadFromRegion(region)); } [Fact] public void BuildDefaultProvider_FromDatKeyframes_SupportsInterpolation() { var region = MakeRegion(dirBright: 1.0f, rBgrOrder: 255); var loaded = SkyDescLoader.LoadFromRegion(region)!; var provider = loaded.BuildDefaultProvider(); // Exactly one keyframe: interpolation at any t returns it. var s = provider.Interpolate(0.1f); Assert.InRange(s.SunColor.X, 0.99f, 1.01f); } [Fact] public void SkyObjectData_IsVisible_HandlesWrap() { var obj = new SkyObjectData { BeginTime = 0.9f, // wraps across midnight EndTime = 0.1f, }; Assert.True(obj.IsVisible(0.95f)); // near end of day Assert.True(obj.IsVisible(0.05f)); // just after midnight Assert.False(obj.IsVisible(0.5f)); // mid-day (not visible) } [Fact] public void SkyObjectData_CurrentAngle_LerpsAcrossWindow() { var obj = new SkyObjectData { BeginTime = 0.25f, EndTime = 0.75f, BeginAngle = 0f, EndAngle = 180f, }; // Middle of the window → 90°. Assert.Equal(90f, obj.CurrentAngle(0.5f), precision: 2); // At begin → begin angle. Assert.Equal(0f, obj.CurrentAngle(0.25f), precision: 2); } }