Add reservoir spatial reuse budget

This commit is contained in:
Meta Vens 2026-05-30 00:35:55 +01:00
commit 2da0ad0cba
18 changed files with 144 additions and 15 deletions

View file

@ -397,6 +397,11 @@ An offline budget-pressure probe now bins that masked reference error by fixed
tiles and ranks tiles by mean error times mask coverage. The probe is allocator
evidence, not a runtime visibility owner: it says where additional reservoir
updates may buy stability under a fixed budget.
The first runtime budget is now explicit: every work-grid pixel writes current
and temporal rows, while only a deterministic budget phase spends the 3x3
spatial neighbor reuse samples. The default therefore combines a half-scale
reservoir grid with half spatial reuse sampling before full-resolution
presentation reconstruction.
\section{Required Evaluation}

Binary file not shown.

View file

@ -678,6 +678,9 @@ native-resolution intermediate reservoir.
Those rows feed an offline budget-pressure probe that ranks fixed tiles by
masked reference error, giving the future scheduler a concrete pressure signal
without letting a debug script own runtime visibility.
The renderer now has a fixed spatial-reuse sampling budget in the history update
owner: current and temporal rows remain per-work-grid-pixel, while 3x3 spatial
neighbor reuse runs only on the active budget phase for the frame.
The system has cut the false ``nearest-travel-minus-coverage'' priority owner
and the side-cache temptation.

View file

@ -567,6 +567,18 @@ Work packet:
1306 of 42864 pixels and found the top 5 of 50 tiles carried 99.3305% of
measured reference-error delta. This is allocator evidence only; it does
not own runtime visibility or scheduling.
14. Partial: add a runtime spatial-reuse sampling budget. The renderer now
carries `GraphicsSettings.FieldReservoirSpatialReuseBudget` through UI,
runtime options, CLI/env overrides, headless capture scripts, frame
constants, and `D3D12ReservoirHistoryUpdateCS`. Every reservoir work-grid
pixel still writes current and temporal rows, but only the budgeted
deterministic phase spends the 3x3 neighbor reuse samples each frame. The
default is 0.5, so the reservoir runs at half presentation grid scale and
half spatial reuse by default. Smoke `reservoir-sequence-20260530-003006-*`
compared native scale 0.5 / spatial budget 0.5 against reference scale
0.75 / spatial budget 1.0; masked reference-error-native covered 3.8261%
of pixels, and budget pressure found the top 5 of 50 tiles carried
90.8321% of measured reference-error delta.
Required verification:

View file

@ -7,6 +7,7 @@ param(
[ValidateSet("native", "baseline")]
[string]$FieldReservoirMode = "native",
[double]$FieldReservoirScale = 0.5,
[double]$FieldReservoirSpatialReuseBudget = 0.5,
[int]$RetainSlots = 4,
[int]$TimeoutSeconds = 120
)
@ -62,6 +63,7 @@ if ($FieldReservoirMode -eq "baseline") {
$arguments += @("--field-reservoir-mode", "baseline")
}
$arguments += @("--field-reservoir-scale", $FieldReservoirScale)
$arguments += @("--field-reservoir-spatial-reuse-budget", $FieldReservoirSpatialReuseBudget)
$env:AQUARIUM_HEADLESS_READY_FRAMES = [string][Math]::Max(1, $ReadyFrames)
$process = Start-Process -FilePath $exePath -ArgumentList $arguments -NoNewWindow -PassThru -RedirectStandardOutput $stdoutLog -RedirectStandardError $stderrLog

View file

@ -7,7 +7,9 @@ param(
[ValidateSet("native", "baseline", "both")]
[string]$FieldReservoirMode = "both",
[double]$FieldReservoirScale = 0.5,
[double]$FieldReservoirSpatialReuseBudget = 0.5,
[double]$ReferenceFieldReservoirScale = 0.0,
[double]$ReferenceFieldReservoirSpatialReuseBudget = 1.0,
[int[]]$RenderDebugModes = @(0, 13),
[int]$RetainSlots = 4,
[int]$TimeoutSeconds = 120
@ -86,6 +88,7 @@ foreach ($mode in $modes) {
foreach ($readyFrame in $ReadyFrames) {
foreach ($debugMode in $RenderDebugModes) {
$captureScale = if ($mode -eq "reference") { $ReferenceFieldReservoirScale } else { $FieldReservoirScale }
$captureSpatialReuseBudget = if ($mode -eq "reference") { $ReferenceFieldReservoirSpatialReuseBudget } else { $FieldReservoirSpatialReuseBudget }
$suffix = DebugSuffix -Mode $debugMode
$outputPath = Join-Path $OutputDirectory ("reservoir-sequence-$Stamp-$mode-f{0:D4}-$suffix.png" -f $readyFrame)
$arguments = @(
@ -96,7 +99,8 @@ foreach ($mode in $modes) {
"--cache", $cachePath,
"--shader-source", (Join-Path $slotPath "Render\Shaders\D3D12HeightField.hlsl"),
"--capture-frame", $outputPath,
"--field-reservoir-scale", $captureScale
"--field-reservoir-scale", $captureScale,
"--field-reservoir-spatial-reuse-budget", $captureSpatialReuseBudget
)
if ($mode -eq "baseline") {
$arguments += @("--field-reservoir-mode", "baseline")
@ -112,6 +116,7 @@ foreach ($mode in $modes) {
ReadyFrames = $readyFrame
RenderDebugMode = $debugMode
ReservoirScale = $captureScale
SpatialReuseBudget = $captureSpatialReuseBudget
Suffix = $suffix
Path = $outputPath
})
@ -146,6 +151,7 @@ foreach ($mode in $modes) {
ReadyFrames = $readyFrame
RenderDebugMode = $debugMode
ReservoirScale = $captureScale
SpatialReuseBudget = $captureSpatialReuseBudget
Suffix = $suffix
Path = $outputPath
})

View file

@ -44,5 +44,6 @@ public readonly record struct AquariumRuntimeOptions(
string? CultCachePath,
int? RenderDebugModeOverride = null,
int? FieldReservoirModeOverride = null,
float? FieldReservoirScaleOverride = null);
float? FieldReservoirScaleOverride = null,
float? FieldReservoirSpatialReuseBudgetOverride = null);

View file

@ -6,7 +6,8 @@ public readonly record struct GraphicsSettings(
float BloomIntensity,
float BloomVeilIntensity,
int FieldReservoirMode,
float FieldReservoirScale)
float FieldReservoirScale,
float FieldReservoirSpatialReuseBudget)
{
public const int MinRenderDebugMode = 0;
public const int MaxRenderDebugMode = 19;
@ -14,6 +15,8 @@ public readonly record struct GraphicsSettings(
public const int FieldReservoirModeTexelBaseline = 1;
public const float MinFieldReservoirScale = 0.25f;
public const float MaxFieldReservoirScale = 0.75f;
public const float MinFieldReservoirSpatialReuseBudget = 0.25f;
public const float MaxFieldReservoirSpatialReuseBudget = 1.0f;
public const float MinSceneExposure = 0.02f;
public const float MaxSceneExposure = 1.2f;
public const float MinBloomIntensity = 0.0f;
@ -27,7 +30,8 @@ public readonly record struct GraphicsSettings(
BloomIntensity: 0.072f,
BloomVeilIntensity: 0.014f,
FieldReservoirMode: FieldReservoirModeNativeDomain,
FieldReservoirScale: 0.5f);
FieldReservoirScale: 0.5f,
FieldReservoirSpatialReuseBudget: 0.5f);
public GraphicsSettings Normalized()
{
@ -37,6 +41,7 @@ public readonly record struct GraphicsSettings(
Math.Clamp(BloomIntensity, MinBloomIntensity, MaxBloomIntensity),
Math.Clamp(BloomVeilIntensity, MinBloomVeilIntensity, MaxBloomVeilIntensity),
Math.Clamp(FieldReservoirMode, FieldReservoirModeNativeDomain, FieldReservoirModeTexelBaseline),
Math.Clamp(FieldReservoirScale, MinFieldReservoirScale, MaxFieldReservoirScale));
Math.Clamp(FieldReservoirScale, MinFieldReservoirScale, MaxFieldReservoirScale),
Math.Clamp(FieldReservoirSpatialReuseBudget, MinFieldReservoirSpatialReuseBudget, MaxFieldReservoirSpatialReuseBudget));
}
}

View file

@ -16,7 +16,8 @@ public static class AquariumHost
ParseCachePath(args),
ParseRenderDebugMode(args),
ParseFieldReservoirMode(args),
ParseFieldReservoirScale(args));
ParseFieldReservoirScale(args),
ParseFieldReservoirSpatialReuseBudget(args));
using var runtimeLoader = new ClientRuntimeLoader(runtimeOptions, ParseClientAssemblyPath(args), ParseClientReloadPointerPath(args));
var runtime = runtimeLoader.Load();
if (runtimeOptions.RenderDebugModeOverride is { } renderDebugModeOverride)
@ -40,6 +41,13 @@ public static class AquariumHost
FieldReservoirScale = fieldReservoirScaleOverride,
}).Normalized();
}
if (runtimeOptions.FieldReservoirSpatialReuseBudgetOverride is { } fieldReservoirSpatialReuseBudgetOverride)
{
runtime.GraphicsSettings = (runtime.GraphicsSettings with
{
FieldReservoirSpatialReuseBudget = fieldReservoirSpatialReuseBudgetOverride,
}).Normalized();
}
var input = new InputState();
var headlessSize = ParseHeadlessSize(args);
var width = runtime.Options.Headless ? headlessSize.Width : 1280;
@ -314,6 +322,23 @@ public static class AquariumHost
: null;
}
private static float? ParseFieldReservoirSpatialReuseBudget(IReadOnlyCollection<string> args)
{
var values = args.ToArray();
for (var index = 0; index < values.Length - 1; index++)
{
if (string.Equals(values[index], "--field-reservoir-spatial-reuse-budget", StringComparison.OrdinalIgnoreCase)
&& float.TryParse(values[index + 1], out var budget))
{
return budget;
}
}
return float.TryParse(Environment.GetEnvironmentVariable("AQUARIUM_FIELD_RESERVOIR_SPATIAL_REUSE_BUDGET"), out var environmentBudget)
? environmentBudget
: null;
}
private static int? ParseFieldReservoirModeValue(string? value)
{
return value?.Trim().ToLowerInvariant() switch

View file

@ -586,7 +586,9 @@ public sealed class D3D12Renderer : IAquariumRenderer
.Button("Reset View", () => RenderDebugMode = 0, "Returns to the final presented frame.", () => activeDebugTab == 0)
.Options("Reservoir Mode", () => settings.FieldReservoirMode, value => settings = (settings with { FieldReservoirMode = Math.Clamp(value, GraphicsSettings.FieldReservoirModeNativeDomain, GraphicsSettings.FieldReservoirModeTexelBaseline) }).Normalized(), FieldReservoirModeOptions, "Selects native-domain validation or texel-owned baseline reuse.", () => activeDebugTab == 0)
.Slider("Reservoir Scale", () => settings.FieldReservoirScale, value => settings = (settings with { FieldReservoirScale = Math.Clamp(value, GraphicsSettings.MinFieldReservoirScale, GraphicsSettings.MaxFieldReservoirScale) }).Normalized(), GraphicsSettings.MinFieldReservoirScale, GraphicsSettings.MaxFieldReservoirScale, "0.##", "Controls the internal reservoir work grid; presentation remains full resolution.", () => activeDebugTab == 0)
.Slider("Spatial Reuse Budget", () => settings.FieldReservoirSpatialReuseBudget, value => settings = (settings with { FieldReservoirSpatialReuseBudget = Math.Clamp(value, GraphicsSettings.MinFieldReservoirSpatialReuseBudget, GraphicsSettings.MaxFieldReservoirSpatialReuseBudget) }).Normalized(), GraphicsSettings.MinFieldReservoirSpatialReuseBudget, GraphicsSettings.MaxFieldReservoirSpatialReuseBudget, "0.##", "Controls what share of reservoir work-grid pixels spend neighbor sampling each frame.", () => activeDebugTab == 0)
.Readout("Reservoir Grid", () => $"{reservoirWidth}x{reservoirHeight} / present {width}x{height}", "Internal reservoir work grid and final present size.", () => activeDebugTab == 0)
.Readout("Reservoir Budget", () => $"{settings.FieldReservoirSpatialReuseBudget:P0} spatial reuse / {settings.FieldReservoirScale:P0} work grid", "Current reservoir sampling budget and work-grid scale.", () => activeDebugTab == 0)
.Section("HDR", () => activeDebugTab == 0)
.Slider("Exposure", () => settings.SceneExposure, value => settings = (settings with { SceneExposure = Math.Clamp(value, GraphicsSettings.MinSceneExposure, GraphicsSettings.MaxSceneExposure) }).Normalized(), GraphicsSettings.MinSceneExposure, GraphicsSettings.MaxSceneExposure, "0.###", "Manual scene exposure before display transform.", () => activeDebugTab == 0)
.Slider("Bloom Intensity", () => settings.BloomIntensity, value => settings = (settings with { BloomIntensity = Math.Clamp(value, GraphicsSettings.MinBloomIntensity, GraphicsSettings.MaxBloomIntensity) }).Normalized(), GraphicsSettings.MinBloomIntensity, GraphicsSettings.MaxBloomIntensity, "0.###", "Strength of pre-tonemap bloom energy.", () => activeDebugTab == 0)
@ -934,7 +936,8 @@ public sealed class D3D12Renderer : IAquariumRenderer
visibleFractalSplatCount,
activeFractalReservoirField.HasInput ? activeFractalReservoirField.ReservoirUpdatesPerPass : 0,
activeFractalReservoirField.HasInput ? activeFractalReservoirField.CandidatesPerReservoirUpdate : 0),
activeFractalReservoirField.HasInput ? activeFractalReservoirField.WorldCenterRadius : Vector4.Zero));
activeFractalReservoirField.HasInput ? activeFractalReservoirField.WorldCenterRadius : Vector4.Zero,
new Vector4(settings.FieldReservoirSpatialReuseBudget, 0.0f, 0.0f, 0.0f)));
frameResources.FrameConstantsDescriptor = frameResources.TransientShaderDescriptors.Allocate();
device.CreateConstantBufferView(
new ConstantBufferViewDescription(frameConstants.GpuVirtualAddress, frameConstants.SizeInBytes),
@ -5191,7 +5194,8 @@ public sealed class D3D12Renderer : IAquariumRenderer
Vector4 CameraFrustumZ,
Vector4 GpuFusionInfo,
Vector4 FractalReservoirInfo,
Vector4 FractalReservoirFrame);
Vector4 FractalReservoirFrame,
Vector4 ReservoirBudgetInfo);
[StructLayout(LayoutKind.Sequential)]
private readonly record struct D3D12SplineVertex(

View file

@ -25,6 +25,10 @@ cbuffer AquariumFrame : register(b0)
float4 temporalGaussianInfo;
float4 cameraFrustumXy;
float4 cameraFrustumZ;
float4 gpuFusionInfo;
float4 fractalReservoirInfo;
float4 fractalReservoirFrame;
float4 reservoirBudgetInfo;
};
Texture2D<float4> sourceTexture : register(t0);
@ -122,6 +126,26 @@ bool nativeDomainReservoirEnabled()
return cameraFrustumZ.z < 0.5;
}
float fieldReservoirSpatialReuseBudget()
{
return clamp(reservoirBudgetInfo.x, 0.25, 1.0);
}
bool fieldReservoirShouldSpendSpatialReuse(uint2 pixel)
{
float budget = fieldReservoirSpatialReuseBudget();
uint activePhases = clamp((uint)floor(budget * 4.0 + 0.5), 1u, 4u);
if (activePhases >= 4u)
{
return true;
}
uint tilePhase = (pixel.x & 1u) | ((pixel.y & 1u) << 1);
uint phaseStart = (uint)frameIndex & 3u;
uint phaseDistance = (tilePhase + 4u - phaseStart) & 3u;
return phaseDistance < activePhases;
}
VertexOut FullscreenTriangleVS(uint vertexId : SV_VertexID)
{
float2 uv = float2((vertexId << 1) & 2, vertexId & 2);
@ -1111,7 +1135,11 @@ void D3D12ReservoirHistoryUpdateCS(uint3 dispatchThreadId : SV_DispatchThreadID)
uint baseIndex = pixelIndex * FieldReservoirSlotsPerPixel;
FieldReservoirSample current = currentFrameReservoirSample(currentPixel, uv);
FieldReservoirSample temporal = temporallyReuseReservoirSample(current, currentPixel, uv);
FieldReservoirSample spatial = spatiallyReuseReservoirSample(temporal, currentPixel, uv, dimensions);
FieldReservoirSample spatial = temporal;
if (fieldReservoirShouldSpendSpatialReuse(currentPixel))
{
spatial = spatiallyReuseReservoirSample(temporal, currentPixel, uv, dimensions);
}
FieldReservoirSample finalSample = spatial;
finalSample.guide.w = spatial.guide.w;

View file

@ -24,6 +24,7 @@ public sealed class FensalirRuntime : IAquariumRuntime
0.52f,
0.08f,
GraphicsSettings.FieldReservoirModeNativeDomain,
0.5f,
0.5f);
public AquariumRenderPlan RenderPlan { get; } = FensalirRenderPlan.Create();

View file

@ -43,6 +43,7 @@ public sealed class ZyphosRuntime : IAquariumRuntime
0.32f,
0.05f,
GraphicsSettings.FieldReservoirModeNativeDomain,
0.5f,
0.5f);
public AquariumRenderPlan RenderPlan { get; } = ZyphosRenderPlan.Create();

View file

@ -303,4 +303,5 @@
{"timestamp":"2026-05-30T01:05:00+01:00","topic":"reservoir-disocclusion-mask","lesson":"Temporal disocclusion is now exposed by the reservoir owner instead of inferred from broad rejection color. Debug mode 19 maps temporal rejection codes for previous UV loss, previous-closer history, and support loss into a focused disocclusion mask, and the sequence measurer prefers disocclusion masks over rejection masks when both exist. The native-only 320x180 smoke set reservoir-sequence-20260530-000151-* produced a 3.1098% frame mask and a masked temporal-native delta row, proving the mask path is live. The remaining proof gap is a high-budget reference or repeated-sample reference mode so masked deltas can become actual error scores.","files":["src/Aquarium.Engine.Contracts/GraphicsSettings.cs","src/Aquarium.Engine/Render/D3D12Renderer.cs","src/Aquarium.Engine/Render/Shaders/D3D12Post.hlsl","scripts/capture-reservoir-mode-sequence.ps1","scripts/measure-reservoir-sequence.ps1","tests/Aquarium.Engine.Tests/FieldReservoirAbiTests.cs","docs/perfect-fensalir-machine-roadmap.md","state/map.yaml","state/scratch.md"],"source":"artifacts/fensalir-captures/reservoir-sequence-20260530-000151-*"}
{"timestamp":"2026-05-30T01:25:00+01:00","topic":"reservoir-higher-work-grid-reference","lesson":"The sequence harness can now score candidates against a same-time higher-work-grid native-domain reference without violating the no-native-reservoir-buffer rule. capture-reservoir-mode-sequence.ps1 accepts -ReferenceFieldReservoirScale, captures reference rows in native-domain mode at that larger scale, and labels them reference in the manifest. measure-reservoir-sequence.ps1 emits reference-error rows and masked reference-error rows using disocclusion/rejection masks when present. The 320x180 smoke set reservoir-sequence-20260530-001046-* compared native scale 0.5 against reference scale 0.75 at the same ready-frame count; masked reference-error-native covered 3.0468% of frame pixels and changed 42.0368%. The next architectural step is adaptive update/sample budget policy driven by these reference-error pressure signals.","files":["scripts/capture-reservoir-mode-sequence.ps1","scripts/measure-reservoir-sequence.ps1","docs/perfect-fensalir-machine-roadmap.md","state/map.yaml","state/scratch.md"],"source":"artifacts/fensalir-captures/reservoir-sequence-20260530-001046-*"}
{"timestamp":"2026-05-30T01:40:00+01:00","topic":"reservoir-budget-pressure-probe","lesson":"The reservoir quality harness now converts same-time higher-work-grid reference error into per-tile budget pressure without making the probe a runtime owner. measure-reservoir-budget-pressure.ps1 reads a sequence manifest, compares a candidate final capture against the native-domain reference final capture at the same ready frame, applies disocclusion/rejection masks when present, and ranks fixed tiles by mean reference error times mask coverage. On reservoir-budget-pressure-20260530-001046.* the probe measured 1306/42864 pixels and found the top 5/50 tiles carried 99.3305% of measured reference-error delta, giving a concrete target for adaptive update policy while preserving the no-native-resolution-reservoir invariant.","files":["scripts/measure-reservoir-budget-pressure.ps1","docs/perfect-fensalir-machine-roadmap.md","state/map.yaml","state/scratch.md"],"source":"artifacts/fensalir-captures/reservoir-budget-pressure-20260530-001046.*"}
{"timestamp":"2026-05-30T01:58:00+01:00","topic":"reservoir-spatial-reuse-budget","lesson":"The renderer now has a first runtime sampling budget inside the reservoir history update owner. GraphicsSettings.FieldReservoirSpatialReuseBudget flows through UI, runtime options, CLI/env overrides, capture scripts, frame constants, and D3D12ReservoirHistoryUpdateCS. Current and temporal rows are still written for every reservoir work-grid pixel; only the 3x3 spatial neighbor reuse pass is spent on the deterministic budget phase. The default is 0.5, so the machine combines half presentation grid reservoir surfaces with half spatial reuse sampling by default. Smoke reservoir-sequence-20260530-003006-* compared native scale 0.5/spatial budget 0.5 against reference scale 0.75/spatial budget 1.0; masked reference-error-native covered 3.8261% of frame pixels, and reservoir-budget-pressure-20260530-003006.* found the top 5/50 tiles carried 90.8321% of measured reference-error delta.","files":["src/Aquarium.Engine.Contracts/GraphicsSettings.cs","src/Aquarium.Engine/AquariumHost.cs","src/Aquarium.Engine/Render/D3D12Renderer.cs","src/Aquarium.Engine/Render/Shaders/D3D12Post.hlsl","scripts/capture-fensalir-frame.ps1","scripts/capture-reservoir-mode-sequence.ps1","tests/Aquarium.Engine.Tests/FieldReservoirAbiTests.cs","docs/perfect-fensalir-machine-roadmap.md","state/map.yaml","state/scratch.md"],"source":"artifacts/fensalir-captures/reservoir-sequence-20260530-003006-*"}
{"timestamp":"2026-05-30T00:29:26+01:00","topic":"surfacepage-uav-depth-socket","lesson":"SurfacePage resources can now be GPU-resident unordered-access outputs as well as shader-readable pages. D3D12FieldTexture2D tracks UAV capability, creates UAV descriptors, and D3D12FieldResourceRegistry resolves UnorderedAccess SurfacePages while reusing empty pages across version changes when shape/format/access are unchanged. This gives Mimir stereo-depth/height outputs a real compute-writable resource socket without private output textures or CPU readback.","files":["src/Aquarium.Engine/Render/D3D12FieldTexture2D.cs","src/Aquarium.Engine/Render/D3D12FieldResourceRegistry.cs","tests/Aquarium.Engine.Tests/D3D12SharedTextureInteropTests.cs","docs/perfect-fensalir-machine-roadmap.md","state/map.yaml"],"source":"dotnet test tests/Aquarium.Engine.Tests/Aquarium.Engine.Tests.csproj --filter RegistryResolvesUnorderedAccessSurfacePageForComputeDepthOutput"}

