568 lines
24 KiB
Markdown
568 lines
24 KiB
Markdown
# Fractal Reference Oracles
|
|
|
|
Aquarium needs boring reference pressure before it earns pretty fractal claims.
|
|
The renderer is not allowed to prove itself by looking interesting.
|
|
|
|
## Apophysis/FLAM3 Boundary
|
|
|
|
Apophysis 7x is the classic editor lineage for fractal flames, and the
|
|
repository is GPL-2.0. FLAM3 is the older portable renderer/genome toolchain
|
|
with the public `.flame`/`.flam3` ecosystem and documented variation list.
|
|
|
|
Use them like AquaSynth uses synth references:
|
|
|
|
- Reference fixtures live in tests or artifacts, not engine runtime.
|
|
- GPL renderer/source stays external; Aquarium does not paste implementation
|
|
code into the runtime.
|
|
- Aquarium DSL candidates must reproduce reference behavior through numeric
|
|
gates before GPU rendering claims are accepted.
|
|
- Image parity receipts should be produced by an external reference renderer
|
|
when a local FLAM3/Apophysis-compatible binary is available.
|
|
|
|
## First Gate
|
|
|
|
The first committed fixture is deliberately small:
|
|
|
|
- `tests/Aquarium.Engine.Fractal.Tests/Fixtures/Apophysis/linear-sierpinski.flame`
|
|
- `tests/Aquarium.Engine.Fractal.Tests/Fixtures/Apophysis/linear-sierpinski.aquageo`
|
|
|
|
The `.flame` file uses three Apophysis/FLAM3-style linear xforms. The
|
|
`.aquageo` file expresses the same affine IFS using `affineifs` and
|
|
`affinemap`. `ApophysisReferenceParityTests` compares both through a fixed
|
|
transform sequence and through chaos-game moment bounds.
|
|
|
|
This is not full flame parity. It is the first ruler on the desk.
|
|
|
|
## Next Gates
|
|
|
|
1. DONE: parse a minimal `.flame` subset into a renderer-agnostic flame
|
|
definition: xform weight, color, affine coefficients, and `linear`,
|
|
`spherical`, and `bubble` variation weights.
|
|
2. DONE: add exact point parity for `linear`, `spherical`, and `bubble`,
|
|
plus a deterministic histogram checksum for the mixed fixture.
|
|
3. DONE: add an optional external FLAM3-compatible render receipt path. When
|
|
`flam3-render` is available, Aquarium renders the fixture to binary PPM and
|
|
records image-shape, non-black pixel count, RGB checksum, and luminance
|
|
checksum under `artifacts/fractal-flame-reference`.
|
|
4. DONE: lower proven flame definitions into GPU program rows for the receipt
|
|
shader's `flame 2D affine/variation program` mode.
|
|
5. Use those rows as a visual test scene only through GPU-resident reservoirs,
|
|
not by restoring a CPU direct renderer.
|
|
|
|
## Local Receipt
|
|
|
|
Run:
|
|
|
|
```powershell
|
|
.\scripts\fractal-flame-receipt.ps1
|
|
```
|
|
|
|
Default receipt:
|
|
|
|
- fixture: `linear-spherical-bubble.flame`
|
|
- samples: `8192`
|
|
- histogram: `64x64`
|
|
- bounds: `-8,-8,8,8`
|
|
- seed: `0x0BADC0DE`
|
|
- expected checksum: `0xAEB1C81B`
|
|
|
|
This receipt is Aquarium's local CPU parser/evaluator checksum. It is not yet
|
|
an external FLAM3/Apophysis renderer receipt.
|
|
|
|
## External Renderer Receipt
|
|
|
|
FLAM3 stays outside the repo and outside runtime binaries. If a local
|
|
`flam3-render` executable is installed, run:
|
|
|
|
```powershell
|
|
.\scripts\fractal-flame-reference-render.ps1 -Flam3Render C:\path\to\flam3-render.exe
|
|
```
|
|
|
|
The script follows FLAM3's documented environment-variable interface: `in`
|
|
selects the flame file, `out` selects the output image, `format=ppm` requests a
|
|
plain reference image, `seed` fixes the random sequence, and `qs` controls
|
|
quality scale. Aquarium then hashes the PPM through
|
|
`tools/Aquarium.Fractal.Receipt --reference-ppm`.
|
|
|
|
This is the first external renderer receipt. It proves the fixture can be
|
|
rendered by the reference family and gives Aquarium a stable image artifact to
|
|
compare as the local flame subset grows.
|
|
|
|
## JWildfire / FARender Visual Reference
|
|
|
|
Aquarium now has a reproducible higher-quality visual reference lane that does
|
|
not depend on tools already installed on the workstation:
|
|
|
|
```powershell
|
|
.\scripts\install-flame-reference-tools.ps1
|
|
.\scripts\fractal-flame-jwildfire-reference.ps1 `
|
|
-Renderer FARender `
|
|
-Width 1024 `
|
|
-Height 1024 `
|
|
-Quality 2000 `
|
|
-NoDensityEstimation
|
|
```
|
|
|
|
`install-flame-reference-tools.ps1` downloads JWildfire 9.00 and a local
|
|
Temurin JRE into ignored `artifacts/reference-tools`. These are reference
|
|
artifacts, not Aquarium runtime dependencies.
|
|
|
|
`fractal-flame-jwildfire-reference.ps1` prefers JWildfire's bundled FARender
|
|
GPU path when available because it accepts an explicit output file and fails
|
|
the script when no image is produced. The Java batch renderer remains available
|
|
through `-Renderer JWildfireJava`, but local smoke showed that it can complete
|
|
without writing an image into the batch directory because JWildfire's output
|
|
folder is preference-driven. Silent reference success is not a reference.
|
|
|
|
Local GTX 1070 smoke receipt from 2026-05-20:
|
|
|
|
- renderer: JWildfire 9.00 bundled FARenderJWF
|
|
- fixture: `artifacts/reference-tools/j-wildfire-9.00/lib/FARenderJWF/selftest.flame`
|
|
- command: `1024x1024`, quality `2000`, CUDA, no density-estimation phase
|
|
- time: `7.69 sec`
|
|
- artifact:
|
|
`artifacts/fractal-flame-jwildfire-reference/selftest-farender-20260520-233638.png`
|
|
|
|
The repository's tiny `linear-spherical-bubble.flame` fixture is still useful
|
|
for Aquarium subset parity, but FARender crashed on it because the fixture is
|
|
Apophysis-style minimal XML rather than full JWildfire `variationGroup` flame
|
|
XML. Do not "fix" that by making the fixture bigger until the parser and DSL
|
|
contract deliberately support the richer reference dialect.
|
|
|
|
## External Density Oracle
|
|
|
|
Aquarium's CPU flame oracle is no longer the only visual-parity target. The GPU
|
|
receipt can compare Aquarium splat density against a binary PPM image rendered
|
|
by an external renderer:
|
|
|
|
```powershell
|
|
.\scripts\new-jwildfire-baby-fixture.ps1
|
|
.\scripts\fractal-flame-jwildfire-reference.ps1 `
|
|
-Renderer FARender `
|
|
-Flame artifacts\fractal-flame-jwildfire-reference\julian-disc-baby.flame `
|
|
-Width 768 `
|
|
-Height 768 `
|
|
-Quality 2000 `
|
|
-OutputDirectory artifacts\fractal-flame-jwildfire-reference
|
|
magick artifacts\fractal-flame-jwildfire-reference\julian-disc-baby-farender-*.png `
|
|
-depth 8 artifacts\fractal-flame-jwildfire-reference\julian-disc-baby-farender.ppm
|
|
.\scripts\fractal-splat-receipt.ps1 `
|
|
-ProgramFlame artifacts\fractal-flame-jwildfire-reference\julian-disc-baby.flame `
|
|
-Splats 2000000 `
|
|
-WarmupSplatUpdates 2000000 `
|
|
-SplatUpdates 50000 `
|
|
-Warmup 8 `
|
|
-Frames 10 `
|
|
-Depth 8 `
|
|
-ReservoirUpdates 15000 `
|
|
-ReadbackSplats 1000000 `
|
|
-ReferenceDensityPpm artifacts\fractal-flame-jwildfire-reference\julian-disc-baby-farender.ppm `
|
|
-HistogramSize 256x256 `
|
|
-HistogramBounds '-2,-2,2,2'
|
|
```
|
|
|
|
`new-jwildfire-baby-fixture.ps1` derives a smaller, reproducible target from
|
|
FARender's bundled `selftest.flame`, preserving the boilerplate FARender needs
|
|
while reducing the nastier variation pressure: `jwf_julian_power` drops from
|
|
`126` to `8`, `jwf_gaussian_blur` becomes `0`, and the `wfield_*` amount fields
|
|
are set to zero. This is not a pristine flame design. It is a compatible rung
|
|
on the ladder that FARender actually renders.
|
|
|
|
First external-density receipt, local GTX 1070, 2026-05-21:
|
|
|
|
- reference renderer: JWildfire 9.00 bundled FARenderJWF
|
|
- fixture: `artifacts/fractal-flame-jwildfire-reference/julian-disc-baby.flame`
|
|
- reference render: `768x768`, quality `2000`, CUDA
|
|
- Aquarium steady GPU time: `1.145 ms/frame`, `873.4 FPS` equivalent
|
|
- external density score: `15.33%` in `256x256` bounds `-2,-2,2,2`
|
|
- score sweep at `128x128`: best tested bound was `-4,-4,4,4` at `32.95%`
|
|
- contact sheet:
|
|
`artifacts/fractal-flame-jwildfire-reference/julian-disc-baby-external-contact.png`
|
|
|
|
The low external score is useful. Aquarium still scores about `89.94%` against
|
|
its CPU interpretation of the same baby fixture in `128x128` bounds
|
|
`-4,-4,4,4`, while FARender density disagrees hard. That means the next work is
|
|
not "more samples"; it is dialect and camera semantics: FARender flame camera
|
|
mapping, color/density filtering, chaos/xaos and weighting-field behavior, and
|
|
variation exactness.
|
|
|
|
The first post-oracle correction found two concrete import/math lies:
|
|
|
|
- JWildfire `coefs` are packed for FARender's affine as `a b d e c f`; Aquarium
|
|
had transposed the off-diagonal terms as `a d / b e`.
|
|
- FARender's `jwf_disc` source uses `atan2(x, y) / PI`; Aquarium used
|
|
`atan2(y, x) / PI`.
|
|
|
|
After fixing both, the same external-density receipt scored `26.87%` at
|
|
`256x256` bounds `-2,-2,2,2`, up from `15.33%`.
|
|
|
|
The parity candidate no longer bins raw splat occupancy. That was the wrong
|
|
organ. The GPU receipt now builds the Aquarium comparison image from the SDF
|
|
envelope reservoir layer by stamping each populated envelope into a shaped
|
|
density histogram. PBR and radiosity reservoirs stay separate; PBR will score
|
|
surface appearance later, and radiosity is not part of this flame-density gate
|
|
yet. With full SDF reservoir coverage on the local GTX 1070, the same
|
|
`julian-disc-baby` receipt scored `22.14%` against the FARender PPM at
|
|
`256x256` bounds `-2,-2,2,2`, with `42.053 ms/frame` while refreshing all
|
|
2,000,000 reservoir rows in all three layers. The contact sheet lives at
|
|
`artifacts/fractal-flame-jwildfire-reference/julian-disc-baby-sdf-density-contact.png`.
|
|
|
|
The next correction removed another dialect lie. JWildfire's
|
|
`variationGroup normal="1"` is not the same thing as an explicit `linear`
|
|
variation in FARender. A normal-only diagnostic renders as a constant point in
|
|
FARender, while Aquarium had been adding affine linear geometry. The importer
|
|
now ignores JWildfire `normal` as variation-group metadata; explicit `linear`
|
|
still stays linear for Apophysis/FLAM3-style fixtures. The SDF-density receipt
|
|
also gives subpixel envelopes a minimum one-bin reconstruction footprint so the
|
|
comparison does not quietly collapse back into point occupancy.
|
|
|
|
After those fixes, the `julian-disc-baby` receipt scored `31.36%` against the
|
|
FARender PPM at `256x256` bounds `-2,-2,2,2`, with `41.692 ms/frame` while
|
|
refreshing all 2,000,000 reservoir rows in all three layers. Switching the
|
|
receipt to FARender-style flame camera bounds (`size / scale / cam_zoom`) raised
|
|
the same gate to `43.13%` with `68.01%` occupancy overlap; pass
|
|
`-HistogramBounds flame-camera` through `scripts/fractal-splat-receipt.ps1` for
|
|
that path. The contact sheet lives at
|
|
`artifacts/fractal-flame-jwildfire-reference/julian-disc-baby-normal-cut-area-contact.png`.
|
|
|
|
Full reservoir refreshes must not use the stochastic hash indexer. When
|
|
`ReservoirUpdatesPerPass == SplatCount`, the compute shader now updates row
|
|
`updateIndex` directly for each reservoir layer. That keeps under-budget passes
|
|
stochastic while making full coverage actually full and memory-coherent. The
|
|
same `julian-disc-baby` full-refresh receipt stayed visually equivalent
|
|
(`43.09%`, `67.99%` occupancy overlap) but dropped from roughly `41 ms/frame`
|
|
to `7.508 ms/frame`, or `133.2 FPS` equivalent.
|
|
|
|
The remaining visible mismatch is now narrower: Aquarium draws the same family
|
|
of form, but its density is still too compact and differently weighted. Fixing
|
|
that belongs in exact JWildfire variation/camera parity, target density
|
|
weighting, and view-conditioned reservoir feedback.
|
|
The next oracle step is to compare Aquarium through a matching density image
|
|
resolve, not raw point occupancy.
|
|
|
|
## JWildfire Selftest Faceplant
|
|
|
|
Running JWildfire's bundled `selftest.flame` through Aquarium is the correct
|
|
kind of embarrassing:
|
|
|
|
```powershell
|
|
.\scripts\fractal-splat-receipt.ps1 `
|
|
-ProgramFlame artifacts\reference-tools\j-wildfire-9.00\lib\FARenderJWF\selftest.flame `
|
|
-Splats 2000000 `
|
|
-SplatUpdates 50000 `
|
|
-Warmup 0 `
|
|
-Frames 1 `
|
|
-Depth 64 `
|
|
-ReservoirUpdates 15000 `
|
|
-ReadbackSplats 250000 `
|
|
-VisualParity `
|
|
-VisualParityReferenceSamples 1000000 `
|
|
-HistogramSize 256x256 `
|
|
-HistogramBounds '-128,-128,128,128'
|
|
```
|
|
|
|
First receipt after adding the JWildfire `variationGroup`/coefficient-layout
|
|
slice and the `disc`, `julian`, and `gaussian_blur` variation subset, before
|
|
lowering `post`:
|
|
|
|
- GPU time: `66.446 ms/frame`, `15.0 FPS` equivalent
|
|
- visual score: `5.01%`
|
|
- occupancy overlap: `3.04%`
|
|
- L1 distance: `1.899840`
|
|
- artifact:
|
|
`artifacts/fractal-jwildfire-faceplant/jwildfire-vs-aquarium-expanded.png`
|
|
|
|
The first unexpanded run scored `0.00%` and rendered a single dot. The expanded
|
|
run produces a recognizable central flame knot, but it still does not match the
|
|
reference. This is not just "more variations needed." It exposes three distinct
|
|
gaps:
|
|
|
|
1. **DSL expressiveness.** A serious flame dialect needs named variation
|
|
nodes, parameters, post transforms, palette/color semantics, xaos/chaos
|
|
weights, and optional weighting fields. The current Aquarium flame subset is
|
|
a compact test dialect, not a full JWildfire import surface.
|
|
2. **Translation quality.** JWildfire's coefficient layout differs from the
|
|
older Apophysis-style fixtures. `variationGroup` can hide the real variation
|
|
list from the xform attributes. Rich dialect import needs a translation
|
|
report that says exactly which fields were accepted, approximated, ignored,
|
|
or rejected.
|
|
3. **Hot-path shape.** Brute replaying 64 flame iterations per splat in one
|
|
compute invocation is too slow. Rich flame rendering should store continuing
|
|
iteration state in GPU reservoirs and advance it stochastically across
|
|
frames, then density-estimate/shade from those resident samples. The reservoir
|
|
is the renderer, not an expensive reset button.
|
|
|
|
The next coherent slice is therefore not "add 200 variations." It is an
|
|
import/lowering contract: parse a richer flame AST, produce an explicit
|
|
translation report, lower supported variation nodes into compact GPU op rows,
|
|
and carry persistent per-sample iteration state so zoom/movement can reuse
|
|
sample history instead of starting every splat from zero.
|
|
|
|
That import report now exists. GPU and histogram flame receipts emit a sibling
|
|
`fractal-flame-import-report-*.json` next to the numeric receipt. For the
|
|
JWildfire selftest flame, the first report after `post` lowering says:
|
|
|
|
- accepted fields: `15`
|
|
- approximated fields: `3`
|
|
- ignored fields: `45`
|
|
- rejected fields: `11`
|
|
- rejected geometry/sampling losses: `wfield_*` weighting-field attributes on
|
|
the disc xform
|
|
|
|
This report is the next expansion gate. A flame may parse successfully and
|
|
still be a bad import; unsupported fields must stay visible until the DSL,
|
|
compiler, or renderer can actually own them.
|
|
|
|
Lowering `post` required expanding only the small authored-program transform
|
|
row, not the resident million-splat or reservoir packets. The closer
|
|
CPU/GPU-slice receipt now gives:
|
|
|
|
- bounds `-8,-8,8,8`: `84.19%` GPU-vs-Aquarium-CPU slice score at
|
|
`129.073 ms/frame`
|
|
- bounds `-128,-128,128,128`: `88.01%` GPU-vs-Aquarium-CPU slice score at
|
|
`129.404 ms/frame`
|
|
- artifact:
|
|
`artifacts/fractal-jwildfire-faceplant/jwildfire-vs-aquarium-post-close.png`
|
|
|
|
This is not external JWildfire visual parity. It says Aquarium's GPU path is
|
|
now much closer to Aquarium's CPU interpretation of the supported slice. The
|
|
image is still missing JWildfire's palette, density estimation, weighting-field
|
|
behavior, render filters, and likely more dialect detail. The speed is also
|
|
unacceptable for realtime because depth-64 replay is the wrong execution model.
|
|
|
|
The first persistent-iteration invariant is now tested on CPU:
|
|
`FractalFlameIterationState` plus `FractalFlameIterationStepper` prove that
|
|
advancing one sample by 64 iterations in one call matches advancing the same
|
|
state in eight 8-iteration chunks. This is not a CPU renderer. It is the
|
|
mockable contract the GPU state buffer must preserve when the shader stops
|
|
replaying every splat from zero.
|
|
|
|
## GPU Flame Receipt
|
|
|
|
The flame fixture can now drive the D3D12 reservoir receipt directly:
|
|
|
|
```powershell
|
|
.\scripts\fractal-splat-receipt.ps1 `
|
|
-Splats 2000000 `
|
|
-SplatUpdates 50000 `
|
|
-Warmup 10 `
|
|
-Frames 60 `
|
|
-Depth 8 `
|
|
-ReservoirUpdates 15000 `
|
|
-ProgramFlame tests\Aquarium.Engine.Fractal.Tests\Fixtures\Apophysis\linear-spherical-bubble.flame
|
|
```
|
|
|
|
Local GTX 1070 receipt from 2026-05-20:
|
|
|
|
- resident splats: `2,000,000`
|
|
- resident reservoir rows: `2,000,000` each for SDF, PBR, and radiosity
|
|
- program transforms: `3`
|
|
- splat updates/frame: `50,000`
|
|
- reservoir updates/pass/frame: `15,000`
|
|
- GPU time: `1.030 ms/frame`
|
|
- equivalent FPS: `971.3`
|
|
- readback checksum: `0x2647D8B08575B848`
|
|
|
|
The flame hot path now keeps per-splat iteration state in a GPU UAV instead of
|
|
replaying every chaos-game ancestry from the origin. `Depth` is the number of
|
|
iterations advanced when a row is touched; the row stores point, support,
|
|
material, random state, and accumulated step for the next stochastic update.
|
|
|
|
This makes the receipt a two-phase cache:
|
|
|
|
- acquisition: seed every resident row and optionally advance the full resident
|
|
population for a few warmup frames
|
|
- maintenance: update only the budgeted stochastic subset while the layered
|
|
SDF/PBR/radiosity reservoirs continue their independent passes
|
|
|
|
```powershell
|
|
.\scripts\fractal-splat-receipt.ps1 `
|
|
-ProgramFlame artifacts\reference-tools\j-wildfire-9.00\lib\FARenderJWF\selftest.flame `
|
|
-Splats 2000000 `
|
|
-WarmupSplatUpdates 2000000 `
|
|
-SplatUpdates 50000 `
|
|
-Warmup 8 `
|
|
-Frames 20 `
|
|
-Depth 8 `
|
|
-ReservoirUpdates 15000 `
|
|
-ReadbackSplats 250000 `
|
|
-VisualParity `
|
|
-VisualParityReferenceSamples 1000000 `
|
|
-HistogramSize 256x256 `
|
|
-HistogramBounds -8,-8,8,8 `
|
|
-VisualParityImageDirectory artifacts\fractal-jwildfire-faceplant\source `
|
|
-VisualParityImagePrefix selftest-persistent-warmfull-depth008-frame-020
|
|
```
|
|
|
|
Local GTX 1070 receipt from 2026-05-21:
|
|
|
|
- resident splats: `2,000,000`
|
|
- flame iteration states: `2,000,000` at `32` bytes each
|
|
- resident reservoir rows: `2,000,000` each for SDF, PBR, and radiosity
|
|
- warmup splat updates/frame: `2,000,000`
|
|
- steady splat updates/frame: `50,000`
|
|
- reservoir updates/pass/frame: `15,000`
|
|
- measured steady GPU time: `1.011 ms/frame`
|
|
- equivalent steady FPS: `988.8`
|
|
- visual parity against Aquarium CPU slice oracle: `84.86%`
|
|
- cosine similarity: `0.986955`
|
|
- readback checksum: `0xC633FADC3BD7E5BE`
|
|
|
|
Without the full-population acquisition pass, the same steady budget scored only
|
|
`32.64%` on the JWildfire selftest slice because most rows were still shallow
|
|
ancestry. That is a useful failure: persistent state is not magic, it is a cache
|
|
that must be filled before a tiny maintenance budget can look clever.
|
|
|
|
## Visual Parity Receipt
|
|
|
|
The receipt tool can compare GPU flame splat positions against the local CPU
|
|
flame oracle:
|
|
|
|
```powershell
|
|
.\scripts\fractal-splat-receipt.ps1 `
|
|
-Splats 2000000 `
|
|
-SplatUpdates 50000 `
|
|
-Warmup 0 `
|
|
-Frames 20 `
|
|
-Depth 8 `
|
|
-ReservoirUpdates 15000 `
|
|
-ProgramFlame tests\Aquarium.Engine.Fractal.Tests\Fixtures\Apophysis\linear-spherical-bubble.flame `
|
|
-ReadbackSplats 250000 `
|
|
-VisualParity `
|
|
-VisualParityReferenceSamples 1000000 `
|
|
-HistogramSize 128x128 `
|
|
-HistogramBounds -8,-8,8,8
|
|
```
|
|
|
|
First local scores, GPU 250k readback samples against 1M CPU reference samples:
|
|
|
|
| Frames | GPU ms/frame | Distribution score | L1 distance | RMSE | Cosine |
|
|
| ---: | ---: | ---: | ---: | ---: | ---: |
|
|
| 1 | 15.645 | 84.05% | 0.319072 | 0.000090 | 0.995365 |
|
|
| 5 | 4.017 | 84.10% | 0.318042 | 0.000089 | 0.995402 |
|
|
| 20 | 1.738 | 84.20% | 0.316019 | 0.000088 | 0.995591 |
|
|
| 60 + 10 warmup | 0.952 | 84.24% | 0.315136 | 0.000088 | 0.995505 |
|
|
|
|
This proves the current GPU flame program has high distribution similarity, but
|
|
it does **not** prove learned reservoir convergence. The score plateaus because
|
|
the current flame path initializes resident splats from the program and then
|
|
performs stochastic replacement; it does not yet use visual error feedback,
|
|
MIS/ReSTIR weights, or an external rendered-image residual to steer updates.
|
|
|
|
## View-Conditioned Parity
|
|
|
|
Global parity is a weak metric. Realtime usefulness depends on the current
|
|
camera window: panning or zooming should keep the visible image smooth while the
|
|
cache spends new work on underrepresented regions.
|
|
|
|
The receipt tool therefore accepts named viewport bounds:
|
|
|
|
```powershell
|
|
.\scripts\fractal-splat-receipt.ps1 `
|
|
-Splats 2000000 `
|
|
-SplatUpdates 50000 `
|
|
-Warmup 10 `
|
|
-Frames 60 `
|
|
-Depth 16 `
|
|
-ReservoirUpdates 15000 `
|
|
-ProgramFlame tests\Aquarium.Engine.Fractal.Tests\Fixtures\Apophysis\linear-spherical-bubble.flame `
|
|
-ReadbackSplats 250000 `
|
|
-VisualParity `
|
|
-VisualParityReferenceSamples 1000000 `
|
|
-HistogramSize 128x128 `
|
|
-VisualParityView global:-8,-8,8,8 `
|
|
-VisualParityView mid:-1,-1,1,1 `
|
|
-VisualParityView close:-0.5,-0.5,0.5,0.5 `
|
|
-VisualParityView micro:-0.25,-0.25,0.25,0.25
|
|
```
|
|
|
|
First view-conditioned receipt:
|
|
|
|
| View | Distribution score | GPU hits | Reference hits | Starved bins | Underrepresented mass |
|
|
| --- | ---: | ---: | ---: | ---: | ---: |
|
|
| global | 97.09% | 249,972 | 999,836 | 1,034 | 2.91% |
|
|
| mid | 91.93% | 166,654 | 667,105 | 1,792 | 8.07% |
|
|
| close | 88.97% | 125,448 | 501,033 | 2,266 | 11.03% |
|
|
| micro | 82.05% | 71,348 | 284,011 | 2,656 | 17.95% |
|
|
|
|
This is the first metric that catches the real problem. The reservoir is fast,
|
|
but it is not yet view-adaptive. It needs camera-conditioned target weights,
|
|
underrepresented-bin feedback, and temporal reuse validation before it deserves
|
|
claims about maintaining flame quality during movement and zoom.
|
|
|
|
## Contact Sheets
|
|
|
|
The quick 1M-sample oracle image is too noisy for visual judgment. Use the image
|
|
export path with a much larger CPU oracle when making contact sheets:
|
|
|
|
```powershell
|
|
.\scripts\fractal-splat-receipt.ps1 `
|
|
-ProgramFlame tests\Aquarium.Engine.Fractal.Tests\Fixtures\Apophysis\linear-spherical-bubble.flame `
|
|
-VisualParity `
|
|
-VisualParityReferenceSamples 20000000 `
|
|
-VisualParityImageDirectory artifacts\fractal-contact-sheet-hq\source `
|
|
-VisualParityImagePrefix zoom-frame-010 `
|
|
-ReadbackSplats 250000 `
|
|
-Depth 16 `
|
|
-Frames 10 `
|
|
-VisualParityView zoom:-0.25,-0.25,0.25,0.25
|
|
```
|
|
|
|
The current high-reference contact sheet lives at:
|
|
|
|
- `artifacts/fractal-contact-sheet-hq/flame-parity-contact-sheet-hq.png`
|
|
|
|
It uses 20M CPU oracle samples and 250k Aquarium readback samples per panel.
|
|
This still is not a full FLAM3 density-estimated render, but it is no longer a
|
|
1M-sample noise patch masquerading as ground truth.
|
|
|
|
## Sources
|
|
|
|
- Apophysis 7x repository: https://github.com/wanily/apophysis7x
|
|
- FLAM3 repository and variation list: https://github.com/scottdraves/flam3
|
|
- Draves, *The Fractal Flame Algorithm*: https://flam3.com/flame_draves.pdf
|
|
|
|
## Reference Ladder
|
|
|
|
Stop calling Aquarium's CPU histogram the visual ground truth. It is a math
|
|
oracle for a tiny variation subset. It is useful because it is deterministic,
|
|
small, and debuggable. It is not the bar for beautiful flame rendering.
|
|
|
|
Use this ladder:
|
|
|
|
1. **Gold visual reference: Chaotica.** Chaotica is the quality target for
|
|
flame art. Its own manual frames progressive rendering as the alternative
|
|
to Apophysis/FLAM3 fixed-quality rerendering, and says high quality often
|
|
means stopping at a sampling level after the image is no longer noisy. Use
|
|
Chaotica renders as imported reference artifacts when available.
|
|
2. **Scriptable visual reference: JWildfire/FARender.** JWildfire is LGPL,
|
|
has a documented headless batch renderer path:
|
|
`java org.jwildfire.create.tina.batch.HeadlessBatchRendererController <dir> <w> <h> <quality>`.
|
|
Its current public bundle also ships FARenderJWF, a Windows/NVIDIA GPU
|
|
renderer that the JWildfire changelog says may still be used while it works.
|
|
Use `scripts/install-flame-reference-tools.ps1` to hydrate the local tools
|
|
and `scripts/fractal-flame-jwildfire-reference.ps1 -Renderer FARender` for
|
|
explicit PNG output.
|
|
3. **Baseline renderer reference: FLAM3/Apophysis family.** Use FLAM3 for
|
|
portable `.flame`/`.flam3` compatibility, deterministic external receipts,
|
|
and old-school density-estimation sanity.
|
|
4. **Aquarium CPU oracle.** Use only for unit tests, point parity, histogram
|
|
smoke checks, and shader debugging. Never present it as market-quality
|
|
visual ground truth.
|
|
|
|
Local machine status on 2026-05-20: Chaotica and `flam3-render` were not found
|
|
on PATH; only Ultra Fractal 6 was visible under Program Files. JWildfire 9.00
|
|
and Temurin JRE 17 were downloaded into ignored artifacts, and FARenderJWF
|
|
successfully rendered its bundled selftest flame on the GTX 1070. Aquarium can
|
|
now compare against a real external flame renderer artifact, but it still
|
|
cannot claim best-in-market visual parity until imported Chaotica renders or a
|
|
fully matched JWildfire flame dialect are wired into residual metrics.
|
|
|
|
Additional sources:
|
|
|
|
- Chaotica features: https://chaoticafractals.com/features
|
|
- Chaotica progressive rendering: https://www.chaoticafractals.com/manual/rendering/progressive_rendering
|
|
- Chaotica render settings / sampling level: https://www.chaoticafractals.com/manual/user_interface/render_settings
|
|
- JWildfire GitHub / LGPL license: https://github.com/thargor6/JWildfire
|
|
- JWildfire headless batch renderer notes: https://www.jwfsanctuary.club/tutorial/how-to/how-do-i-render-a-full-folder-of-flames/
|
|
- JWildfire bundled FARender/Swan notes: `artifacts/reference-tools/j-wildfire-9.00/CHANGES.txt`
|