Add reservoir baseline comparison mode

This commit is contained in:
Meta Vens 2026-05-29 23:28:43 +01:00
commit 5e4b9eea00
18 changed files with 144 additions and 20 deletions

View file

@ -370,6 +370,13 @@ and conservative object support. Exact client SDF distance re-evaluation
remains producer-owned work rather than a generic postprocess authority.
Debug modes expose selected coordinates, support footprint/domain kind, and
shift/rejection state from the same row-3 layer consumed by presentation.
The renderer also exposes a field-reservoir mode switch for comparison: native
mode enables domain validity, support overlap, and producer replay during
temporal reuse, while texel-baseline mode keeps the same rows but disables those
native-domain gates. The first paired capture set
\texttt{reservoir-compare-20260529-231904-*} changed 10.8208\% of final-frame
pixels between modes. This proves the comparison switch is live; it is not yet
a temporal leakage or ghosting metric.
\section{Required Evaluation}

Binary file not shown.

View file

@ -654,7 +654,13 @@ producer-keyed manifest/source table to re-evaluate selected logical
column/curve coordinates in the current frame, and SDF object-hit replay carries
the previous selected hit through object motion before validating current
ray/travel/support. The next proof cuts are exact producer SDF replay, measured
rejection precision, and baseline/native-domain captures.
temporal leakage/ghosting, and measured rejection precision.
Those captures now have an explicit comparison switch: native mode enables the
domain validity, support-overlap, and producer-replay gates, while texel-baseline
mode uses the same row ABI but disables those native-domain gates during
temporal reuse. The first paired Fensalir capture set
\texttt{reservoir-compare-20260529-231904-*} produced a 10.8208\% final-frame
pixel delta. That is a live-switch check, not a quality claim.
The system has cut the false ``nearest-travel-minus-coverage'' priority owner
and the side-cache temptation.

View file

@ -493,8 +493,12 @@ Work packet:
reconstructed from stored selected UV/travel, carried through object center
motion, and checked against the current camera ray/travel/support before
reuse can merge them.
8. Next: capture the occluded-spectrum-tubes scene in baseline and native-domain
modes.
8. Done: add an explicit field-reservoir mode switch and capture paired
native-domain versus texel-baseline outputs. The first comparison set lives
under `artifacts/fensalir-captures/reservoir-compare-20260529-231904-*`.
Native versus baseline changed 10.8208% of final-frame pixels, 0.0165% of
rejection-debug pixels, and 0.2976% of shift-debug pixels. That proves the
switch is live; it is not yet a quality metric.
Required verification:

View file

@ -4,6 +4,8 @@ param(
[int]$Height = 720,
[int]$ReadyFrames = 8,
[int]$RenderDebugMode = -1,
[ValidateSet("native", "baseline")]
[string]$FieldReservoirMode = "native",
[int]$RetainSlots = 4,
[int]$TimeoutSeconds = 120
)
@ -55,6 +57,9 @@ $arguments = @(
if ($RenderDebugMode -ge 0) {
$arguments += @("--render-debug", $RenderDebugMode)
}
if ($FieldReservoirMode -eq "baseline") {
$arguments += @("--field-reservoir-mode", "baseline")
}
$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

@ -39,5 +39,9 @@ public interface IAquariumRuntimeFactory
IAquariumRuntime Create(AquariumRuntimeOptions options);
}
public readonly record struct AquariumRuntimeOptions(bool Headless, string? CultCachePath, int? RenderDebugModeOverride = null);
public readonly record struct AquariumRuntimeOptions(
bool Headless,
string? CultCachePath,
int? RenderDebugModeOverride = null,
int? FieldReservoirModeOverride = null);

View file