View file

@ -164,9 +164,14 @@ next_actions:
proves reference-error rows from scale 0.5 against scale 0.75. The offline
budget-pressure probe `reservoir-budget-pressure-20260530-001046.*` ranks
masked reference error by tile; its first smoke found the top 5 of 50 tiles
carried 99.3305% of measured reference-error delta. The next cut is turning
that evidence into a renderer-owned adaptive update contract plus future
exact producer SDF replay.
carried 99.3305% of measured reference-error delta. Runtime now has a fixed
spatial-reuse sampling budget via `FieldReservoirSpatialReuseBudget`; every
work-grid pixel writes current/temporal rows, while a deterministic phase
spends 3x3 neighbor sampling. Smoke `reservoir-sequence-20260530-003006-*`
proves native scale 0.5 / spatial budget 0.5 against reference scale 0.75 /
spatial budget 1.0. The next cut is turning budget-pressure evidence into a
renderer-owned per-tile adaptive update contract plus future exact producer
SDF replay.
- Build the planetary flow organ in staged cuts: provenance manifest, static
relief and reanalysis cube-sphere bake pipeline, stochastic advection
contracts, synthetic runtime slice, ERA5/OSCAR baseline learning, then

View file

@ -67,6 +67,18 @@ measured 1306 of 42864 pixels and found the top 5 of 50 tiles carried 99.3305%
of measured reference-error delta. This is offline allocator evidence, not a
runtime scheduler.
Runtime spatial reuse budget:
`GraphicsSettings.FieldReservoirSpatialReuseBudget` now flows through renderer
UI, runtime options, CLI/env overrides, headless capture scripts, frame
constants, and `D3D12ReservoirHistoryUpdateCS`. Current and temporal reservoir
rows are still written for every work-grid pixel; the budget only decides
whether that pixel spends the 3x3 spatial neighbor sampling pass this frame.
Default is 0.5. Smoke `artifacts/fensalir-captures/reservoir-sequence-20260530-003006-*`
used native scale 0.5 / spatial budget 0.5 against reference scale 0.75 /
spatial budget 1.0. Masked reference-error-native changed 63.7195% over 3.8261%
of frame pixels, and `reservoir-budget-pressure-20260530-003006.*` found the
top 5 of 50 tiles carried 90.8321% of measured reference-error delta.
## Hot Lesson
Area ReSTIR confirms that reuse over an area domain is invalid unless the
@ -98,9 +110,9 @@ cap remain intentionally non-authoritative.
Follow `docs/perfect-fensalir-machine-roadmap.md` Phase 1:
1. Convert budget-pressure evidence into a renderer-owned adaptive update
contract only after the owner, inputs, outputs, and transition behavior are
explicit. Do not add native-resolution reservoir intermediates.
1. Convert the fixed spatial-reuse budget into a pressure-guided adaptive
update contract only after the renderer owns a per-tile pressure input path.
Do not add native-resolution reservoir intermediates.
2. Add exact SDF producer replay only when client SDF distance functions have a
shared replay include or manifest instead of duplicating shader policy in
post.

