-
Notifications
You must be signed in to change notification settings - Fork 0
UserBulb3D DevelopmentPlan
Companion pages: Technical Index · User Bulb Sandbox Dev Plan · Fractal Equation Design Guide · User-facing User Bulb Guide · Resources & Bibliography
Note
Snapshot 2026-06-11. Stages 3A (Roslyn → ILGPU lowering) and 3B (quaternion axis GPU JIT) are
live on feature/gpu-compute (commits b79c2d0, e462e4e). Stage 3C (sandbox interpreter perf)
and chain-mode GPU dispatch are next. Re-check sequencing in
UserBulbSandbox-DevPlan.md before resuming.
Drive agent execution of phased perf + creative work on the User Bulb 3D calculator. Phases run in listed order. Each phase ends in a single commit. Validate build + manual smoke render between phases.
- GPU backend: ILGPU (CPU+GPU JIT, .NET-native, cross-vendor).
- Param bank: arbitrary count. User adds/removes rows dynamically.
- Multi-equation: arbitrary chain. Each step has a named output. Later steps reference earlier outputs by name.
- Quaternion: unified with Vec3 path via axis-toggle (3D / 4D mode flag). No separate dialog.
-
Calculators/UserBulbCalculator.cs— main render loop. -
Calculators/MandelbulbCalculator.cs— reference for camera/light parity. -
Models/Vec3.cs— math helpers, expand surface area. -
Models/FractalParameters.cs— add new persisted fields + Clone() entries. -
Models/UserBulbStore.cs— preset persistence, extend schema. -
Views/UserBulbDialog.cs— UI editor, add new groups/tabs. -
MainForm.cs— viewport mouse-drag camera, render dispatch. -
VideoZoom.cs— animation export (re-hook time global in Phase 3.4).
- Commit per numbered task. Caveman commit messages via
/caveman-commit. - No emojis in code/UI.
- Preserve existing public API on
IFractalCalculator. - All hot-path math:
[MethodImpl(MethodImplOptions.AggressiveInlining)]. - All new
FractalParametersfields: add toClone()in same commit. - Default values must reproduce current visuals when feature is off/zero.
- Verify perf with run skill after each Phase 1 + Phase 2 task.
Single commit at phase end. Target: 2-3× speedup, no UI changes.
- File:
Calculators/UserBulbCalculator.cs:216-217, 225, 259, 283-284 - Delete
hits/totallocals + 2×Interlocked.Increment. - Wrap trailing
Debug.WriteLinein[Conditional("DEBUG")]helper or delete.
- File:
Calculators/UserBulbCalculator.cs:342-347 - Change sig:
private static (double X, double Y, double Z) Normalize3(double, double, double) - Add
[MethodImpl(MethodImplOptions.AggressiveInlining)]. - Update callsites at
:193-214, :230, :265. Use.X/.Y/.Znot[0]/[1]/[2].
- File:
Models/Vec3.cs:25-46 - Add
[MethodImpl(MethodImplOptions.AggressiveInlining)]to every operator,Length,LengthSquared,Dot,Cross,Sin/Cos/Sinh/Cosh/Exp/Abs,Normalized.
- File:
Calculators/UserBulbCalculator.cs:233-251 - Before raymarch loop: ray-vs-sphere(center=target, r=
UserBulbCullRadius). - Miss → write
InSetColor,continue. - Hit → advance
px,py,pztotEnter, settTotal=tEnter. - Add
FractalParameters.UserBulbCullRadius(default 2.5) + Clone() entry. - Expose in dialog "Render" group as
Cull r:numeric.
- File:
Calculators/UserBulbCalculator.cs:262-265 - Replace central diff with forward diff: 3 probes instead of 6.
- Cache
dBasefrom hit-step DE call; reuse asf(p)for(f(p+h)-f(p))/h.
- File:
Calculators/UserBulbCalculator.cs:318-330 - In
Compile(): after successful Roslyn compile, smoke-test by invokingfn(Vec3.Zero, new Vec3(0.5,0.5,0.5), 0)and verifying all componentsdouble.IsFinite. On throw or non-finite → setLastError, null out_compiled. - Remove try/catch inside
UserBulbDE. Addif (!double.IsFinite(r)) break;after ther = z.Lengthline.
Commit message: perf(userbulb): kill alloc/atomic/exception overhead in hot loop
Order: 2.1 → 2.2 → 2.3 → 2.4 → 2.6 → 2.5. Separate commit each.
- New file:
Calculators/UserBulbAnalyticDE.cs - Source-pattern detector (regex on
UserBulbSource):-
z*z + c→ square triplex DE:dr = 2*r*dr + 1 -
Vec3.Pow(z, N) + c→ Hubbard-Douady:dr = N*r^(N-1)*dr + 1 - Standard Mandelbulb triplex (sin/cos pattern) → power-N DE
-
- Return enum
AnalyticDEKind { None, Square, PowerN, Mandelbulb }+ params. - In
UserBulbCalculator.Calculate, branch on kind:- Non-None → single-trajectory DE (1× delegate call/iter)
- None → existing 4-trajectory numerical Jacobian
- Add
FractalParameters.UserBulbDEModeenum{ Auto, Analytic, Numerical }- Clone().
- UI: radio group in dialog "Render" section.
- Auto mode: at compile time, do 1 numerical probe at sample point, compare to analytic; if delta < 5%, mark eligible; else fall back to numerical.
Commit: perf(userbulb): analytic DE fast path for detected power maps
- File:
Calculators/UserBulbCalculator.cs - Add
bool LowResPreview { get; set; }property. - When true: render at W/2 × H/2, nearest-upscale to
ColorBuffer. - File:
MainForm.cs— find existing drag-state flag (used by Mandelbulb). During drag setLowResPreview=true; on mouse-up set false + re-render.
Commit: perf(userbulb): low-res preview during camera drag
- File:
Calculators/UserBulbCalculator.cs - New private:
ConeMarchTileMins(...)— for each 16×16 tile, march center ray withepsilon * tileRadius. Outputdouble[]tiletMincache. - Per-pixel raymarch initializes
tTotal = tileMins[tileIdx] * 0.95(5% safety margin),px,py,pzadvanced accordingly. - Skip tiles where
tMinexceeded raymarch limit (12.0).
Commit: perf(userbulb): cone-march prepass for empty-space skip
- File:
Calculators/UserBulbCalculator.cs:87-128 - Replace
CSharpScript.Create/RunAsyncwith:-
CSharpCompilation.Create→ emit assembly to memoryStream. - Load via
Assembly.Load(bytes), findStepmethod,Delegate.CreateDelegate.
-
- Preserve
LastErrorsurface (collectCompilation.GetDiagnostics()). - Bench before/after with a
Stopwatchperfn(...)call, log to debug.
Commit: perf(userbulb): replace Roslyn script wrapper with direct emit
- New file:
Calculators/UserBulbTemporalCache.cs - Store prior frame:
uint[] prevColor, prior camera basis (fwd/right/up + pos). - Each frame: per tile, reproject prior color via prior basis → screen NDC. Tiles within reprojection error budget reused; rest raymarched.
- Invalidate on:
- Source recompile
-
Qualitychange - Any
FractalParametersnon-camera field change - Camera rotation delta > 5° per axis
- Add toggle
FractalParameters.UserBulbTemporalReusedefault true.
Commit: perf(userbulb): temporal tile reuse across small camera deltas
- New file:
Calculators/UserBulbGpuCalculator.cs - NuGet:
ILGPUpackage. - New file:
Calculators/UserBulbIlgpuTranslator.cs— walks RoslynSyntaxTreeforUserBulbSource, emits ILGPU-compatible C# (no closures, no heap alloc, fixed-arity Vec3 struct). - Restrict supported user-source grammar (document allowed surface):
- Allowed: arithmetic,
Math.*,Vec3.*,if/else, ternary, local vars. - Disallowed:
newfor non-Vec3, loops, lambdas, attribute access on user-defined types.
- Allowed: arithmetic,
- Kernel:
(Index2D, ArrayView<uint>, RenderParams)→ writes color per pixel. - Add
FractalParameters.UserBulbBackendenum{ CPU, GPU }+ Clone(). - Dialog: combobox "Backend: CPU / GPU (experimental)".
- Fallback to CPU on translator failure; surface error in
LastErrorlabel. - Reuse
Acceleratorinstance across renders; dispose on calculator dispose.
Commit: feat(userbulb): ILGPU GPU compute backend for compatible sources
- File:
Models/Vec3.cs - Add statics (all inlined):
-
Pow(Vec3 v, double n)— triplex spherical power.r = v.Length; theta = atan2(v.Y, v.X); phi = asin(v.Z / r);- Return
r^n * (cos(n*phi)*cos(n*theta), cos(n*phi)*sin(n*theta), sin(n*phi)). - Guard
r < 1e-12→ return Zero.
-
Rot(Vec3 v, Vec3 axis, double angle)— Rodrigues formula. -
BoxFold(Vec3 v, double limit)— per-axisabs(x)>limit ? sign(x)*2*limit - x : x. -
SphereFold(Vec3 v, double rMin, double rMax)—r2 = v.LengthSquaredr2 < rMin² → v * (rMax²/rMin²)rMin² ≤ r2 < rMax² → v * (rMax²/r2)- Else
v.
-
AbsX/AbsY/AbsZ— selective per-axis abs. -
Mod(Vec3 v, double period)—v - period*floor(v/period + 0.5). -
SMin(double a, double b, double k)—-log(exp(-k*a) + exp(-k*b)) / k. -
ToSpherical(Vec3 v)→(r, theta, phi)tuple. -
FromSpherical(double r, double theta, double phi)→ Vec3.
-
Commit: feat(vec3): expand math lib for bulb formula authoring
- New file:
Models/Quat.csreadonly record struct Quat(double W, double X, double Y, double Z)- Ops:
+,-,*(Hamilton), scalar*,Length,LengthSquared,Conjugate,Dot. - Static:
FromVec3(Vec3 v, double w=0),ToVec3() => new Vec3(X,Y,Z).
- File:
Calculators/UserBulbCalculator.cs- Add
FractalParameters.UserBulbAxisModeenum{ Vec3, Quat }+ Clone(). - Add second compile path: when Quat mode, wrap as
Quat Step(Quat z, Quat c, int n). - DE in Quat mode: analytic
dq' = 2*q*dqfor square map; numerical Jacobian on the W/X/Y/Z perturbations for arbitrary. - Color/normal: project Quat → Vec3 via
.ToVec3()for raymarch position. - User code in Quat mode:
z.Waccessible;c4D too (4th coord = slice plane position).
- Add
- File:
Models/FractalParameters.cs-
public double UserBulbQuatSliceW { get; set; } = 0.0;— 4D slice plane.
-
- File:
Views/UserBulbDialog.cs- Axis-mode combobox "Algebra: Vec3 / Quat".
- On Quat: enable
Slice W:numeric. Hint label updates to showQuatsignature.
Commit: feat(userbulb): unified Vec3/Quat algebra mode with W-slice
- File:
Models/FractalParameters.cspublic List<UserBulbParam> UserBulbParams { get; set; } = new();- Clone(): deep-copy list.
- New file:
Models/UserBulbParam.csrecord class UserBulbParam(string Name, double Value, double Min, double Max);
- File:
Calculators/UserBulbCalculator.cs:130-140WrapUserSource- Compile sig:
Func<Vec3, Vec3, int, double[], Vec3>(and Quat variant). - Inject before body:
double {p.Name} = _p[i];for each param (validate name is valid C# identifier; reject duplicates at save time). - Call site: pass
UserBulbParams.Select(p => p.Value).ToArray()per render (cache, only rebuild on param-list mutation).
- Compile sig:
- File:
Views/UserBulbDialog.cs- New collapsible "Params" group below "Render".
-
+button → add row: [name TextBox] [value NumericUpDown] [min] [max] [delete X]. - Value change →
RenderRequested(no recompile). - Name/min/max change → recompile (name affects wrap source).
- Persist with saved equation in
UserBulbStore.
- File:
Models/UserBulbStore.cs- Extend entry schema:
List<UserBulbParam> Params.
- Extend entry schema:
Commit: feat(userbulb): arbitrary-count named parameter sliders
- File:
Models/FractalParameters.cs-
public double UserBulbTime { get; set; }(not cloned for time-line; or do clone — decide at impl time, default: clone).
-
- File:
Calculators/UserBulbCalculator.cs- Compile sig adds
double tparam. - Wrap source: prepend
double t = _t;.
- Compile sig adds
- New file:
Views/UserBulbAnimateBar.cs- Play/Pause toggle.
- Speed slider (units per second, -5..5).
- Loop length (seconds, 0 = no loop).
-
System.Windows.Forms.Timer30Hz:_params.UserBulbTime += speed * dt; RenderRequested?.Invoke();
- File:
VideoZoom.cs- Add "Time sweep" mode: sweep
UserBulbTimefrom A→B over N frames, capture each.
- Add "Time sweep" mode: sweep
Commit: feat(userbulb): animation time global + animate bar + video sweep
- File:
Models/FractalParameters.cspublic bool UserBulbJuliaMode { get; set; }public Vec3 UserBulbJuliaC { get; set; } = new(-0.2, 0.4, 0.0);- Clone() entries.
- File:
Calculators/UserBulbCalculator.cs:303-340UserBulbDE- If Julia:
cBase = JuliaC. Jacobian now w.r.t. z (initial), not c. Perturb initial z trajectory:z0Px = (h,0,0), etc. - Effectively swaps which input is held fixed.
- Add
bool juliaModeparam toUserBulbDE; route fromCalculate.
- If Julia:
- File:
Views/UserBulbDialog.cs- Checkbox "Julia mode" + 3 NumericUpDown rows for
JuliaC.X/Y/Z. - Both fire
RenderRequestedonly.
- Checkbox "Julia mode" + 3 NumericUpDown rows for
- Quat-mode (3.2) interaction:
UserBulbJuliaCbecomes Quat type; UI shows 4 numerics when in Quat mode.
Commit: feat(userbulb): Julia mode with fixed-c (Vec3/Quat)
- File:
Models/FractalParameters.cspublic enum BulbColorDriver { StepDepth, OrbitTrap, EscapeAngle, FinalMagnitude, IterComponent, Normal }public BulbColorDriver UserBulbColorDriver { get; set; }public Vec3 UserBulbOrbitTrap { get; set; } = Vec3.Zero;-
public int UserBulbIterComponent { get; set; } = 0;(0=X, 1=Y, 2=Z) - Clone().
- File:
Calculators/UserBulbCalculator.cs-
UserBulbDEtracks:minTrapDist(min distance toUserBulbOrbitTrapacross iters), finalz, finalr, escape iter. - Return struct
DEResult { double Dist; double TrapMin; double FinalR; Vec3 FinalZ; double EscapeIter; }. - Color computation
:274-276switches on driver.
-
- File:
Views/UserBulbDialog.cs- Combobox "Color driver".
- Conditionally show: trap XYZ (OrbitTrap), axis selector (IterComponent).
Commit: feat(userbulb): color drivers (orbit trap, angle, magnitude, axis, normal)
- File:
Models/FractalParameters.cs(port from existing theme system; see commitd420ff4for 3rd rim light reference)-
UserBulbLight2Theta/Phi/ColorR/G/B/Intensity(default off, intensity=0) -
UserBulbLight3Theta/Phi/ColorR/G/B/Intensity(default off) - Re-use
UserBulbLightTheta/Phias Light1; addLight1Color*+Intensity. -
UserBulbShadowSoft0..1 (0=off, 1=hard, else penumbra width). -
UserBulbAOSamples0..8 (0=off). -
UserBulbAOStrength0..1. -
UserBulbFogDensity0..2 (0=off). -
UserBulbBgTopColor/UserBulbBgBottomColor(uint ARGB).
-
- File:
Calculators/UserBulbCalculator.cs- Loop over enabled lights, sum diffuse * color * intensity.
- Soft shadow: from hit point, march toward each light. Track
minRatio = min(DE/tToLight). Penumbra =smoothstep(0, soft, minRatio). - AO: 5 taps along surface normal at
r, 2r, 4r, 8r, 16rdistances.occl = 1 - strength * sum((maxR - hit_d)/maxR). Clamp [0,1]. - Fog:
fogF = 1 - exp(-tTotal * density). Mix shaded color toward bg. - BG ray (miss): vertical gradient
lerp(BgBottom, BgTop, 0.5*(rdy+1)).
- File:
Views/UserBulbDialog.cs- New tab/group "Lighting": 3 light rows + shadow/AO/fog/bg color pickers.
Commit: feat(userbulb): 3-light shading + soft shadows + AO + fog + sky gradient
- File:
Models/FractalParameters.cs-
UserBulbFovDegreesdefault 60. -
UserBulbDoFAperturedefault 0 (off). -
UserBulbDoFFocusDistdefault 0. -
UserBulbDoFSamplesdefault 8. -
UserBulbClipPlaneNormal(Vec3) +UserBulbClipPlaneDist(0 = off). -
UserBulbSuperSampleenum{ x1, x2, x4 }.
-
- File:
Calculators/UserBulbCalculator.cs:206- Replace
Math.PI / 3.0withFov * Math.PI / 180. - DoF: when aperture>0, per pixel jitter ray origin on disc tangent to
camera basis; accumulate
Samplesrays; average. - Clip: in
UserBulbDE, ifVec3.Dot(p, clipN) > clipD→ return huge dist (skip surface). - SS: render at
W*ss × H*ssinto temp buffer, box-filter downsample toColorBuffer.
- Replace
- File:
Views/UserBulbDialog.cs- New "View" group: FOV slider, DoF aperture/focus/samples, Clip plane enable+normal+dist, SS combobox.
- File:
MainForm.cs- Find existing Mandelbulb viewport mouse handler; fork for UserBulb fractal
type:
- Left-drag:
CameraTheta += dx * sensitivity,CameraPhi -= dy * sensitivity(clampPhito[0.01, π-0.01]). - Wheel:
CameraDistance *= (1 - delta * 0.1).
- Left-drag:
- Find existing Mandelbulb viewport mouse handler; fork for UserBulb fractal
type:
Commit: feat(userbulb): FOV/DoF/clip/supersample + viewport mouse orbit
- File:
Models/FractalParameters.cspublic List<UserBulbChainStep> UserBulbChain { get; set; } = new();- Clone().
- New file:
Models/UserBulbChainStep.csrecord class UserBulbChainStep(string OutputName, string Source);-
Sourceis body ofVec3 Step(Vec3 z, Vec3 c, int n, ChainCtx ctx) => ...;wherectx.{name}accesses prior step outputs.
- File:
Calculators/UserBulbCalculator.cs- Compile each step; cache
Func<Vec3,Vec3,int,ChainCtx,Vec3>list. - Per iter: execute each step in order, store outputs by name in
ChainCtx. - Final iter z = last step's output (or designated "final" step).
- Numerical Jacobian: replay full chain w/ perturbed c (4× chain cost).
- Compile each step; cache
- New file:
Models/ChainCtx.csclass ChainCtx { Dictionary<string, Vec3> Outputs; public Vec3 this[string k] => Outputs[k]; }- Roslyn imports must include namespace.
- File:
Views/UserBulbDialog.cs- Replace single editor with a chain list:
- Toolbar:
+ Add step,− Remove,↑/↓ Reorder. - Each step row: [name TextBox] [collapsed/expanded source TextBox].
- Toolbar:
- Backward compat: if
UserBulbSourceset + chain empty, on load auto- convert to a single chain step named "out".
- Replace single editor with a chain list:
Commit: feat(userbulb): arbitrary-length step chain with named outputs
- File:
Models/UserBulbStore.cs- On first
Load()where store is empty, seed with presets. Each preset populates: chain source, params, camera, lighting, color driver, axis mode. - Presets:
- Mandelbulb p=8 (
Vec3.Pow(z, 8) + c) - Mandelbulb squared (
z*z + ctriplex) - Sin-bulb (
Vec3.Sin(z) * Vec3.Cosh(z) + c) - Abs-bulb (
Vec3.Pow(Vec3.Abs(z), 8) + c) - Mandelbox (
Vec3.BoxFold(z, 1) * scale + cwith sphere fold) - Kaleidoscopic IFS (chain: fold → rot → scale, 3 steps)
- Quaternion Julia (Quat mode,
z*z + c, fixed c) - Menger sponge step (
abs+ fold combo) - Sierpinski tetrahedron (3 reflections per iter)
- Animated breathing bulb (uses
tglobal)
- Mandelbulb p=8 (
- On first
Commit: feat(userbulb): seed preset library on first run
- New file:
Export/UserBulbMeshExporter.cs- Sample DE on N³ grid (default 128) within bounding cube.
- Marching cubes → triangle list.
- Writers: OBJ, STL (binary).
- File:
Views/UserBulbDialog.cs- "Export mesh…" button → save dialog (.obj/.stl) + grid-resolution prompt.
- File:
VideoZoom.cs- Already covered by 3.4 time sweep. Confirm path works end-to-end.
Commit: feat(userbulb): marching-cubes mesh export (OBJ/STL)
- File:
Models/UserBulbStore.cs- Existing JSON schema → extend to capture full entry (chain, params, camera, lights, color, axis mode, presets).
-
Export(string name, string filePath)— write one entry as .fbulb JSON. -
Import(string filePath)— read, merge or rename on name collision.
- File:
Views/UserBulbDialog.cs- Buttons: "Import…" / "Export…" beside
Save/Delete.
- Buttons: "Import…" / "Export…" beside
Commit: feat(userbulb): .fbulb single-equation import/export
After each commit:
dotnet buildThen launch and smoke-render:
dotnet run --project FracturingFog.csproj(Or invoke run skill if available.)
Validate:
- Build succeeds, no warnings introduced.
- App launches.
- Switch fractal type to "User Bulb (3D)".
- Default source renders without exception.
- Quick visual A/B vs prior commit (HEAD~1 build) — no regression.
-
Preset migration: when adding new
FractalParametersfields, ensureUserBulbStoreJSON deserializer tolerates missing fields (use defaults). -
Backward compat:
UserBulbSourcefield stays for old presets; auto- convert to chain step on load (Phase 3.6). - GPU + Quat: ILGPU backend (Phase 2.5) initially Vec3-only. Document Quat as CPU-only until follow-up.
- GPU + chain (3.6): translator must handle multi-step chain. Defer until 3.6 lands; until then GPU runs single-source compatible chains only.
-
GPU + params (3.3): pass param array as ILGPU
ArrayView<double>. -
Caveman commits: every commit message via
/caveman-commit.
- VR / stereo rendering.
- Reflections / refractions (1 bounce).
- Sub-surface scattering.
- Real-time GPU denoiser.
- Web (WASM) port of CPU path for embedding.