@ -4,10 +4,13 @@ public readonly record struct GraphicsSettings(
int RenderDebugMode,
float SceneExposure,
float BloomIntensity,
float BloomVeilIntensity)
float BloomVeilIntensity,
int FieldReservoirMode)
{
public const int MinRenderDebugMode = 0;
public const int MaxRenderDebugMode = 18;
public const int FieldReservoirModeNativeDomain = 0;
public const int FieldReservoirModeTexelBaseline = 1;
public const float MinSceneExposure = 0.02f;
public const float MaxSceneExposure = 1.2f;
public const float MinBloomIntensity = 0.0f;
@ -19,7 +22,8 @@ public readonly record struct GraphicsSettings(
RenderDebugMode: 0,
SceneExposure: 0.16f,
BloomIntensity: 0.072f,
BloomVeilIntensity: 0.014f);
BloomVeilIntensity: 0.014f,
FieldReservoirMode: FieldReservoirModeNativeDomain);
public GraphicsSettings Normalized()
{
@ -27,6 +31,7 @@ public readonly record struct GraphicsSettings(
Math.Clamp(RenderDebugMode, MinRenderDebugMode, MaxRenderDebugMode),
Math.Clamp(SceneExposure, MinSceneExposure, MaxSceneExposure),
Math.Clamp(BloomIntensity, MinBloomIntensity, MaxBloomIntensity),
Math.Clamp(BloomVeilIntensity, MinBloomVeilIntensity, MaxBloomVeilIntensity));
Math.Clamp(BloomVeilIntensity, MinBloomVeilIntensity, MaxBloomVeilIntensity),
Math.Clamp(FieldReservoirMode, FieldReservoirModeNativeDomain, FieldReservoirModeTexelBaseline));
}
}

View file

