fix(render): foliage wind keys on the DAT-classified WeatherKind, not the day-group index (Campaign VM VM6)
FoliageWindByDayGroup / FoliageWindDayGroupPoint(int ActiveDayGroup, ...) becomes FoliageWindByWeather / FoliageWindWeatherPoint(string WeatherKind, ...) in AtmospherePolicyDeclaration (Plugin.Abstractions is BCL-only, so the key is the exact member name of AcDream.Core.World.WeatherKind rather than the enum itself). The raw activeDayGroup index carries no weather meaning by itself; WeatherState.cs already classifies each day group's authored DAT name into one of five real weather kinds, and that fact was already threaded through AtmosphericFrameInputs.Weather / uAtmosphereWeather.x — this reuses it instead of guessing an index-to-category mapping. Built-in table (BuiltInAtmosphericRenderPack.AtmospherePolicy()): Clear 0.25/0.15, Overcast 0.60/0.35, Rain 0.85/0.60, Snow 0.35/0.20, Storm 1.00/0.75 — all five WeatherKind members declared, the invented "Cloudy" row dropped. RenderPackAtmospherePolicyEvaluation.FoliageWind now takes a WeatherKind and matches by weather.ToString() (ordinal) against each declared point's name; a kind absent from the table falls back to the declared Clear row, then to (0,0) if Clear itself is undeclared. The delta-seconds EMA interpolation (EaseTowardTarget) is unchanged. AtmosphericPostProcessGraph.ResolveFoliageWind and its two callers (RenderPostProcess via inputs.Weather; RenderDirectionalShadows via foundation.Atmosphere.Kind) now pass WeatherKind instead of the day-group int. RenderPackValidation.ValidateAtmosphere (runs for every pack declaring an AtmospherePolicy, not gated to Tier2/shadow packs) now rejects an unknown or non-exact-case weather-kind name and a repeated kind, mirroring the existing ActiveDayGroupMultiplier duplicate-key check. Tests: RenderPackAtmospherePolicyEvaluationTests rewritten for the kind-keyed API (all five kinds resolve to their declared row, an unlisted kind falls back to Clear, ordinal exact-case matching, null-table handling); RenderPackSpirvValidatorTests gains four descriptor-validation cases (unknown name, wrong case, duplicate kind, the five-kind table accepted); AtmosphericPostProcessGraphTests' three foliage-wind cases now select WeatherKind.Storm via `with` instead of an assumed day-group index. Spot-check (per the coordinator's ask, not changed here): yes — ActiveDayGroupMultiplier / EvaluateDayGroupPolicy (pre-existing, Campaign AR/VM3-era — BuiltInAtmosphericRenderPack.AtmospherePolicy()'s three rows `new ActiveDayGroupMultiplier(0, 1.0), (1, 0.35), (2, 0.20)`) key the sun-ray/shadow/volumetric day-group strength multiplier by the same raw activeDayGroup index with an undocumented assumed meaning (0=brightest ... 2=dimmest), the identical class of issue this commit fixes for foliage wind. Left unchanged per instruction; flagging for the coordinator to file. Full solution Debug and Release builds green. App hermetic filter 6024/6026 — the same 2 pre-existing failures as VM6a/VM6b. Both were re-run in isolation per the verification ask: both still fail alone (not a load-flake in this environment) — confirmed via git stash earlier this session that both already fail on the unmodified pre-VM6 baseline, so they are pre-existing and unrelated to this change. Core.Tests hermetic 4697/4697. RenderPackValidator.Tests 30/30. No shader/spv changes in this commit (pure C#/docs fix). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
39e8408c7d
commit
6cc5e183b9
10 changed files with 325 additions and 106 deletions
|
|
@ -1228,11 +1228,12 @@ public sealed class AtmosphericPostProcessGraphTests
|
|||
var device = new RecordingGpuDevice();
|
||||
using var graph = Graph(device, "medium", windClockSecondsOverride: 12f);
|
||||
graph.PrepareWorldTarget(640, 480, 1);
|
||||
// Rainy (index 3 in the built-in table) is the highest declared
|
||||
// mean/gust — the exact target does not matter here, only that the
|
||||
// gate still zeroes the output despite a nonzero smoothed target.
|
||||
AtmosphericFrameInputs indoors = Inputs(640, 480, activeDayGroup: 3) with
|
||||
// Storm is the highest declared mean/gust — the exact target does
|
||||
// not matter here, only that the gate still zeroes the output
|
||||
// despite a nonzero smoothed target.
|
||||
AtmosphericFrameInputs indoors = Inputs(640, 480) with
|
||||
{
|
||||
Weather = WeatherKind.Storm,
|
||||
IsOutdoor = false,
|
||||
};
|
||||
|
||||
|
|
@ -1252,7 +1253,7 @@ public sealed class AtmosphericPostProcessGraphTests
|
|||
windClockSecondsOverride: 12f,
|
||||
userSettingOverrides: new Dictionary<string, string> { ["wind-enabled"] = "false" });
|
||||
graph.PrepareWorldTarget(640, 480, 1);
|
||||
AtmosphericFrameInputs outdoors = Inputs(640, 480, activeDayGroup: 3);
|
||||
AtmosphericFrameInputs outdoors = Inputs(640, 480) with { Weather = WeatherKind.Storm };
|
||||
|
||||
AtmosphericFrameUniforms atmospheric = RenderAndReadFrameBlock(device, graph, outdoors);
|
||||
|
||||
|
|
@ -1290,7 +1291,7 @@ public sealed class AtmosphericPostProcessGraphTests
|
|||
IGpuRenderTarget world = graph.PrepareWorldTarget(640, 480, 1);
|
||||
using IGpuFrame frame = device.BeginFrame();
|
||||
RecordWorldPass(frame, world);
|
||||
AtmosphericFrameInputs inputs = Inputs(640, 480, activeDayGroup: 3);
|
||||
AtmosphericFrameInputs inputs = Inputs(640, 480) with { Weather = WeatherKind.Storm };
|
||||
|
||||
graph.RenderPostProcess(frame, in inputs);
|
||||
AtmosphericFrameUniforms first = ReadLastFrameBlock(device);
|
||||
|
|
|
|||
|
|
@ -1,39 +1,76 @@
|
|||
using AcDream.App.Rendering.Packs;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Plugin.Abstractions.Rendering;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Packs;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign VM VM6: <see cref="RenderPackAtmospherePolicyEvaluation.FoliageWind"/>
|
||||
/// (the exact per-day-group mean/gust lookup) and
|
||||
/// <see cref="RenderPackAtmospherePolicyEvaluation.EaseTowardTarget"/> (the
|
||||
/// smoothing step that keeps a day-group change from snapping).
|
||||
/// (the exact per-weather-kind mean/gust lookup, keyed by the DAT-classified
|
||||
/// <see cref="WeatherKind"/> — corrected in the fix round from the raw
|
||||
/// <c>activeDayGroup</c> index, which carries no weather meaning by itself)
|
||||
/// and <see cref="RenderPackAtmospherePolicyEvaluation.EaseTowardTarget"/>
|
||||
/// (the smoothing step that keeps a weather change from snapping).
|
||||
/// </summary>
|
||||
public sealed class RenderPackAtmospherePolicyEvaluationTests
|
||||
{
|
||||
[Fact]
|
||||
public void FoliageWindLooksUpExactDayGroupMatchOnly()
|
||||
private static readonly FoliageWindWeatherPoint[] FiveKindTable =
|
||||
[
|
||||
new("Clear", 0.25, 0.15),
|
||||
new("Overcast", 0.60, 0.35),
|
||||
new("Rain", 0.85, 0.60),
|
||||
new("Snow", 0.35, 0.20),
|
||||
new("Storm", 1.00, 0.75),
|
||||
];
|
||||
|
||||
[Theory]
|
||||
[InlineData(WeatherKind.Clear, 0.25f, 0.15f)]
|
||||
[InlineData(WeatherKind.Overcast, 0.60f, 0.35f)]
|
||||
[InlineData(WeatherKind.Rain, 0.85f, 0.60f)]
|
||||
[InlineData(WeatherKind.Snow, 0.35f, 0.20f)]
|
||||
[InlineData(WeatherKind.Storm, 1.00f, 0.75f)]
|
||||
public void FoliageWindResolvesEachOfTheFiveDeclaredWeatherKinds(
|
||||
WeatherKind kind,
|
||||
float expectedMean,
|
||||
float expectedGust)
|
||||
{
|
||||
FoliageWindDayGroupPoint[] table =
|
||||
[
|
||||
new(0, 0.25, 0.15),
|
||||
new(1, 0.45, 0.30),
|
||||
new(2, 0.60, 0.35),
|
||||
new(3, 0.85, 0.60),
|
||||
];
|
||||
(float mean, float gust) = RenderPackAtmospherePolicyEvaluation.FoliageWind(
|
||||
FiveKindTable,
|
||||
kind);
|
||||
|
||||
(float mean, float gust) = RenderPackAtmospherePolicyEvaluation.FoliageWind(table, 3);
|
||||
|
||||
Assert.Equal(0.85f, mean);
|
||||
Assert.Equal(0.60f, gust);
|
||||
Assert.Equal(expectedMean, mean);
|
||||
Assert.Equal(expectedGust, gust);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FoliageWindReturnsZeroForADayGroupAbsentFromTheTable()
|
||||
public void FoliageWindFallsBackToTheClearRowForAnUnlistedKind()
|
||||
{
|
||||
FoliageWindDayGroupPoint[] table = [new(0, 0.25, 0.15)];
|
||||
// A table that only declares Clear and Storm — a kind absent from
|
||||
// the table (Overcast here) falls back to the declared Clear row,
|
||||
// not to (0, 0), since an unresolved weather kind is closer to "no
|
||||
// weather data" than to "assume it's calm."
|
||||
FoliageWindWeatherPoint[] table =
|
||||
[
|
||||
new("Clear", 0.25, 0.15),
|
||||
new("Storm", 1.00, 0.75),
|
||||
];
|
||||
|
||||
(float mean, float gust) = RenderPackAtmospherePolicyEvaluation.FoliageWind(table, 99);
|
||||
(float mean, float gust) = RenderPackAtmospherePolicyEvaluation.FoliageWind(
|
||||
table,
|
||||
WeatherKind.Overcast);
|
||||
|
||||
Assert.Equal(0.25f, mean);
|
||||
Assert.Equal(0.15f, gust);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FoliageWindReturnsZeroWhenNeitherTheKindNorClearIsDeclared()
|
||||
{
|
||||
FoliageWindWeatherPoint[] table = [new("Storm", 1.00, 0.75)];
|
||||
|
||||
(float mean, float gust) = RenderPackAtmospherePolicyEvaluation.FoliageWind(
|
||||
table,
|
||||
WeatherKind.Overcast);
|
||||
|
||||
Assert.Equal(0f, mean);
|
||||
Assert.Equal(0f, gust);
|
||||
|
|
@ -42,26 +79,34 @@ public sealed class RenderPackAtmospherePolicyEvaluationTests
|
|||
[Fact]
|
||||
public void FoliageWindReturnsZeroForANullTable()
|
||||
{
|
||||
(float mean, float gust) = RenderPackAtmospherePolicyEvaluation.FoliageWind(null, 0);
|
||||
(float mean, float gust) = RenderPackAtmospherePolicyEvaluation.FoliageWind(
|
||||
null,
|
||||
WeatherKind.Clear);
|
||||
|
||||
Assert.Equal(0f, mean);
|
||||
Assert.Equal(0f, gust);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FoliageWindDoesNotInterpolateBetweenDayGroupIndices()
|
||||
public void FoliageWindMatchesByExactNameNotSubstringOrCase()
|
||||
{
|
||||
// Day-group ids are not ordered by "how windy" — index 1 sitting
|
||||
// between 0 and 2 in the table must not produce a value between
|
||||
// their mean/gust. This asserts the lookup is an exact match, not
|
||||
// continuous interpolation across the index axis.
|
||||
FoliageWindDayGroupPoint[] table = [new(0, 0.0, 0.0), new(5, 1.0, 1.0)];
|
||||
// "Rain" must not match "Rainy" or any case variant — the lookup is
|
||||
// an ordinal exact match against the WeatherKind member name.
|
||||
FoliageWindWeatherPoint[] table =
|
||||
[
|
||||
new("Rainy", 0.99, 0.99),
|
||||
new("rain", 0.99, 0.99),
|
||||
new("Clear", 0.10, 0.05),
|
||||
];
|
||||
|
||||
(float meanAtUnlistedMidpoint, float gustAtUnlistedMidpoint) =
|
||||
RenderPackAtmospherePolicyEvaluation.FoliageWind(table, 2);
|
||||
(float mean, float gust) = RenderPackAtmospherePolicyEvaluation.FoliageWind(
|
||||
table,
|
||||
WeatherKind.Rain);
|
||||
|
||||
Assert.Equal(0f, meanAtUnlistedMidpoint);
|
||||
Assert.Equal(0f, gustAtUnlistedMidpoint);
|
||||
// Neither "Rainy" nor "rain" matches WeatherKind.Rain.ToString()
|
||||
// ("Rain", exact case) — falls back to the declared Clear row.
|
||||
Assert.Equal(0.10f, mean);
|
||||
Assert.Equal(0.05f, gust);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -39,6 +39,104 @@ public sealed class RenderPackSpirvValidatorTests
|
|||
result.Reason);
|
||||
}
|
||||
|
||||
// Campaign VM VM6 fix round: FoliageWindByWeather is keyed by the
|
||||
// DAT-classified AcDream.Core.World.WeatherKind name (exact,
|
||||
// case-sensitive), not the raw activeDayGroup index. An unknown name or
|
||||
// a repeated kind must be rejected outright.
|
||||
|
||||
[Fact]
|
||||
public void FoliageWindByWeatherRejectsAnUnknownWeatherKindName()
|
||||
{
|
||||
RenderPackDescriptor descriptor = BuiltInAtmosphericRenderPack.Descriptor;
|
||||
descriptor = descriptor with
|
||||
{
|
||||
AtmospherePolicy = descriptor.AtmospherePolicy! with
|
||||
{
|
||||
FoliageWindByWeather =
|
||||
[
|
||||
new FoliageWindWeatherPoint("Cloudy", 0.45, 0.30),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
RenderPackValidationResult result = RenderPackValidator.ValidateDescriptor(
|
||||
descriptor,
|
||||
RenderPackHostCapabilities.Conformance);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Contains("unknown foliage-wind weather", result.Reason, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FoliageWindByWeatherRejectsANonExactCaseWeatherKindName()
|
||||
{
|
||||
RenderPackDescriptor descriptor = BuiltInAtmosphericRenderPack.Descriptor;
|
||||
descriptor = descriptor with
|
||||
{
|
||||
AtmospherePolicy = descriptor.AtmospherePolicy! with
|
||||
{
|
||||
FoliageWindByWeather = [new FoliageWindWeatherPoint("clear", 0.25, 0.15)],
|
||||
},
|
||||
};
|
||||
|
||||
RenderPackValidationResult result = RenderPackValidator.ValidateDescriptor(
|
||||
descriptor,
|
||||
RenderPackHostCapabilities.Conformance);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Contains("unknown foliage-wind weather", result.Reason, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FoliageWindByWeatherRejectsADuplicateWeatherKind()
|
||||
{
|
||||
RenderPackDescriptor descriptor = BuiltInAtmosphericRenderPack.Descriptor;
|
||||
descriptor = descriptor with
|
||||
{
|
||||
AtmospherePolicy = descriptor.AtmospherePolicy! with
|
||||
{
|
||||
FoliageWindByWeather =
|
||||
[
|
||||
new FoliageWindWeatherPoint("Clear", 0.25, 0.15),
|
||||
new FoliageWindWeatherPoint("Clear", 0.30, 0.20),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
RenderPackValidationResult result = RenderPackValidator.ValidateDescriptor(
|
||||
descriptor,
|
||||
RenderPackHostCapabilities.Conformance);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Contains("more than once", result.Reason, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FoliageWindByWeatherAcceptsTheFiveDeclaredKindsOnce()
|
||||
{
|
||||
RenderPackDescriptor descriptor = BuiltInAtmosphericRenderPack.Descriptor;
|
||||
descriptor = descriptor with
|
||||
{
|
||||
AtmospherePolicy = descriptor.AtmospherePolicy! with
|
||||
{
|
||||
FoliageWindByWeather =
|
||||
[
|
||||
new FoliageWindWeatherPoint("Clear", 0.25, 0.15),
|
||||
new FoliageWindWeatherPoint("Overcast", 0.60, 0.35),
|
||||
new FoliageWindWeatherPoint("Rain", 0.85, 0.60),
|
||||
new FoliageWindWeatherPoint("Snow", 0.35, 0.20),
|
||||
new FoliageWindWeatherPoint("Storm", 1.00, 0.75),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
RenderPackValidationResult result = RenderPackValidator.ValidateDescriptor(
|
||||
descriptor,
|
||||
RenderPackHostCapabilities.Conformance);
|
||||
|
||||
Assert.True(result.Success, result.Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectionalShadowDepthRejectsSunDirectionAlias()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue