Add fractal probe temporal validation

This commit is contained in:
Meta Vens 2026-05-19 20:05:47 +01:00
commit d40ede8137
6 changed files with 129 additions and 0 deletions

View file

@ -489,6 +489,9 @@ payload handle. Renderer-facing temporal validation remains open.
`FractalStructuralProbeReservoir` builds a selected-cut reservoir from an
ownership tree, summaries, and projected pixels per world unit; Zyphos now
exposes that reservoir in its fractal render plan/debug dump.
`FractalProbeReuseValidator.ValidateTemporal` now names the renderer-facing
rejection reasons for camera motion, disocclusion, material mismatch, and
visibility before those checks are lowered to GPU reuse passes.
### Phase D: Occupancy Graph

View file

@ -347,6 +347,9 @@ Tasks:
error, bounds, and disocclusion/material compatibility. Domain ancestry and
local-shift checks exist; camera/disocclusion/material validation still need
renderer inputs.
- [x] Add CPU temporal reuse validation contract with camera motion,
disocclusion, material mismatch, visibility, domain ancestry, local-frame
error, and bound rejection reasons.
- [ ] Add spatial reuse validation across screen tiles and cube-sphere neighbor
domains.
- [ ] Feed reservoir confidence, selected target, candidate count, sample age,
@ -373,6 +376,8 @@ Tests:
- fixed camera path converges within tolerance;
- [x] temporal reuse rejects mismatched domain ancestry;
- [x] spatial reuse rejects invalid local-frame shifts;
- [x] temporal reuse rejects camera motion, disocclusion, material mismatch,
and low visibility;
- [x] TAA history confidence is modulated by current reservoir confidence;
- [x] TAA history confidence drops when previous-frame reservoir/domain validity changes;
- update cost respects budget.

View file

