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:
Erik 2026-08-23 01:07:54 +02:00
parent 39e8408c7d
commit 6cc5e183b9
10 changed files with 325 additions and 106 deletions

View file

@ -387,12 +387,25 @@ public sealed record ActiveDayGroupMultiplier(
double Multiplier);
/// <summary>
/// Explicit mapping from an authored AC day group to the foliage-wind mean
/// and gust strength for that group (both in the declared [0,1] range before
/// the pack's global wind-strength setting scales them). Campaign VM VM6.
/// Explicit mapping from a DAT-classified weather kind to the foliage-wind
/// mean and gust strength for that weather (both in the declared [0,1] range
/// before the pack's global wind-strength setting scales them). Campaign VM
/// VM6, corrected in the fix round: the day-group's raw <c>activeDayGroup</c>
/// INDEX carries no weather meaning by itself — <c>WeatherState.cs</c>'s
/// existing name classification is what turns a day group's DAT name into
/// one of the five real weather kinds, and that is what the host already
/// threads through <c>AtmosphericFrameInputs.Weather</c> / <c>uAtmosphereWeather.x</c>.
/// This is the same fact, keyed correctly instead of by index.
/// </summary>
public sealed record FoliageWindDayGroupPoint(
int ActiveDayGroup,
/// <param name="WeatherKind">
/// The exact member name of <c>AcDream.Core.World.WeatherKind</c> — one of
/// <c>"Clear"</c>, <c>"Overcast"</c>, <c>"Rain"</c>, <c>"Snow"</c>,
/// <c>"Storm"</c>. A plain string because this project (Plugin.Abstractions)
/// is BCL-only and cannot reference the Core enum; the host converts with
/// <c>WeatherKind.ToString()</c> (ordinal) when resolving.
/// </param>
public sealed record FoliageWindWeatherPoint(
string WeatherKind,
double Mean,
double Gust);
@ -424,18 +437,18 @@ public sealed record AtmospherePolicyDeclaration(
{ get; init; } = [];
/// <summary>
/// Campaign VM VM6: exact per-day-group foliage-wind mean/gust targets,
/// looked up by the same categorical <c>activeDayGroup</c> id
/// <see cref="ActiveDayGroupMultipliers"/> already keys on (an exact
/// match, not an interpolation across day-group ids — day groups are not
/// ordered by "how windy"). The host smooths the resolved (mean, gust)
/// target toward whatever this table returns over time using the
/// existing weather delta-seconds clock, so a day-group change never
/// snaps. A day group absent from this table gets zero wind. A pack
/// using foliage wind should declare an entry for every day group it
/// cares about distinguishing.
/// Campaign VM VM6: exact per-weather-kind foliage-wind mean/gust
/// targets, looked up by the DAT-classified
/// <c>AcDream.Core.World.WeatherKind</c> (an exact match, not an
/// interpolation — the five kinds are not ordered by "how windy"). The
/// host smooths the resolved (mean, gust) target toward whatever this
/// table returns over time using the existing weather delta-seconds
/// clock, so a weather change never snaps. A kind absent from this table
/// falls back to the declared Clear row, then to (0, 0) if Clear itself
/// is undeclared. Each declared <see cref="FoliageWindWeatherPoint.WeatherKind"/>
/// must be one of the five real kind names and must appear at most once.
/// </summary>
public IReadOnlyList<FoliageWindDayGroupPoint> FoliageWindByDayGroup
public IReadOnlyList<FoliageWindWeatherPoint> FoliageWindByWeather
{ get; init; } = [];
/// <summary>