@ -11,7 +11,11 @@ public static class AquariumHost
{
public static int Run(string[] args)
{
var runtimeOptions = new AquariumRuntimeOptions(ParseHeadless(args), ParseCachePath(args), ParseRenderDebugMode(args));
var runtimeOptions = new AquariumRuntimeOptions(
ParseHeadless(args),
ParseCachePath(args),
ParseRenderDebugMode(args),
ParseFieldReservoirMode(args));
using var runtimeLoader = new ClientRuntimeLoader(runtimeOptions, ParseClientAssemblyPath(args), ParseClientReloadPointerPath(args));
var runtime = runtimeLoader.Load();
if (runtimeOptions.RenderDebugModeOverride is { } renderDebugModeOverride)
@ -21,6 +25,13 @@ public static class AquariumHost
RenderDebugMode = renderDebugModeOverride,
}).Normalized();
}
if (runtimeOptions.FieldReservoirModeOverride is { } fieldReservoirModeOverride)
{
runtime.GraphicsSettings = (runtime.GraphicsSettings with
{
FieldReservoirMode = fieldReservoirModeOverride,
}).Normalized();
}
var input = new InputState();
var headlessSize = ParseHeadlessSize(args);
var width = runtime.Options.Headless ? headlessSize.Width : 1280;
@ -264,6 +275,30 @@ public static class AquariumHost
: null;
}
private static int? ParseFieldReservoirMode(IReadOnlyCollection<string> args)
{
var values = args.ToArray();
for (var index = 0; index < values.Length - 1; index++)
{
if (string.Equals(values[index], "--field-reservoir-mode", StringComparison.OrdinalIgnoreCase))
{
return ParseFieldReservoirModeValue(values[index + 1]);
}
}
return ParseFieldReservoirModeValue(Environment.GetEnvironmentVariable("AQUARIUM_FIELD_RESERVOIR_MODE"));
}
private static int? ParseFieldReservoirModeValue(string? value)
{
return value?.Trim().ToLowerInvariant() switch
{
"native" or "native-domain" or "0" => GraphicsSettings.FieldReservoirModeNativeDomain,
"baseline" or "texel" or "texel-baseline" or "1" => GraphicsSettings.FieldReservoirModeTexelBaseline,
_ => null,
};
}
private static int ParsePositiveIntArgument(IReadOnlyCollection<string> args, string name, string environmentName, int fallback)
{
var values = args.ToArray();

View file

@ -139,6 +139,11 @@ public sealed class D3D12Renderer : IAquariumRenderer
new(17, "Reservoir Support"),
new(18, "Reservoir Shift"),
];
private static readonly DebugUi.DebugUiOption[] FieldReservoirModeOptions =
[
new(GraphicsSettings.FieldReservoirModeNativeDomain, "Native Domain"),
new(GraphicsSettings.FieldReservoirModeTexelBaseline, "Texel Baseline"),
];
private static readonly DebugUi.DebugUiOption[] SynthPresetOptions = AquaSynth.Dsl.BuiltInScripts.ReferenceScripts()
.Select((preset, index) => new DebugUi.DebugUiOption(index, $"{preset.Family}/{preset.Name}"))
.ToArray();
@ -572,6 +577,7 @@ public sealed class D3D12Renderer : IAquariumRenderer
.Section("View", () => activeDebugTab == 0)
.Options("Render Debug", () => RenderDebugMode, value => RenderDebugMode = Math.Clamp(value, GraphicsSettings.MinRenderDebugMode, GraphicsSettings.MaxRenderDebugMode), RenderDebugOptions, "Selects the active renderer debug view.", () => activeDebugTab == 0)
.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)
.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)
@ -907,7 +913,7 @@ public sealed class D3D12Renderer : IAquariumRenderer
cameraFrustum.Right / cameraFrustum.Near,
cameraFrustum.Bottom / cameraFrustum.Near,
cameraFrustum.Top / cameraFrustum.Near),
new Vector4(cameraFrustum.Near, cameraFrustum.Far, 0.0f, 0.0f),
new Vector4(cameraFrustum.Near, cameraFrustum.Far, settings.FieldReservoirMode, 0.0f),
new Vector4(
acousticConstraintCount,
gpuFusionPointCount,

View file

@ -116,6 +116,12 @@ static const float FIELD_ID_TUBE_FIELD_BASE = 5100.0;
static const float FIELD_ID_TUBE_FIELD_MAX = 10000.0;
static const int AQUARIUM_SDF_OBJECT_CAPACITY = 64;
static const float MAX_HISTORY_AGE = 32.0;
bool nativeDomainReservoirEnabled()
{
return cameraFrustumZ.z < 0.5;
}
VertexOut FullscreenTriangleVS(uint vertexId : SV_VertexID)
{
float2 uv = float2((vertexId << 1) & 2, vertexId & 2);
@ -716,14 +722,21 @@ float reservoirTemporalValidationWeight(
: 1.0 - smoothstep(0.10, 0.50, abs(saturate(previousSample.control.x) - saturate(currentSample.control.x)));
float detailWeight = 1.0 - smoothstep(0.08, 0.45, abs(saturate(previousSample.control.z) - saturate(currentSample.control.z)));
float confidenceWeight = lerp(0.45, 1.0, min(reservoirSampleConfidence(currentSample), reservoirSampleConfidence(previousSample)));
float domainWeight = reservoirSampleDomainValidity(currentSample) * reservoirSampleDomainValidity(previousSample);
float supportOverlap = fieldReservoirDomainSupportOverlap(currentSample, previousSample, dimensions);
bool useNativeDomain = nativeDomainReservoirEnabled();
float domainWeight = useNativeDomain
? reservoirSampleDomainValidity(currentSample) * reservoirSampleDomainValidity(previousSample)
: 1.0;
float supportOverlap = useNativeDomain
? fieldReservoirDomainSupportOverlap(currentSample, previousSample, dimensions)
: 1.0;
uint2 currentPixel = (uint2)pixelFromUv(currentSample.domainSample.xy);
float replayWeight = fieldReservoirSampleRequiresExplicitMotion(currentSample)
float replayWeight = !useNativeDomain
? 1.0
: (fieldReservoirSampleRequiresExplicitMotion(currentSample)
? tubeFieldReplayValidationWeight(currentSample, previousSample, currentPixel)
: (fieldReservoirSampleIsSdfObject(currentSample)
? sdfObjectReplayValidationWeight(currentSample, previousSample, currentPixel)
: 1.0);
: 1.0));
return travelWeight * fieldWeight * normalWeight * colorWeight * coverageWeight * coverageContinuityWeight * detailWeight * confidenceWeight * domainWeight * supportOverlap * replayWeight;
}
@ -754,6 +767,11 @@ float reservoirTemporalRejectionCode(
return 6.0;
}
if (!nativeDomainReservoirEnabled())
{
return 1.0;
}
if (!fieldReservoirDomainStateValid(previousSample) || !fieldReservoirDomainStateValid(currentSample))
{
return 7.0;

View file

@ -18,7 +18,12 @@ public sealed class FensalirRuntime : IAquariumRuntime
public AquariumRuntimeOptions Options { get; private set; }
public GraphicsSettings GraphicsSettings { get; set; } = new(0, 1.04f, 0.52f, 0.08f);
public GraphicsSettings GraphicsSettings { get; set; } = new(
0,
1.04f,
0.52f,
0.08f,
GraphicsSettings.FieldReservoirModeNativeDomain);
public AquariumRenderPlan RenderPlan { get; } = FensalirRenderPlan.Create();

View file

@ -37,7 +37,12 @@ public sealed class ZyphosRuntime : IAquariumRuntime
public AquariumRuntimeOptions Options { get; private set; }
public GraphicsSettings GraphicsSettings { get; set; } = new(0, 1.08f, 0.32f, 0.05f);
public GraphicsSettings GraphicsSettings { get; set; } = new(
0,
1.08f,
0.32f,
0.05f,
GraphicsSettings.FieldReservoirModeNativeDomain);
public AquariumRenderPlan RenderPlan { get; } = ZyphosRenderPlan.Create();

View file

@ -296,3 +296,4 @@
{"timestamp":"2026-05-29T22:42:00+01:00","topic":"tubefield-single-batch-replay","lesson":"TubeField native-domain replay now uses logical source column plus curve coordinate as the selected producer coordinate while field id retains physical rolling-column identity. The reservoir history compute pass receives a TubeField replay source/constants binding only when exactly one TubeField batch exists, then re-evaluates the shifted selected sample against the live producer buffer before temporal merge. Multi-batch TubeField replay is intentionally invalid until a producer-keyed replay manifest maps selected samples to source buffers and constants; do not fill that gap with a side cache.","files":["src/Aquarium.Engine/Render/Shaders/D3D12TubeField.hlsl","src/Aquarium.Engine/Render/Shaders/D3D12Post.hlsl","src/Aquarium.Engine/Render/D3D12Renderer.cs","docs/perfect-fensalir-machine-roadmap.md","docs/papers/fensalir-native-domain-reservoirs.tex","docs/papers/fensalir-pipeline.tex","state/map.yaml","state/scratch.md"],"sources":["docs/perfect-fensalir-machine-roadmap.md"]}
{"timestamp":"2026-05-29T23:10:00+01:00","topic":"native-domain-sdf-object-replay","lesson":"SDF temporal reuse should validate the visible hit, not merely the texel history. The native reservoir now reconstructs the previous selected SDF world hit from stored UV/travel, carries it through object center motion, and rejects history unless the replayed hit still matches the current ray/travel/support envelope; exact client SDF distance re-evaluation remains producer-owned work until replay manifests or shared includes exist.","files":["src/Aquarium.Engine/Render/Shaders/D3D12Post.hlsl","tests/Aquarium.Engine.Tests/FieldReservoirAbiTests.cs","docs/perfect-fensalir-machine-roadmap.md","state/map.yaml","state/memory.json"],"source":"docs/perfect-fensalir-machine-roadmap.md"}
{"timestamp":"2026-05-29T23:16:00+01:00","topic":"tubefield-replay-manifest","lesson":"TubeField replay authority no longer comes from a CPU-side single-batch special case. The history shader reads a bounded producer-keyed replay manifest and raw source descriptor table, scans for the selected sample's field-id family, and re-evaluates the shifted logical column/curve coordinate from the matching source. Overflow is invalid instead of guessed; exact scaling beyond the descriptor cap should become a replay atlas only when captures prove the cap is real pressure.","files":["src/Aquarium.Engine/Render/D3D12Renderer.cs","src/Aquarium.Engine/Render/D3D12StructuredBuffer.cs","src/Aquarium.Engine/Render/Shaders/D3D12Post.hlsl","tests/Aquarium.Engine.Tests/FieldReservoirAbiTests.cs","docs/perfect-fensalir-machine-roadmap.md","state/map.yaml","state/memory.json"],"source":"docs/perfect-fensalir-machine-roadmap.md"}
{"timestamp":"2026-05-29T23:30:00+01:00","topic":"reservoir-baseline-comparison-mode","lesson":"Native-domain versus texel-owned baseline comparison now has an explicit field-reservoir mode switch. Both modes use the same reservoir rows; native mode enables domain validity, support overlap, and producer replay at temporal reuse, while baseline mode keeps texel-owned field/travel/normal/color/confidence validation. First paired captures at artifacts/fensalir-captures/reservoir-compare-20260529-231904-* changed 10.8208% of final-frame pixels, 0.0165% of rejection-debug pixels, and 0.2976% of shift-debug pixels. This proves the switch is live but does not yet measure temporal leakage or ghosting quality.","files":["src/Aquarium.Engine.Contracts/GraphicsSettings.cs","src/Aquarium.Engine.Contracts/AquariumRuntimeContracts.cs","src/Aquarium.Engine/AquariumHost.cs","src/Aquarium.Engine/Render/D3D12Renderer.cs","src/Aquarium.Engine/Render/Shaders/D3D12Post.hlsl","scripts/capture-fensalir-frame.ps1","tests/Aquarium.Engine.Tests/FieldReservoirAbiTests.cs","docs/perfect-fensalir-machine-roadmap.md"],"source":"artifacts/fensalir-captures/reservoir-compare-20260529-231904-*"}

View file

@ -135,8 +135,11 @@ next_actions:
producer-keyed manifest before re-evaluating the shifted logical
column/curve coordinate against the live producer buffer, and SDF history
carries the previous selected hit through object motion before validating
current ray/travel/support. The next cut is baseline/native-domain capture
plus future exact producer SDF replay.
current ray/travel/support. A field-reservoir mode switch now captures
native-domain validation against a texel-owned baseline; the first paired
set is `reservoir-compare-20260529-231904-*`, where native versus baseline
changed 10.8208% of final-frame pixels. The next cut is a temporal
leakage/ghosting metric harness 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

@ -55,6 +55,7 @@
"The consolidated fractal brush architecture lives in research/rendering/fractal-brush-architecture-plan.md. Pipeline: client intent -> domain roots -> semantic IFS brush grammar -> ownership tree -> node summaries -> contribution cache/residency scheduler -> backend packets -> renderer passes -> debug/evidence.",
"The fractal domain reservoir doctrine lives in docs/fractal-domain-reservoir.md. Area ReSTIR makes selected sample coordinates and support part of reservoir identity; TSR-style reconstruction then consumes that validated domain evidence. Pixel rows are execution projections, not the native sampling domain.",
"Native-domain reservoir temporal reuse validates producer replay at the visible field layer: TubeField history chooses a bounded producer-keyed replay manifest/source table before re-evaluating selected logical column/curve coordinates against the live buffer, and SDF history carries the previous selected hit through object motion before accepting current ray/travel/support.",
"Field reservoir comparison is an explicit renderer mode, not a separate producer path: native-domain validation and texel-baseline reuse share rows and differ at the temporal reuse gate. The 2026-05-29 comparison capture proved the switch is live but is not yet a temporal quality metric.",
"The actionable implementation checklist lives in research/rendering/fractal-brush-implementation-roadmap.md.",
"The .aquageo DSL carries semantic domains and Form claims before backend lowering. Height, SignedDistance, Density, and Extinction must not be collapsed into one payload lane.",
"Fractal summaries carry FormEncodingMask so selected-cut structural probes do not silently default transparent form nodes back into SDF.",

View file

@ -3,7 +3,10 @@
## Current Slice
Extend native-domain reservoir replay from single-batch TubeField/SDF proof
paths into measured baseline/native-domain capture.
paths into measured baseline/native-domain capture. The first comparison set is
`artifacts/fensalir-captures/reservoir-compare-20260529-231904-*`: native versus
texel-baseline changed 10.8208% of final-frame pixels, 0.0165% of
rejection-debug pixels, and 0.2976% of shift-debug pixels.
## Hot Lesson
@ -32,8 +35,8 @@ cap remain intentionally non-authoritative.
Follow `docs/perfect-fensalir-machine-roadmap.md` Phase 1:
1. Capture the occluded-spectrum-tubes scene in baseline/native-domain modes
after replay is real, not merely support-overlap filtered.
1. Add a real metric harness for temporal leakage/ghosting instead of relying
on single-frame image deltas.
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

@ -67,6 +67,22 @@ public sealed class FieldReservoirAbiTests
Assert.Contains("CreateRawShaderResourceView", renderer);
}
[Fact]
public void ReservoirBaselineModeIsAnExplicitValidationSwitch()
{
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 post = File.ReadAllText(Path.Combine(repoRoot, "src", "Aquarium.Engine", "Render", "Shaders", "D3D12Post.hlsl"));
var capture = File.ReadAllText(Path.Combine(repoRoot, "scripts", "capture-fensalir-frame.ps1"));
Assert.Contains("FieldReservoirModeNativeDomain", settings);
Assert.Contains("FieldReservoirModeTexelBaseline", settings);
Assert.Contains("--field-reservoir-mode", host);
Assert.Contains("nativeDomainReservoirEnabled", post);
Assert.Contains("FieldReservoirMode", capture);
}
private static string FindRepoRoot()
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);