acdream/tests/AcDream.Core.Tests/World/SkyDescLoaderTests.cs

204 lines
6.3 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.

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
{
[Theory]
[InlineData(20, 10, 0, 16)]
[InlineData(20, 10, 83, 5)]
[InlineData(20, 116, 83, 18)]
[InlineData(20, 127, 359, 4)]
public void DayGroupSelector_MatchesNamedRetailGoldenValues(
int count,
int absoluteYear,
int dayOfYear,
int expected)
{
Assert.Equal(
expected,
SkyDayGroupSelector.SelectIndex(
count,
absoluteYear,
daysPerYear: 360,
dayOfYear));
}
[Fact]
public void DayGroupSelector_ForcedIndexIsExplicitInput()
{
Assert.Equal(
7,
SkyDayGroupSelector.SelectIndex(
20,
absoluteYear: 10,
daysPerYear: 360,
dayOfYear: 0,
forcedIndex: 7));
}
/// <summary>
/// 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.
/// </summary>
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) — diffuse = DirColor × |LScape::sunlight|.
// cdb-verified (reference-retail-ambient-values): |LScape::sunlight| ==
// DirBright for every keyframe (world-space spherical vector, magnitude
// DirBright·sqrt(cos²P+sin²P) = DirBright).
//
// For this region: H=180°, P=70°, B=1.5
// sunVec = 1.5 × (cos(70)·sin(180), cos(70)·cos(180), sin(70))
// = (0, -0.513, 1.410)
// |sunVec| = sqrt(0 + 0.263 + 1.988) = 1.500 (= DirBright)
// DirColor.X = 200/255 = 0.7843
// SunColor.X = 0.7843 × 1.500 = 1.1765
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.17f, 1.18f);
}
[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);
}
}