View file

@ -83,6 +83,24 @@ public sealed class FieldReservoirAbiTests
Assert.Contains("FieldReservoirMode", capture);
}
[Fact]
public void ReservoirSpatialReuseHasExplicitBudgetOwner()
{
var repoRoot = FindRepoRoot();
var settings = File.ReadAllText(Path.Combine(repoRoot, "src", "Aquarium.Engine.Contracts", "GraphicsSettings.cs"));
var host = File.ReadAllText(Path.Combine(repoRoot, "src", "Aquarium.Engine", "AquariumHost.cs"));
var renderer = File.ReadAllText(Path.Combine(repoRoot, "src", "Aquarium.Engine", "Render", "D3D12Renderer.cs"));
var post = File.ReadAllText(Path.Combine(repoRoot, "src", "Aquarium.Engine", "Render", "Shaders", "D3D12Post.hlsl"));
var capture = File.ReadAllText(Path.Combine(repoRoot, "scripts", "capture-reservoir-mode-sequence.ps1"));
Assert.Contains("FieldReservoirSpatialReuseBudget", settings);
Assert.Contains("MinFieldReservoirSpatialReuseBudget = 0.25f", settings);
Assert.Contains("--field-reservoir-spatial-reuse-budget", host);
Assert.Contains("ReservoirBudgetInfo", renderer);
Assert.Contains("fieldReservoirShouldSpendSpatialReuse", post);
Assert.Contains("SpatialReuseBudget", capture);
}
[Fact]
public void ReservoirDisocclusionDebugModeIsOwnerDerived()
{