refactor(render): one group-creation seam, required foliage key field, first-frame wind snap (Campaign VM VM6 review 3)
Narrow re-review of a82959f1: APPROVE, with follow-ups. All items landed.
N2 (structural): (a) extracted the ONE shared InstanceGroup-from-key
construction seam, WbDrawDispatcher.CreateGroupFromKey(key, registration,
frame) — before this there were two near-identical `new InstanceGroup
{ ... }` initializers (GetOrCreateInstanceGroup and GetOrCreatePackedGroup)
that had already drifted once (the round-2 F1 bug). Both routes call it now;
CreateGroupFromKey's own `new()` is the only production InstanceGroup
construction site repo-wide, same precedent as AppendPackedInstance. (b)
GroupKey.FoliageFlags lost its `= 0u` default and moved before CullMode in
the declaration (CullMode keeps its default, C# requires optional params to
trail required ones), so a `new GroupKey(...)` that omits it is a compile
error. Fixed every real construction site the reorder/requirement touched:
the 2 production sites, ToKey (a reconstruction from InstanceGroup the
review didn't count but the reorder broke), and 5 test sites (one more than
the review's "4" — InstanceGroupClearTests had a second, implicit
target-typed `MakeKey` factory the original count missed). Verified by a
full solution build.
N1: added CreateGroupFromKey_CopiesFoliageFlagsFromTheKey
(InstanceGroupClearTests) — a key carrying FoliageFlags 0x2 in, the created
group's FoliageFlags 0x2 out. That test plus N2b's required field are what
actually guard the round-2 F1 blocker; reworded PackedDispatcherOracleTests'
existing test comment to say what IT proves (the classification-to-
BuildIndirectArrays-to-BatchData.flags path), not that it guards the
classifier.
N3: corrected the plan's round-2 paragraph — folding FoliageFlags into the
G2/G3 digest is correct and symmetric, but CompareClassifiedOutput only
runs from RenderScenePViewFrameProductController.BuildAndCompare, which has
no production caller anywhere in src/AcDream.App/, and both of
RenderScenePViewFrameProductTests's own callers construct the controller
without the optional dispatcher argument — so the fold catches nothing
until that oracle is wired to an actual caller.
N4: the plan's F6 note now names both classification caches — the classic
route's EntityClassificationCache.EntityCacheEntry (self-heals per entity
on its own next eviction) and the packed route's
PackedProjectionClassificationEntry/PackedClassifiedBatch.Key
(PackedProjectionClassificationCache.BeginFrame clears its entire cache in
one shot on a RenderSceneGeneration change) — and notes neither mechanism
is keyed to a pack switch specifically.
N5: deleted the now-unused single-generic ComputeEntityHasCutoutSubset<T>
overload; its 4 test call sites now use the two-generic, zero-alloc
overload with an unused int context and a static (_, value) => value
lambda, so there is exactly one ComputeEntityHasCutoutSubset to keep
correct.
A6 (reviewer-filed): ResolveFoliageWind's _windMean/_windGust started at 0
and always eased toward the weather target by clock delta, with no
distinction for a graph's first-ever advance. A pinned clock
(ACDREAM_SKY_PHASE_SECONDS, the offline pixel gate's determinism pin) never
advances between calls, so the wind reached only whatever fraction the
first (1-second-clamped) step produced and sat there forever; live, the
first 10 s after a graph is constructed (pack selection / login) spun up
from dead calm even though the weather already IS what it is. Fixed at the
root: the first advance (_windFrameSerial == -1, the constructor sentinel)
now snaps _windMean/_windGust straight to the target; every later advance
eases over WeatherSystem.TransitionSeconds exactly as before. Added a
SetWindClockSecondsOverrideForTesting seam (_windClockSecondsOverride is no
longer readonly) so a hermetic test can advance the pinned clock by an
exact amount between two resolves without a real-time Thread.Sleep; two new
tests prove the first-advance snap is exact and a second advance still
eases at the normal rate. The three existing indoor/wind-disabled/amplitude
gate tests pass unchanged.
Verify: Release build 0 warnings/0 errors. App hermetic-lane filter
6,046/0 failed. Core.Tests 4,695/0 failed. Full hermetic-filtered solution:
15,274/0 failed across 15 projects.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
a82959f1b7
commit
fccba8390d
12 changed files with 347 additions and 141 deletions
|
|
@ -184,7 +184,7 @@ internal sealed class AtmosphericPostProcessGraph :
|
|||
// frame (see D2/D4 — the shadow must move with the leaf).
|
||||
private readonly FoliageWindSettings _foliageWind;
|
||||
private readonly IReadOnlySet<uint> _foliageWindExclusions;
|
||||
private readonly float? _windClockSecondsOverride;
|
||||
private float? _windClockSecondsOverride;
|
||||
private readonly System.Diagnostics.Stopwatch _windClock =
|
||||
System.Diagnostics.Stopwatch.StartNew();
|
||||
private long _windFrameSerial = -1;
|
||||
|
|
@ -545,6 +545,26 @@ internal sealed class AtmosphericPostProcessGraph :
|
|||
/// cell transition reads exactly zero on the very next frame, and
|
||||
/// resuming outdoors/enabled picks the smoothed state back up without a
|
||||
/// spin-up glitch.</para>
|
||||
///
|
||||
/// <para><b>The first advance.</b> Review fix round 3 (A6): the very
|
||||
/// first advance for a graph instance (<see cref="_windFrameSerial"/>
|
||||
/// still its <c>-1</c> constructor sentinel) SNAPS <see cref="_windMean"/>/
|
||||
/// <see cref="_windGust"/> straight to that frame's target instead of
|
||||
/// easing toward it — there is no previous state to ease FROM. Without
|
||||
/// this, two things went wrong: (1) a pinned clock
|
||||
/// (<c>ACDREAM_SKY_PHASE_SECONDS</c>, the determinism pin the offline
|
||||
/// pixel gate uses) never advances between frames, so
|
||||
/// <c>deltaSeconds</c> is 0 on every call AFTER the first — the wind
|
||||
/// would ease only once, by whatever the first call's clamped
|
||||
/// <c>deltaSeconds</c> happened to be (up to the 1 s clamp, ~10% of the
|
||||
/// way at the 10 s transition rate), then sit there forever, so every
|
||||
/// offline capture under-represented the motion; (2) live, the first
|
||||
/// 10 s after this graph is constructed (pack selection / login) spun
|
||||
/// up from dead calm even though the weather already IS what it is —
|
||||
/// there was no "previous frame" to have been easing from. Every
|
||||
/// advance AFTER the first still eases over
|
||||
/// <see cref="AcDream.Core.World.WeatherSystem.TransitionSeconds"/>
|
||||
/// exactly as before; only the very first one is exact.</para>
|
||||
/// </summary>
|
||||
private (Vector4 ClockWind, Vector4 WindAmplitude) ResolveFoliageWind(
|
||||
long frameSerial,
|
||||
|
|
@ -555,26 +575,44 @@ internal sealed class AtmosphericPostProcessGraph :
|
|||
?? (float)_windClock.Elapsed.TotalSeconds;
|
||||
if (_windFrameSerial != frameSerial)
|
||||
{
|
||||
float deltaSeconds = Math.Clamp(
|
||||
clockSeconds - _windLastAdvanceClockSeconds,
|
||||
0f,
|
||||
1f);
|
||||
(float targetMean, float targetGust) = RenderPackAtmospherePolicyEvaluation
|
||||
.FoliageWind(
|
||||
Descriptor.AtmospherePolicy?.FoliageWindByWeather,
|
||||
weather);
|
||||
targetMean *= _foliageWind.Strength;
|
||||
targetGust *= _foliageWind.Strength;
|
||||
_windMean = RenderPackAtmospherePolicyEvaluation.EaseTowardTarget(
|
||||
_windMean,
|
||||
targetMean,
|
||||
deltaSeconds,
|
||||
AcDream.Core.World.WeatherSystem.TransitionSeconds);
|
||||
_windGust = RenderPackAtmospherePolicyEvaluation.EaseTowardTarget(
|
||||
_windGust,
|
||||
targetGust,
|
||||
deltaSeconds,
|
||||
AcDream.Core.World.WeatherSystem.TransitionSeconds);
|
||||
|
||||
// Review fix round 3 (A6): _windFrameSerial's constructor
|
||||
// sentinel (-1) means this is the FIRST advance this graph
|
||||
// instance has ever done — there is no previous _windMean/
|
||||
// _windGust state to ease FROM, so snap straight to the target
|
||||
// instead of computing a deltaSeconds-based ease step. See this
|
||||
// method's "The first advance" doc paragraph for the two
|
||||
// concrete symptoms this fixes (a pinned clock's permanent
|
||||
// near-zero wind, and live's dead-calm spin-up on construction).
|
||||
if (_windFrameSerial == -1)
|
||||
{
|
||||
_windMean = targetMean;
|
||||
_windGust = targetGust;
|
||||
}
|
||||
else
|
||||
{
|
||||
float deltaSeconds = Math.Clamp(
|
||||
clockSeconds - _windLastAdvanceClockSeconds,
|
||||
0f,
|
||||
1f);
|
||||
_windMean = RenderPackAtmospherePolicyEvaluation.EaseTowardTarget(
|
||||
_windMean,
|
||||
targetMean,
|
||||
deltaSeconds,
|
||||
AcDream.Core.World.WeatherSystem.TransitionSeconds);
|
||||
_windGust = RenderPackAtmospherePolicyEvaluation.EaseTowardTarget(
|
||||
_windGust,
|
||||
targetGust,
|
||||
deltaSeconds,
|
||||
AcDream.Core.World.WeatherSystem.TransitionSeconds);
|
||||
}
|
||||
|
||||
_windClockSeconds = clockSeconds;
|
||||
_windLastAdvanceClockSeconds = clockSeconds;
|
||||
_windFrameSerial = frameSerial;
|
||||
|
|
@ -595,6 +633,20 @@ internal sealed class AtmosphericPostProcessGraph :
|
|||
return (clockWind, windAmplitude);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign VM VM6 review fix round 3 (A6 test support, N1-adjacent):
|
||||
/// <see cref="_windClockSecondsOverride"/> is normally fixed for a
|
||||
/// graph's whole lifetime (constructor-supplied, from
|
||||
/// <c>ACDREAM_SKY_PHASE_SECONDS</c>) — production never changes it
|
||||
/// mid-session. This lets a hermetic test advance the pinned clock by
|
||||
/// an EXACT, deterministic amount between two resolves (proving
|
||||
/// <see cref="ResolveFoliageWind"/>'s ease-after-the-first-advance
|
||||
/// behavior) without a real-time <c>Thread.Sleep</c> or a flaky
|
||||
/// tolerance window. Test-only: nothing in production calls this.
|
||||
/// </summary>
|
||||
internal void SetWindClockSecondsOverrideForTesting(float seconds) =>
|
||||
_windClockSecondsOverride = seconds;
|
||||
|
||||
public IGpuRenderTarget PrepareWorldTarget(
|
||||
int width,
|
||||
int height,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue