Spherical Harmonics for Browser Light Sculpture
Render one normalized lobe composition, inspect coefficient energy and ringing, and export a deterministic accessible SVG plate.
Spherical harmonics turn labeled angular basis functions into a browser light sculpture. Keep coefficients, signed lobes, ringing, normalization, and deterministic SVG identity visible.
Spherical harmonics form an angular basis
Spherical harmonics are basis functions over directions on a sphere, indexed by degree l and order m. Low orders capture broad angular variation, making them useful for compact lighting approximations and for generative forms whose lobes remain mathematically labeled.
The NIST DLMF spherical-harmonics reference documents definitions, normalization context, and identities. Choose a complex or real convention, ordering, signs, and normalization explicitly, because coefficients from another convention cannot be mixed safely by matching l and m labels alone.
The browser artifact evaluates all sixteen pinned normalized real Cartesian basis formulas through degree three over a fixed 2,048-point Fibonacci sample set. It is a light-sculpture and projection teaching tool, not a global-illumination renderer, high-frequency accuracy proof, or benchmark.
Spherical harmonics form angular basis functions indexed by degree l and order m. This edition pins a real-basis convention, coefficient ordering, signs, and normalization before drawing anything; without that declaration, two correct implementations can produce sculptures that look rotated or inverted despite matching their own conventions.
Pin the real basis convention
Write each basis formula beside its index and test selected directions where value and sign are known. Common real forms derive sine-like and cosine-like components from complex functions, but libraries differ in phase and ordering; an unexplained array of coefficients is not portable evidence.
Spherical harmonics through degree three provide sixteen functions in a complete basis. The sample sculpture now uses a visible sixteen-value coefficient vector; every displayed basis participates in target generation, discrete projection, reconstruction, the matching coefficient gallery, and the exported radial SVG.
The practical graphics paper Stupid Spherical Harmonics Tricks offers evaluation and ringing context. Use it with an exact convention table rather than copying coefficient constants without provenance.
The individual lobe plate is a calibration tool as well as an image. Positive and negative values use distinct signed color, zero crossings remain visible, and labels retain l and m so a coefficient slider can be traced back to the exact basis function it controls.
Coordinate systems are a frequent interoperability failure. Name which axis is up, whether directions point toward or away from incoming light, handedness, azimuth origin, and coefficient packing order. Render known single-lobe fixtures before transferring values to a shader. If a WebGPU result appears rotated or mirrored, compare those conventions before modifying signs until the picture looks right.
Sample the sphere deterministically
A Fibonacci sphere distributes a fixed number of directions without a latitude singularity and is easy to reproduce. Record count, indexing, coordinate handedness, polar axis, angle formula, and weights; equal sample weights approximate integration but introduce finite-sample error that should be measured.
Spherical harmonics orthogonality means distinct normalized basis functions integrate to zero product over the sphere and self-products follow their normalization. Approximate those integrals on the pinned samples, report maximum off-diagonal residue, and set a tolerance justified by sample count.
The artifact uses 2,048 equal-weight Fibonacci points and computes the complete 16×16 sampled Gram matrix. It rejects maximum off-diagonal or diagonal error above 0.01, while the current local fixture reports a 0.00014428764683329048 maximum off-diagonal. That numerical test checks implementation consistency, not a symbolic mathematical proof.
SH coefficient visualization turns a vector of weights into a field over directions. Preserve negative values for analysis, offer clamping only as a named display mode, and report how much energy that display transformation removes instead of treating it as harmless styling.
- Real spherical harmonic basis gallery
- Signed lobes through degree three show l and m indices, normalized basis identity, and positive versus negative regions.
| l,m | Pattern | Sign encoding |
|---|---|---|
| 0,0 | constant sphere | positive |
| 1,-1 | two y-oriented lobes | pearl / violet |
| 1,0 | two z-oriented lobes | pearl / violet |
| 2,0 | axial lobes and equatorial ring | signed regions |
Mix coefficients without hiding sign
Evaluate each selected basis at a direction, multiply by its coefficient, and sum. Preserve negative values in the raw angular field because clamping changes the representation and can erase lobes or introduce visible bias; provide a separate clamped preview when an artistic mapping requires nonnegative radiance.
The default coefficients are fixed and the SVG namespace derives from their signed rounded values, so identical controls create identical markup and hash. Changing a slider changes both the lobe path and its deterministic ID without introducing random export noise.
Spherical harmonics coefficients can be art-direction controls: one sets a base sphere, another pulls a diagonal pair, and a higher band adds smaller folds. Their numeric receipt keeps intuition attached to basis identity.
A Fibonacci sphere supplies a deterministic, approximately even sample set without a polar grid singularity. Store sample count and ordering, because projection coefficients and exported paths depend on those directions even when the analytic basis formulas remain unchanged.
For animation, interpolate coefficients under a declared rule and monitor energy, extrema, and self-intersection throughout the path. Honor reduced motion with a still state and expose the coefficient timeline as data. A kinetic sculpture can be expressive without claiming that linear coefficient interpolation represents a physical rotation or conservation law.
Map the angular field into sculpture
Choose a radial mapping that remains finite for the expected field range, such as a base radius plus scaled signed value. Declare scale, clamping, and handling of negative radius, then sample a stable path order; a visually pleasing silhouette can still self-intersect or fold under aggressive coefficients.
The lab projects its directional sequence into a two-dimensional radial plate rather than a 3D mesh. This keeps SVG output inspectable and should not be mislabeled as a spherical surface renderer; browser sculpture extraction handles a different implicit-geometry problem.
Color maps positive and negative contributions with an accessible description. Hue is expressive here, while the semantic table retains coefficient, minimum, maximum, and ringing counts without relying on color perception.
A real spherical harmonic basis is orthogonal under integration on the sphere, but a finite sample only approximates that property. The artifact checks diagonal energy and off-diagonal leakage within declared tolerance, turning sample quality into a receipt rather than decorative mathematical language.
- Coefficient projection sculpture
- All sixteen degree-0 through degree-3 basis contributions generate a target, a projected reconstruction, a radial plate, and a matching coefficient gallery.
- Evaluate all 16 pinned real Cartesian basis formulas at 2,048 Fibonacci-sphere samples.
- Generate the target from the current 16-value coefficient vector.
- Project the target by equal-area quadrature and reconstruct every sample.
- Keep signed target values; do not silently clamp negative lobes.
- Generate the radial SVG and 16-bar gallery from the recovered coefficients.
Track energy by band
Squared coefficient magnitude offers a simple coefficient-energy view under an orthonormal convention. Sum values by degree to see whether a form is dominated by its base, broad directional lobes, or higher-frequency detail, and retain the convention because other normalizations change interpretation.
Spherical harmonics coefficient energy is not emitted light, perceptual brightness, or physical power unless the projection and units justify that claim. The committed deterministic fixture groups the squares of all sixteen input coefficients by band—0.5184, 0.1049, 0.3243, and 0.0440 for l=0 through l=3—and labels them as generated local diagnostics, not production lighting measurements.
Use the distribution to art-direct complexity. If high bands dominate, increase sample density and inspect aliasing or ringing before celebrating the added intricacy.
Low-order spherical harmonic lighting captures broad angular structure and rings around sharp targets. The generated radial lobe deliberately exposes reconstruction error, negative undershoot, and clamped difference so the sculpture does not imply high-frequency or physically complete illumination.
Runnable artifact — The artifact is a low-order visualization and art-direction tool, not a physically complete global-illumination renderer or proof of high-frequency lighting accuracy.
<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Degree-3 real spherical harmonics projector</title><style>:root{color-scheme:dark}*{box-sizing:border-box}body{font:16px/1.45 system-ui;background:#091318;color:#f4f7f6;max-width:980px;margin:auto;padding:24px}main{display:grid;gap:16px;min-width:0}fieldset,.panel{border:1px solid #8aa0aa;border-radius:12px;padding:14px}fieldset{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px}label{display:grid;gap:5px}button,input,select,a,textarea{font:inherit;padding:9px}button,a{min-height:44px}textarea{width:100%;min-height:150px;background:#071014;color:#f4f7f6}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}.status{padding:10px;border-left:5px solid #46e0c1;background:#10242b}svg,canvas{max-width:100%;height:auto}.sr{position:absolute;left:-9999px}table{border-collapse:collapse;width:100%;table-layout:fixed}th,td{padding:7px;border:1px solid #8aa0aa;text-align:left;overflow-wrap:anywhere}@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important;scroll-behavior:auto!important}}#coefficientInputs{grid-template-columns:repeat(auto-fit,minmax(130px,1fr))}#stage svg{width:100%;border:1px solid #8aa0aa}#gallery{font-variant-numeric:tabular-nums}</style><main><h1>Degree-3 real spherical harmonics projector</h1><p>All 16 coefficients in the pinned real orthonormal basis generate the target, drive projection and reconstruction, and produce the current SVG and gallery.</p><fieldset id="coefficientInputs"><legend>Current degree-0 through degree-3 coefficients</legend></fieldset><p><button id="run">Project current target</button> <a id="svgExport" download="degree-3-harmonic.svg">Export current SVG</a> <a id="jsonExport" download="degree-3-harmonic.json">Export current JSON</a></p><div id="stage" class="panel"></div><p id="status" class="status" aria-live="polite"></p><table><caption>Current basis gallery and recovered coefficients</caption><thead><tr><th>Basis</th><th>Input</th><th>Projected</th><th>Error</th></tr></thead><tbody id="gallery"></tbody></table><textarea id="receipt" readonly aria-label="Execution receipt"></textarea></main><script>const PI=Math.PI,SAMPLE_COUNT=2048,labels=[{l:0,m:0,label:'Y00'},{l:1,m:-1,label:'Y1-1'},{l:1,m:0,label:'Y10'},{l:1,m:1,label:'Y11'},{l:2,m:-2,label:'Y2-2'},{l:2,m:-1,label:'Y2-1'},{l:2,m:0,label:'Y20'},{l:2,m:1,label:'Y21'},{l:2,m:2,label:'Y22'},{l:3,m:-3,label:'Y3-3'},{l:3,m:-2,label:'Y3-2'},{l:3,m:-1,label:'Y3-1'},{l:3,m:0,label:'Y30'},{l:3,m:1,label:'Y31'},{l:3,m:2,label:'Y32'},{l:3,m:3,label:'Y33'}],defaults=[.72,-.14,.23,-.18,.12,-.09,.31,-.44,.11,-.08,.07,-.05,.13,-.06,.09,-.04],stage=document.getElementById('stage'),gallery=document.getElementById('gallery'),receipt=document.getElementById('receipt'),statusNode=document.getElementById('status');
coefficientInputs.insertAdjacentHTML('beforeend',labels.map((basis,index)=>'<label>'+basis.label+' (l='+basis.l+', m='+basis.m+')<input data-coefficient="'+index+'" type="number" min="-2" max="2" step="0.01" value="'+defaults[index]+'"></label>').join(''));
function basis(x,y,z){return[.28209479177387814,.4886025119029199*y,.4886025119029199*z,.4886025119029199*x,1.0925484305920792*x*y,1.0925484305920792*y*z,.31539156525252005*(3*z*z-1),1.0925484305920792*x*z,.5462742152960396*(x*x-y*y),.5900435899266435*y*(3*x*x-y*y),2.890611442640554*x*y*z,.4570457994644658*y*(5*z*z-1),.3731763325901154*z*(5*z*z-3),.4570457994644658*x*(5*z*z-1),1.445305721320277*z*(x*x-y*y),.5900435899266435*x*(x*x-3*y*y)]}
function fibonacci(count){return Array.from({length:count},(_,index)=>{const z=1-2*(index+.5)/count,r=Math.sqrt(1-z*z),phi=index*PI*(3-Math.sqrt(5));return{x:r*Math.cos(phi),y:r*Math.sin(phi),z}})}
function dot(values,coefficients){let sum=0;for(let index=0;index<values.length;index++)sum+=values[index]*coefficients[index];return sum}
function project(samples,target){const recovered=new Float64Array(labels.length),scale=4*PI/samples.length;for(let basisIndex=0;basisIndex<labels.length;basisIndex++){let sum=0;for(let sampleIndex=0;sampleIndex<samples.length;sampleIndex++)sum+=target[sampleIndex]*basis(samples[sampleIndex].x,samples[sampleIndex].y,samples[sampleIndex].z)[basisIndex];recovered[basisIndex]=sum*scale}return recovered}
function fnv(value){let hash=2166136261;for(let index=0;index<value.length;index++){hash^=value.charCodeAt(index);hash=Math.imul(hash,16777619)}return(hash>>>0).toString(16).padStart(8,'0')}
function stablePrefix(coefficients){return'sh-d3-'+fnv(coefficients.map(value=>Number(value).toFixed(6)).join('|'))}
function makeSvg(coefficients,recovered){const prefix=stablePrefix(coefficients),count=240,ring=Array.from({length:count},(_,index)=>{const angle=2*PI*index/count,x=Math.cos(angle),y=Math.sin(angle),value=dot(basis(x,y,0),recovered);return{angle,value}}),maximum=Math.max(1e-12,...ring.map(point=>Math.abs(point.value))),points=ring.map(point=>{const radius=126+72*point.value/maximum;return[450+radius*Math.cos(point.angle),220+radius*Math.sin(point.angle)]}),path=points.map((point,index)=>(index?'L':'M')+point[0].toFixed(3)+' '+point[1].toFixed(3)).join(' ')+'Z',bars=labels.map((entry,index)=>{const height=Math.min(72,Math.abs(recovered[index])*80),x=42+index*50,y=386-height,color=recovered[index]>=0?'#62e6d0':'#ff8d76';return'<g data-basis="'+entry.label+'"><rect x="'+x+'" y="'+y.toFixed(3)+'" width="28" height="'+height.toFixed(3)+'" fill="'+color+'"/><text x="'+x+'" y="410">'+entry.label+'</text></g>'}).join(''),svg='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 430" role="img" aria-labelledby="'+prefix+'-title '+prefix+'-desc"><title id="'+prefix+'-title">Current degree-3 real spherical harmonic sculpture</title><desc id="'+prefix+'-desc">A signed equatorial reconstruction and sixteen coefficient bars generated from the current inputs.</desc><defs><linearGradient id="'+prefix+'-gradient"><stop stop-color="#62e6d0"/><stop offset="1" stop-color="#9c6cff"/></linearGradient></defs><rect width="900" height="430" fill="#090d18"/><path id="'+prefix+'-path" d="'+path+'" fill="url(#'+prefix+'-gradient)" fill-opacity=".7" stroke="#f5f0ff" stroke-width="2"/>'+bars+'</svg>';return{prefix,svg,ring,points}}
const shaBuffer=async buffer=>[...new Uint8Array(await crypto.subtle.digest('SHA-256',buffer))].map(value=>value.toString(16).padStart(2,'0')).join(''),shaText=value=>crypto.subtle.digest('SHA-256',new TextEncoder().encode(value)).then(hash=>[...new Uint8Array(hash)].map(item=>item.toString(16).padStart(2,'0')).join(''));
async function execute(){try{const coefficients=Float64Array.from(document.querySelectorAll('[data-coefficient]'),input=>Number(input.value));if(coefficients.length!==16||[...coefficients].some(value=>!Number.isFinite(value)))throw Error('all sixteen coefficients must be finite');const samples=fibonacci(SAMPLE_COUNT),basisRows=samples.map(sample=>basis(sample.x,sample.y,sample.z)),target=Float64Array.from(basisRows,row=>dot(row,coefficients)),recovered=project(samples,target),reconstruction=Float64Array.from(basisRows,row=>dot(row,recovered));let sumSquared=0,maxAbsolute=0;for(let index=0;index<target.length;index++){const error=reconstruction[index]-target[index];sumSquared+=error*error;maxAbsolute=Math.max(maxAbsolute,Math.abs(error))}let maxOffDiagonal=0,maxDiagonalError=0;const scale=4*PI/SAMPLE_COUNT;for(let left=0;left<labels.length;left++)for(let right=0;right<labels.length;right++){let sum=0;for(const row of basisRows)sum+=row[left]*row[right];const value=sum*scale;if(left===right)maxDiagonalError=Math.max(maxDiagonalError,Math.abs(value-1));else maxOffDiagonal=Math.max(maxOffDiagonal,Math.abs(value))}const rendered=makeSvg(coefficients,recovered),renderedRepeat=makeSvg(coefficients,recovered),svgSha256=await shaText(rendered.svg),svgRepeatSha256=await shaText(renderedRepeat.svg),matchingGallery=labels.map((entry,index)=>({...entry,inputCoefficient:coefficients[index],projectedCoefficient:recovered[index],projectionError:recovered[index]-coefficients[index]})),bandEnergy=[0,1,2,3].map(l=>({l,input:matchingGallery.filter(row=>row.l===l).reduce((sum,row)=>sum+row.inputCoefficient**2,0),projected:matchingGallery.filter(row=>row.l===l).reduce((sum,row)=>sum+row.projectedCoefficient**2,0)})),checkpointIndices=[0,17,113,509,1023,1531,2047],data={basisConvention:'normalized real Cartesian spherical harmonics; labels and signs pinned in source',degreeMax:3,basisCount:labels.length,sampleSet:{kind:'Fibonacci sphere',count:SAMPLE_COUNT,weight:4*PI/SAMPLE_COUNT},coefficients:[...coefficients],projectedCoefficients:[...recovered],matchingGallery,bandEnergy,projection:{targetGeneratedFromCurrentCoefficients:true,rmsError:Math.sqrt(sumSquared/target.length),maxAbsoluteError:maxAbsolute,coefficientMaxError:Math.max(...matchingGallery.map(row=>Math.abs(row.projectionError))),targetCheckpoint:checkpointIndices.map(index=>({index,target:target[index],reconstruction:reconstruction[index]})),targetSha256:await shaBuffer(target.buffer),reconstructionSha256:await shaBuffer(reconstruction.buffer)},orthogonality:{maxOffDiagonal,maxDiagonalError},signedEvidence:{targetMinimum:Math.min(...target),targetMaximum:Math.max(...target),negativeSamples:[...target].filter(value=>value<0).length,clampedWouldChange:[...target].some(value=>value<0)},svg:{idPrefix:rendered.prefix,ids:[rendered.prefix+'-title',rendered.prefix+'-desc',rendered.prefix+'-gradient',rendered.prefix+'-path'],galleryBasisLabels:labels.map(entry=>entry.label),sha256:svgSha256,repeatSha256:svgRepeatSha256,hashRepeatMatches:svgSha256===svgRepeatSha256}};const invariants={fullDegreeThree:data.basisCount===16&&matchingGallery.length===16,galleryMatches:data.svg.galleryBasisLabels.every((label,index)=>label===matchingGallery[index].label),finite:[...coefficients,...recovered,...target,...reconstruction,maxOffDiagonal,maxDiagonalError].every(Number.isFinite),projectionBounded:data.projection.rmsError<.01&&data.projection.coefficientMaxError<.02,orthogonalityBounded:maxOffDiagonal<.01&&maxDiagonalError<.01,signedNotSilentlyClamped:data.signedEvidence.clampedWouldChange,deterministicSvg:data.svg.hashRepeatMatches&&new Set(data.svg.ids).size===data.svg.ids.length};if(!Object.values(invariants).every(Boolean))throw Error('degree-3 projection invariant failed');data.invariants=invariants;window.__sphericalLast={samples,basisRows,target,recovered,reconstruction,rendered,data};stage.innerHTML=rendered.svg;gallery.innerHTML=matchingGallery.map(row=>'<tr><th>'+row.label+'</th><td>'+row.inputCoefficient.toFixed(6)+'</td><td>'+row.projectedCoefficient.toFixed(6)+'</td><td>'+row.projectionError.toExponential(2)+'</td></tr>').join('');svgExport.href=URL.createObjectURL(new Blob([rendered.svg],{type:'image/svg+xml'}));jsonExport.href=URL.createObjectURL(new Blob([JSON.stringify(data,null,2)],{type:'application/json'}));receipt.dataset.execution=JSON.stringify(data);receipt.value='PASS: '+JSON.stringify(data,null,2);statusNode.textContent='16 bases · RMS '+data.projection.rmsError.toExponential(2)+' · SVG '+svgSha256.slice(0,12)}catch(error){receipt.dataset.execution=JSON.stringify({unexpectedError:error.name+': '+error.message});receipt.value='FAIL: unexpected '+error.message}}
run.onclick=()=>void execute();document.querySelectorAll('[data-coefficient]').forEach(input=>input.addEventListener('change',()=>void execute()));window.__spherical={labels,basis,fibonacci,dot,project,stablePrefix,makeSvg,execute};void execute();</script></html>
Make ringing and clamping visible
Truncated low-order expansions can overshoot around sharp angular features, producing negative regions or lobes that do not match a target. Count negative samples, show raw and clamped profiles separately, and compare reconstruction error against a declared target when one exists.
The artifact has no target projection and therefore does not invent an error-to-truth score. It exports min, max, negative count, orthogonality residue, coefficient energy, and a clampedDistinct boolean, all tied to the executing coefficients.
Spherical harmonics are attractive partly because ringing can become sculptural. Artistic use is valid when the artifact calls it intentional form rather than physically accurate illumination.
SH coefficient visualization becomes an art instrument when each coefficient has a stable visual role. Solo, mute, normalize, and signed-palette controls encourage composition, while the diagnostics keep a beautiful mixture connected to energy, sample error, ringing, coefficient order, sampling density, and the declared normalization convention. Retain the raw and clamped sample vectors beside every exported composition.
Treat rotation as its own verified feature
Rotating SH coefficients is powerful but convention-sensitive. Robin Green’s GDC course paper is an important implementation reference, and its author has warned about transcription bugs in rotation equations; verify against independent fixtures before shipping.
The teaching lab explicitly says rotation not implemented. It rotates neither sample directions nor coefficient bands, which is more truthful than exposing a decorative rotation slider backed by untested algebra.
When adding rotation, test identity, inverse, composition, band-energy preservation, known axial cases, and comparison with directly resampled functions. Keep coordinate and handedness labels beside the export.
Spherical harmonics light sculpture exports must be deterministic beyond their visible paths. Namespace gradients and clips from fixed coefficients, serialize values in a pinned order and precision, reparse the SVG, assert finite geometry, and compare its SHA-256 across repeated exports.
Projection from an environment or target function requires quadrature weights and a declared sampling distribution. Estimate each coefficient from basis value times target radiance and weight, then reconstruct on an independent direction set to measure error. The current artifact skips projection entirely, so its coefficients are authored controls; this prevents an artistic preset from being misrepresented as a fitted lighting environment.
Export deterministic namespaced SVG
SVG IDs must be unique within the document and stable for identical coefficients. Derive a prefix from the artifact version and coefficient receipt, apply it to title, description, gradient, path, and URL references, then reparse the markup and compare ID count with set size.
The browser lab generates the SVG string once, hashes it twice, and requires equality before reporting PASS. It also confirms every path coordinate is finite and keeps clamped and raw results distinct; no timestamp, random suffix, or browser-specific element order enters the file.
Move a verified composition to WebGPU generative art only after defining how its shader reproduces the same basis convention. A faster renderer is not automatically a matching renderer.
The browser piece is a low-order directional study, not a global-illumination renderer. It omits visibility, material response, transport, rotation, and high-frequency environment detail; those omissions stay beside the controls so an artistic abstraction is not mistaken for measured lighting.
- Harmonic diagnostic plate
- Per-band coefficient energy, 16-basis orthogonality, projection error, signed evidence, and deterministic export identity share one plate.
- Input coefficient energy by band
- l0 0.5184; l1 0.1049; l2 0.3243; l3 0.0440
- Maximum sampled off-diagonal
- 0.00014428764683329048
- Projection RMS / coefficient maximum error
- 0.000028182792789999195 / 0.000058458337413809514
- Target samples
- 2,048; signed negative values retained
- SVG SHA-256
- cbc2894ad9b709d935d6faa75fd659840e1fe8fdc191db1bd0a687b2a958973b
Ship a harmonic sculpture with its math
Archive basis formulas and source, convention, l/m mapping, coefficient vector, sample scheme and count, coordinate axes, weights, orthogonality tolerance, radial mapping, color mapping, raw range, negative count, clamped preview, SVG IDs, hash, browser, and review notes. Keep rotation status explicit.
Run the lab with default coefficients twice and compare exported SVG hashes, then change one coefficient and verify the namespace, path, energy, and hash change together. Inspect positive and negative patterns without treating the palette as quantitative evidence.
Revisit spherical harmonics when basis normalization, sampling, rotation, radial mapping, SVG export, or color handling changes. Compare the resulting lobes with minimal-surface sculpture or thin-film color while preserving each technique’s separate mathematics.
Review the work after basis corrections, sampling changes, rotation support, or SVG serialization edits. A lobe poster can circulate as an independent visual, but its caption should name the convention and link back to coefficients, signed diagnostics, deterministic identity, and the cited implementation cautions.
SVG accessibility and durability require more than unique IDs. Keep a title and description, meaningful document order, sufficient foreground contrast, finite viewBox, and no external references. Reparse exports, resolve every url() fragment, and test multiple sculptures embedded on one page. Coefficient-derived namespaces prevent collisions for different presets while identical presets remain byte-reproducible.