@ -30,6 +30,10 @@ public enum FractalProbeReuseRejection
DifferentLineage,
ExcessiveLocalShift,
InvalidBounds,
ExcessiveCameraMotion,
Disoccluded,
MaterialMismatch,
NotVisible,
}
public readonly record struct FractalProbeReuseResult(
@ -37,6 +41,16 @@ public readonly record struct FractalProbeReuseResult(
FractalProbeReuseRejection Rejection,
float LocalShift);
public readonly record struct FractalProbeTemporalValidation(
float CameraMotionPixels,
float MaxCameraMotionPixels,
float DisocclusionConfidence,
float MinDisocclusionConfidence,
float MaterialDelta,
float MaxMaterialDelta,
float VisibilityConfidence,
float MinVisibilityConfidence);
public static class FractalProbeReuseValidator
{
public static FractalProbeReuseResult Validate(
@ -76,6 +90,42 @@ public static class FractalProbeReuseValidator
return new FractalProbeReuseResult(true, FractalProbeReuseRejection.None, localShift);
}
public static FractalProbeReuseResult ValidateTemporal(
FractalProbeSample source,
FractalProbeSample target,
FractalDomainGraph domains,
float maxLocalShift,
FractalProbeTemporalValidation temporal)
{
var spatial = Validate(source, target, domains, maxLocalShift);
if (!spatial.CanReuse)
{
return spatial;
}
if (!float.IsFinite(temporal.CameraMotionPixels) || temporal.CameraMotionPixels > MathF.Max(temporal.MaxCameraMotionPixels, 0.0f))
{
return spatial with { CanReuse = false, Rejection = FractalProbeReuseRejection.ExcessiveCameraMotion };
}
if (!float.IsFinite(temporal.DisocclusionConfidence) || temporal.DisocclusionConfidence < Math.Clamp(temporal.MinDisocclusionConfidence, 0.0f, 1.0f))
{
return spatial with { CanReuse = false, Rejection = FractalProbeReuseRejection.Disoccluded };
}
if (!float.IsFinite(temporal.MaterialDelta) || temporal.MaterialDelta > MathF.Max(temporal.MaxMaterialDelta, 0.0f))
{
return spatial with { CanReuse = false, Rejection = FractalProbeReuseRejection.MaterialMismatch };
}
if (!float.IsFinite(temporal.VisibilityConfidence) || temporal.VisibilityConfidence < Math.Clamp(temporal.MinVisibilityConfidence, 0.0f, 1.0f))
{
return spatial with { CanReuse = false, Rejection = FractalProbeReuseRejection.NotVisible };
}
return spatial;
}
private static bool ShareLineage(AquariumFractalKey sourceDomain, AquariumFractalKey targetDomain, FractalDomainGraph domains)
{
var sourcePath = domains.GetPath(sourceDomain).Select(domain => domain.Key).ToArray();

View file

@ -167,3 +167,4 @@
{"timestamp":"2026-05-19T20:39:00+01:00","topic":"fractal-occupancy-graph","lesson":"FractalContributionCache now routes through `FractalOccupancyGraph`, the first explicit occupancy memory organ from the Perfect Machine architecture. The graph owns contribution estimates plus visibility/update-probability telemetry; observations update mean/variance/confidence through the existing estimator, and unobserved nodes lose confidence while stale uncertainty grows. This keeps cache convergence state in one authority instead of growing side tables.","files":["src/Aquarium.Engine.Fractal/Lod/FractalOccupancyGraph.cs","src/Aquarium.Engine.Fractal/Lod/FractalContributionCache.cs","tests/Aquarium.Engine.Fractal.Tests/FractalOccupancyGraphTests.cs","tests/Aquarium.Engine.Fractal.Tests/FractalContributionCacheTests.cs","docs/perfect-machine-architecture.md","research/rendering/fractal-brush-implementation-roadmap.md"],"source":"Perfect Machine Phase D implementation after explicit reservoir guide targets"}
{"timestamp":"2026-05-19T21:01:00+01:00","topic":"structural-probe-generator","lesson":"IFS structure can now enter the reservoir path as typed evidence. `FractalStructuralProbeGenerator` lowers `AquariumFractalSummary` rows into `FractalProbeSample` candidates with local center, bound radius, projected target contribution, clamped source PDF, material delta, and payload handle. This gives structural `.aquageo` summaries a real producer boundary before renderer-facing temporal validation and GPU reservoir reuse are added.","files":["src/Aquarium.Engine.Fractal/Lod/FractalStructuralProbeGenerator.cs","tests/Aquarium.Engine.Fractal.Tests/FractalStructuralProbeGeneratorTests.cs","docs/perfect-machine-architecture.md","research/rendering/fractal-brush-implementation-roadmap.md","state/memory.json"],"source":"Perfect Machine Phase C implementation after occupancy graph promotion"}
{"timestamp":"2026-05-19T21:18:00+01:00","topic":"zyphos-structural-probe-reservoir","lesson":"Zyphos now exposes a structural probe reservoir beside its selected cut and resource plan. `FractalStructuralProbeReservoir` builds a selected-cut `FractalProbeSample` reservoir from the ownership tree, summaries, node domains, and projected pixels per world unit; `ZyphosFractalRenderPlan` carries the snapshot and the debug dump reports selected probe weight/candidate count. This connects the pure IFS structural-probe producer to a live client path before GPU reservoir buffers are introduced.","files":["src/Aquarium.Engine.Fractal/Lod/FractalStructuralProbeReservoir.cs","src/Aquarium.Zyphos/ZyphosFractalTerrain.cs","tests/Aquarium.Engine.Fractal.Tests/FractalStructuralProbeReservoirTests.cs","tests/Aquarium.Engine.Fractal.Tests/ZyphosFractalTerrainTests.cs","docs/perfect-machine-architecture.md","research/rendering/fractal-brush-implementation-roadmap.md"],"source":"continuing Perfect Machine Phase C after adding structural probe generation"}
{"timestamp":"2026-05-19T21:42:00+01:00","topic":"fractal-probe-temporal-validation","lesson":"Fractal probe reuse now has a CPU temporal validation contract before GPU reuse exists. `FractalProbeReuseValidator.ValidateTemporal` composes domain/local-shift validation with renderer-facing gates for camera motion, disocclusion confidence, material delta, and visibility confidence, returning explicit rejection reasons. This keeps future ReSTIR/GRIS probe reuse from becoming private shader folklore.","files":["src/Aquarium.Engine.Fractal/Lod/FractalProbeSample.cs","tests/Aquarium.Engine.Fractal.Tests/FractalProbeSampleTests.cs","docs/perfect-machine-architecture.md","research/rendering/fractal-brush-implementation-roadmap.md","state/memory.json"],"source":"Perfect Machine Phase C validation contract work"}

View file

@ -93,6 +93,7 @@
"FractalContributionCache is the live cache loop: each frame builds a resource plan from current summaries and cached states, stochastically observes only budgeted update nodes, stores mean/variance/confidence/sample age through FractalContributionEstimator, frames scheduled updates as weighted ResampledImportanceReservoir candidates, and lets backend packet compilation stay cached by selected-cut key. It is a candidate source, not the whole temporal/spatial reuse pass. Do not replace this with a static plan cache that stops learning after the first camera bucket.",
"FractalOccupancyGraph is now the live memory behind FractalContributionCache. It owns per-node contribution state, last visible frame, last observed score, and update probability; unobserved nodes decay confidence and grow stale uncertainty. Keep richer residency/resource fields here rather than adding another cache beside it.",
"FractalProbeSample is the typed reservoir sample shape for fractal SDF/detail reuse: domain key, node key, local center, bound radius, target contribution, source PDF, material delta, and payload handle. Reuse must pass domain lineage and local-shift validation now, and later camera motion, disocclusion, material compatibility, and visibility validation before it is allowed into temporal/spatial reuse.",
"FractalProbeReuseValidator.ValidateTemporal is the CPU contract for future GPU temporal reuse. It rejects excessive camera motion, disocclusion, material mismatch, low visibility, bad domain lineage, excessive local shift, and invalid bounds with explicit reasons; shader reuse should mirror this contract instead of inventing private validation rules.",
"FractalStructuralProbeGenerator lowers AquariumFractalSummary rows into FractalProbeSample reservoir candidates with local support, projected target contribution, clamped source PDF, material delta, and payload handle. This is the structural IFS probe producer; renderer-facing temporal validation still needs camera/disocclusion/material/visibility inputs.",
"FractalStructuralProbeReservoir builds a reservoir snapshot from an ownership tree, selected cut, summaries, and projected pixels per world unit. ZyphosFractalRenderPlan now carries this structural probe reservoir, so the planet path exposes reservoir-shaped structural evidence beside brushes/resource plans before GPU reuse exists.",
"Zyphos camera distance limits belong to the selected spatial domain, not one global planetary clamp. Leaf/pebble domains must be able to zoom below whole-body distances, and the parent star must be a visible SDF body when it is also the primary light source.",

View file

@ -60,6 +60,62 @@ public sealed class FractalProbeSampleTests
Assert.Equal(FractalProbeReuseRejection.ExcessiveLocalShift, result.Rejection);
}
[Fact]
public void TemporalReuseValidatorRejectsExcessiveCameraMotion()
{
var result = FractalProbeReuseValidator.ValidateTemporal(
Probe("zyphos/surface", Vector3.Zero),
Probe("zyphos/surface", Vector3.Zero),
DomainGraph(),
maxLocalShift: 1.0f,
ValidTemporal() with { CameraMotionPixels = 12.0f, MaxCameraMotionPixels = 4.0f });
Assert.False(result.CanReuse);
Assert.Equal(FractalProbeReuseRejection.ExcessiveCameraMotion, result.Rejection);
}
[Fact]
public void TemporalReuseValidatorRejectsDisocclusion()
{
var result = FractalProbeReuseValidator.ValidateTemporal(
Probe("zyphos/surface", Vector3.Zero),
Probe("zyphos/surface", Vector3.Zero),
DomainGraph(),
maxLocalShift: 1.0f,
ValidTemporal() with { DisocclusionConfidence = 0.2f, MinDisocclusionConfidence = 0.7f });
Assert.False(result.CanReuse);
Assert.Equal(FractalProbeReuseRejection.Disoccluded, result.Rejection);
}
[Fact]
public void TemporalReuseValidatorRejectsMaterialMismatch()
{
var result = FractalProbeReuseValidator.ValidateTemporal(
Probe("zyphos/surface", Vector3.Zero),
Probe("zyphos/surface", Vector3.Zero),
DomainGraph(),
maxLocalShift: 1.0f,
ValidTemporal() with { MaterialDelta = 0.6f, MaxMaterialDelta = 0.25f });
Assert.False(result.CanReuse);
Assert.Equal(FractalProbeReuseRejection.MaterialMismatch, result.Rejection);
}
[Fact]
public void TemporalReuseValidatorAllowsValidRendererInputs()
{
var result = FractalProbeReuseValidator.ValidateTemporal(
Probe("zyphos/surface", Vector3.Zero),
Probe("zyphos/surface/forest", new Vector3(0.1f, 0.0f, 0.0f)),
DomainGraph(),
maxLocalShift: 1.0f,
ValidTemporal());
Assert.True(result.CanReuse);
Assert.Equal(FractalProbeReuseRejection.None, result.Rejection);
}
private static FractalProbeSample Probe(
string domainKey,
Vector3 localCenter,
@ -89,6 +145,19 @@ public sealed class FractalProbeSampleTests
]);
}
private static FractalProbeTemporalValidation ValidTemporal()
{
return new FractalProbeTemporalValidation(
CameraMotionPixels: 1.0f,
MaxCameraMotionPixels: 4.0f,
DisocclusionConfidence: 0.95f,
MinDisocclusionConfidence: 0.7f,
MaterialDelta: 0.05f,
MaxMaterialDelta: 0.25f,
VisibilityConfidence: 0.9f,
MinVisibilityConfidence: 0.5f);
}
private static AquariumFractalDomain Domain(string key, AquariumFractalDomainKind kind, string? parent)
{
return new AquariumFractalDomain(