diff --git a/docs/papers/fensalir-native-domain-reservoirs.pdf b/docs/papers/fensalir-native-domain-reservoirs.pdf index eb0e0e6..76ce615 100644 Binary files a/docs/papers/fensalir-native-domain-reservoirs.pdf and b/docs/papers/fensalir-native-domain-reservoirs.pdf differ diff --git a/docs/papers/fensalir-native-domain-reservoirs.tex b/docs/papers/fensalir-native-domain-reservoirs.tex index 1f2a645..c223bce 100644 --- a/docs/papers/fensalir-native-domain-reservoirs.tex +++ b/docs/papers/fensalir-native-domain-reservoirs.tex @@ -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} diff --git a/docs/papers/fensalir-pipeline.pdf b/docs/papers/fensalir-pipeline.pdf index 373a2fa..f7449da 100644 Binary files a/docs/papers/fensalir-pipeline.pdf and b/docs/papers/fensalir-pipeline.pdf differ diff --git a/docs/papers/fensalir-pipeline.tex b/docs/papers/fensalir-pipeline.tex index ec6c275..1fa19bf 100644 --- a/docs/papers/fensalir-pipeline.tex +++ b/docs/papers/fensalir-pipeline.tex @@ -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. diff --git a/docs/perfect-fensalir-machine-roadmap.md b/docs/perfect-fensalir-machine-roadmap.md index 1d0a6f1..40a23af 100644 --- a/docs/perfect-fensalir-machine-roadmap.md +++ b/docs/perfect-fensalir-machine-roadmap.md @@ -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: diff --git a/scripts/capture-fensalir-frame.ps1 b/scripts/capture-fensalir-frame.ps1 index 2f68c0a..99f4811 100644 --- a/scripts/capture-fensalir-frame.ps1 +++ b/scripts/capture-fensalir-frame.ps1 @@ -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 diff --git a/src/Aquarium.Engine.Contracts/AquariumRuntimeContracts.cs b/src/Aquarium.Engine.Contracts/AquariumRuntimeContracts.cs index 7fdd03d..4433669 100644 --- a/src/Aquarium.Engine.Contracts/AquariumRuntimeContracts.cs +++ b/src/Aquarium.Engine.Contracts/AquariumRuntimeContracts.cs @@ -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); diff --git a/src/Aquarium.Engine.Contracts/GraphicsSettings.cs b/src/Aquarium.Engine.Contracts/GraphicsSettings.cs index b7507e4..d18fa48 100644 --- a/src/Aquarium.Engine.Contracts/GraphicsSettings.cs +++ b/src/Aquarium.Engine.Contracts/GraphicsSettings.cs @@ -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)); } } diff --git a/src/Aquarium.Engine/AquariumHost.cs b/src/Aquarium.Engine/AquariumHost.cs index a0b5e93..efe0117 100644 --- a/src/Aquarium.Engine/AquariumHost.cs +++ b/src/Aquarium.Engine/AquariumHost.cs @@ -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 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 args, string name, string environmentName, int fallback) { var values = args.ToArray(); diff --git a/src/Aquarium.Engine/Render/D3D12Renderer.cs b/src/Aquarium.Engine/Render/D3D12Renderer.cs index 211e295..ff87038 100644 --- a/src/Aquarium.Engine/Render/D3D12Renderer.cs +++ b/src/Aquarium.Engine/Render/D3D12Renderer.cs @@ -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, diff --git a/src/Aquarium.Engine/Render/Shaders/D3D12Post.hlsl b/src/Aquarium.Engine/Render/Shaders/D3D12Post.hlsl index 0441154..7200796 100644 --- a/src/Aquarium.Engine/Render/Shaders/D3D12Post.hlsl +++ b/src/Aquarium.Engine/Render/Shaders/D3D12Post.hlsl @@ -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; diff --git a/src/Aquarium.Fensalir/FensalirRuntime.cs b/src/Aquarium.Fensalir/FensalirRuntime.cs index 41bdf65..17d9398 100644 --- a/src/Aquarium.Fensalir/FensalirRuntime.cs +++ b/src/Aquarium.Fensalir/FensalirRuntime.cs @@ -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(); diff --git a/src/Aquarium.Zyphos/ZyphosRuntime.cs b/src/Aquarium.Zyphos/ZyphosRuntime.cs index beab0ad..1c8e6cf 100644 --- a/src/Aquarium.Zyphos/ZyphosRuntime.cs +++ b/src/Aquarium.Zyphos/ZyphosRuntime.cs @@ -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(); diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 2664bb6..317a73b 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -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-*"} diff --git a/state/map.yaml b/state/map.yaml index 1f4ea8e..545aa95 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -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 diff --git a/state/memory.json b/state/memory.json index 39d5049..9c87576 100644 --- a/state/memory.json +++ b/state/memory.json @@ -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.", diff --git a/state/scratch.md b/state/scratch.md index b611db6..bc60a29 100644 --- a/state/scratch.md +++ b/state/scratch.md @@ -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. diff --git a/tests/Aquarium.Engine.Tests/FieldReservoirAbiTests.cs b/tests/Aquarium.Engine.Tests/FieldReservoirAbiTests.cs index b164a56..e41220b 100644 --- a/tests/Aquarium.Engine.Tests/FieldReservoirAbiTests.cs +++ b/tests/Aquarium.Engine.Tests/FieldReservoirAbiTests.cs @@ -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);