-
Notifications
You must be signed in to change notification settings - Fork 0
AcidWarp Mode Design
Tracking document for adding an Acid Warp mode to Fracturing Fog, plus six "ideas to steal" harvested from the Acid Warp source that FF does not already do. Acid Warp is the DOS palette-cycling demo by Noah Spurrier (1992), later ported to SDL/Emscripten by Boris Gjenero (dreamlayers).
Reference source: https://github.com/dreamlayers/acidwarp (gen_img.c,
palinit.c, rolnfade.c, acidwarp.c).
Status legend: ☐ not started · ◐ in progress · ☑ shipped
Design R&D provenance. This plan was produced design-only. No engine code was written when it was filed — only this doc and the tracking issues.
Branch feat/acidwarp-mode (off main, not pushed). Full suite 1111/1111.
| Issue | State | Notes |
|---|---|---|
| #247 AW-1 calculator | ☑ shipped | 20 clean-room patterns; 7 tests |
| #248 AW-2 wiring + params UI | ☑ shipped | full FractalType wiring + picker + region |
| #249 IDEA-1 live palette cycling | ☑ shipped | LUT rotation + "Cycle" toolbar toggle |
| #250 AW-4 Spurrier intro | ☑ shipped | once-per-process gate |
| #251 IDEA-6 auto-VJ | ☑ shipped | shuffle+classic-first playlist + AcidWarpAmbientDirector (hold/lock/pause/next) wired into MainViewModel with fade-to-black advance; toolbar Auto-VJ / Lock / Next (Acid Fog only); awaits on-device visual sign-off |
| #252 IDEA-2 XOR post-transform | ☑ shipped | colour-index moiré on any field |
| #253 IDEA-3 domain-warp | ☑ shipped | warp inside Acid Warp + cross-fractal warp on the EscapeTimeCalculator family (Julia/Burning Ship/Tricorn/Multibrot/Magnet/Glynn/Phoenix/Spider), toggle + strength + frequency, animatable; Mandelbrot's dedicated deep-zoom SIMD calc intentionally excluded |
| #254 IDEA-4 sparkle | ☑ shipped | every-Nth LUT boost |
| #255 IDEA-5 seamless toggle | ☑ shipped | opt-in close-the-loop |
Remaining app-integration work (wants on-device visual verification): the
auto-VJ ambient loop (#251) is wired (MainViewModel ambient loop +
AcidWarpAmbientDirector), pending only on-device visual sign-off of the fade;
and the cross-fractal domain warp (#253) — shipped on the EscapeTimeCalculator
family (see below) — wants a visual pass on the swirl.
-
Fracturing Fog is
AGPL-3.0-or-later(see the SPDX header on every engine source file, e.g.Engine/Calculators/PlasmaCalculator.cs:1). - Acidwarp is GPL-licensed. A GPL-2.0-only upstream is not license- compatible with AGPL-3. Do not copy acidwarp source, lookup tables, or palette data into this repo.
- Reimplement from the mathematics. The pattern equations (distance, angle, sine interference, XOR fields) are not copyrightable — express them fresh in C#. This is a clean-room port of the ideas, not the code.
- Attribution. Credit Noah Spurrier (original concept, 1992) and Boris Gjenero (modern port) in the About box / mode help text. This is courtesy, not a license obligation, once the code is clean-room.
Acid Warp is two mechanisms, and FF already has both surfaces:
| Acid Warp mechanism | FF equivalent that already exists |
|---|---|
| Closed-form per-pixel function → 8-bit palette index | Scalar field → IColorMap LUT (Engine/Models/ColorUtils.cs) |
| Animate by rotating the color LUT (no re-render) |
ColorOffset phase term already in the LUT sample (ColorUtils.cs:130, applied at :288 raw = smooth * cycleSpeed * ColorDensity + ColorOffset) |
| Non-fractal procedural field, pan/zoom is a no-op |
Plasma already ships exactly this shape (FractalType.Plasma, PlasmaCalculator — "the generated field IS the image", Enums.cs:167) |
So the mode is: a new procedural field calculator (sibling of
PlasmaCalculator) feeding FF's existing color pipeline, animated by
time-driving the existing ColorOffset. No new render backend. No new
color pipeline.
Key structural facts (from ColorTheme-Enhancement-Roadmap.md §1):
- The LUT is built once per theme instance (256 entries). Rotating the
phase (
ColorOffset) is free at render time — it does not rebuild the LUT. - Acid Warp's "cheap" claim holds in FF: compute the field once, then only the per-frame recolor runs. FF already has a cheap-recolor precedent (Buddhabrot #194/#197).
-
What. New
IFractalCalculatormirroringPlasmaCalculator(one-shot fillColorBuffer,SupportsZoom = false). Selects one of ~40 closed-form patterns and writes a normalized[0,1)field sampled through the activeIColorMap. -
Pattern set (clean-room from the equations).
dx = x - cx,dy = y - cy;dist = hypot(dx,dy),angle = atan2(dy,dx):- Radial:
dist;sin(dist*4);dist + sin(dist*4). - Angular:
angle;sin(angle*7). - Spiral:
angle + sin(dist)and cosine-x/cosine-y blends. - Multi-center "peacock": sum of
sin(dist_k)from 1–3 offset centers. - Wave interference: sums of multi-frequency
sin/cosin x and y. - Bitwise:
xor(angle, dist),xor(dx, dy)(see IDEA-2). - Stochastic: neighbor-blended jitter fields.
- Modern FF computes these in float directly — skip the DOS integer LUTs
(
lut_sin/lut_dist/lut_angle).
- Radial:
-
Output range. Normalize to
[0,1)and wrap (acidwarp reserves index 0 and uses 1..255; FF's LUT is 0..1 float, so wrap withx - floor(x)). -
Injection. New file
Engine/Calculators/AcidWarpCalculator.cs. Pattern id- tunables (
AcidWarpPattern, center offsets, frequencies) asFractalParametersfields (per the tunable-params convention).
- tunables (
-
Test. Golden-hash a handful of patterns at a fixed size; assert field is
in
[0,1)and deterministic per pattern id. - Depends on: nothing.
- Add
AcidWarptoAbstractions/Models/Enums.cs; register the calculator on the same pathPlasmauses (FractalRenderHostselection). - Expose pattern picker + tunables in
FractalParamsView. - Add to
Server/Guard/FractalTypeAllowlist.cs(defense-in-depth). - ASCII-only names (per the no-Unicode-names rule).
- Depends on: AW-1.
- See §3 IDEA-1. This is Acid Warp's core and also a standalone win on every fractal. The mode requires it; ship it general.
- Depends on: nothing (but the mode leans on it).
- Requirement (from the user). The first time Acid Warp mode is entered in a given app launch, show Noah Spurrier's original into-screen before the shuffle begins.
-
Design. Process-scoped
static bool _acidWarpIntroShown(lifetime = the process; not persisted to disk, not per-mode-entry). First entry → force the canonical original pattern + its palette as playlist item 0 (acidwarp'sDRAW_LOGO/ "logo only fades to black"), then hand off to the normal shuffle (AW-5). Reimplement that specific startup pattern + palette from the DOS math. -
Naming. ASCII, e.g.
"Acid Warp Classic (Spurrier 1992)". - Test. Two mode-entries in one process → first yields the classic id, second yields a shuffled id. New process resets.
- Depends on: AW-1, AW-2.
- See §3 IDEA-6. Shuffle playlist + timed auto-advance + fade-to-black crossfade + lock-field/cycle-color. Reuses slideshow + Scene transitions.
- Depends on: AW-2, AW-3.
Each spec: what · surfaces · algorithm · injection · back-compat · test. These are ranked by artful payoff. IDEA-1 and IDEA-6 double as mode slices AW-3 / AW-5; IDEA-2..5 are independently useful on the existing fractals.
- What. FF's entire motion vocabulary today animates geometry (zoom / pan / scene camera). Acid Warp's signature is the opposite: hold the frame, move the palette. Make time-driven palette cycling a first-class, video/slideshow-exportable effect on any fractal.
-
Surfaces.
CyclingGradientColorMap/GradientColorMapphase term; Scene Engine param-animation; video exporter; slideshow. -
Algorithm. Drive
ColorOffset(ColorUtils.cs:130, consumed at:288) from wall-clock:ColorOffset += rate * dt, wrapped byCycleWrap. Field / fractal computed once; only the recolor runs per frame → near-free. -
Injection. An animation driver that ticks
ColorOffset(Scene Engine track or a dedicated "palette cycle" clock). Wire into export so the effect bakes into MP4 / slideshow. - Back-compat. Rate default 0 = current static behavior. Opt-in.
- Test. Frame N and N+period render identically when the period aligns to the LUT length; recolor path does not re-invoke the calculator.
-
What. Acid Warp's
xor(angle,dist)/xor(dx,dy)produce plaid / moiré fields with a demoscene / 8-bit aesthetic FF has no analog for. - Surfaces. AW pattern set (AW-1) and an optional color-index post-transform usable on any field.
-
Algorithm. Quantize the field (or coordinates) to integers, XOR, renormalize
to
[0,1). As a post-transform:t' = ((floor(t*N)) ^ mask) / N. -
Injection. Pattern cases in
AcidWarpCalculator; optional transform hook ahead of the LUT sample. - Back-compat. Off by default; only active for AW patterns / when the transform is selected.
- Test. Golden-hash a known XOR pattern; verify bit-exactness across runs.
-
What. Sum of
sin(dist_k)from N offset centers = coherent wave interference ("peacock"). FF'sPlasmais incoherent noise — this is a different, orderly beauty. Bonus: reuse the same field as a domain-warp layer that displaces the sampling coordinates of an existing fractal. - Surfaces. AW pattern set (AW-1); optional pre-sample coordinate warp on the 2D fractal path.
-
Algorithm.
f(x,y) = Σ_k A_k · sin(ω_k · |p - c_k| + φ_k); as a warp, offset(x,y)byε·∇fbefore the fractal samples. - Injection. Pattern cases; a warp stage guarded behind a toggle + strength.
- Back-compat. Warp strength default 0 = no-op.
- Test. Zero strength ⇒ byte-identical to the un-warped fractal.
-
Shipped. Multi-centre "peacock" patterns live in
AcidWarpCalculator(cases 8/9). The cross-fractal warp isFractalDomainWarp.Apply— the same two-tap sine field lifted to a shared helper — injected as a pre-sample coordinate stage inEscapeTimeCalculator's scalar cores (Julia, Burning Ship, Tricorn, Multibrot, Magnet 1/2, Glynn, Phoenix, Spider). TunablesDomainWarpEnabled/DomainWarpStrength/DomainWarpFrequencyonFractalParameters;DomainWarpStrengthis animatable (breathing swirl). An active warp forces the scalar path (SIMD builds onecyper row, which a per-pixel warp breaks) and skips GPU; it is gated belowEscapeTimeCalculator.MaxWarpZoom(1e6). Mandelbrot is excluded — it runs on the dedicated deep-zoom SIMD/perturbation calculator, and warping that vectorised path is out of scope (the original deep-zoom deferral). Off / strength 0 stays byte-identical (FractalDomainWarpTests).
-
What. Acid Warp's
add_sparkles_to_palettebrightens every Nth palette entry → cheap glitter / lightning. FF has no such modifier. -
Surfaces.
GradientColorMap.BuildLut()(bake into the 256-entry LUT — free at render time). -
Algorithm. For every Nth LUT entry,
rgb = min(rgb + boost, 1);Nandboosttunable. Optionally phase-shift the sparkle set over time (rides IDEA-1) for twinkle. -
Injection. A post-step in
BuildLut; new nullableColorThemeDatafields (SparkleStride,SparkleBoost) → back-compat by nullability. - Back-compat. Absent / 0 = no sparkle. Hooks the Random theme generator (#83) as an optional experimental knob.
- Test. Stride 0 ⇒ LUT unchanged; stride N ⇒ exactly ⌈256/N⌉ entries lifted.
-
What. Acid Warp palettes are built to tile with no visible seam when
the phase rotates. FF has
CycleWrapbut the Random theme generator (#83) does not guarantee first-stop ≈ last-stop, so cycling can flash a seam. -
Creative-choice requirement (from the user). Ship this as an opt-in
toggle, not a forced rule — a hard seam is sometimes the desired look.
- Toggle ON ("Seamless cycling") → force / nudge the palette so
stop[0] ≈ stop[last](close the loop), so IDEA-1 cycling never seams. - Toggle OFF (default = today's behavior) → palette left as authored; seams allowed.
- Toggle ON ("Seamless cycling") → force / nudge the palette so
-
Surfaces.
ColorThemeData(new nullableSeamlessCyclebool); Random theme generator (#83); Color Theme Editor UI checkbox. -
Algorithm. When ON, append/adjust a terminal stop equal to the first (or
blend the two ends within a tolerance) before
BuildLut. - Back-compat. Null / false = unchanged. Purely additive.
-
Test. ON ⇒
|LUT[0] - LUT[255]|within tolerance; OFF ⇒ LUT identical to today.
-
What. Acid Warp's signature loop = lock geometry, cycle color,
auto-advance on a timer with a fade-to-black crossfade, drawing from a
shuffled, non-repeating playlist (
makeShuffledList). FF's slideshow advances frames; it has no "hold one image, cycle its palette, then fade to the next" ambient mode. - Surfaces. Slideshow engine; Scene Engine transitions (fade); IDEA-1 for the color motion.
-
Algorithm. Fisher–Yates shuffle over the pattern/preset pool → play in
order (no repeats until exhausted); per item: hold + palette-cycle for
image_time, then crossfade (fade-to-black) to the next. Controls: lock, pause, next. - Injection. New slideshow "Acid Warp / ambient" playback profile.
- Back-compat. New mode; existing slideshow untouched.
- Test. Shuffle emits every id once before repeating; auto-advance fires on the timer; lock freezes advancement while color keeps cycling.
AW-1 (calculator) ──┬─> AW-2 (type + UI) ──┬─> AW-4 (Spurrier intro)
│ └─> AW-5 / IDEA-6 (auto-VJ) ─┐
IDEA-1 / AW-3 (animate color) ─────────────────────────────────────────┘
IDEA-2 (xor fields) ── needs AW-1 for the pattern slot
IDEA-3 (interference/warp) ─ needs AW-1 for the pattern; warp layer standalone
IDEA-4 (sparkle pfx) ── standalone (hooks #83)
IDEA-5 (seamless toggle) ── standalone (hooks #83, pairs with IDEA-1)
Suggested order: AW-1 → IDEA-1/AW-3 → AW-2 → AW-5/IDEA-6 → AW-4, then the standalone color wins IDEA-4, IDEA-5 any time, IDEA-2 / IDEA-3 after AW-1.
Filed under AloneButUnsober/FracturingFog. Tracking issue:
#246.
Each slice/idea is one issue (per the repo's issue-first convention).
| Item | Issue |
|---|---|
| Tracking | #246 |
| AW-1 calculator | #247 |
| AW-2 type + UI | #248 |
| AW-3 / IDEA-1 animate color | #249 |
| AW-4 Spurrier intro | #250 |
| AW-5 / IDEA-6 auto-VJ | #251 |
| IDEA-2 xor fields | #252 |
| IDEA-3 interference / warp | #253 |
| IDEA-4 sparkle pfx | #254 |
| IDEA-5 seamless toggle | #255 |
To auto-close at merge, each PR needs its own explicit Closes #N line per
issue — ranges / mentions don't count (see the repo's PR-issue-autoclose note).