SplasheryPlayground

Definitions, derivations, notes, and long tables start folded.

Chapter 3The splat equation language

Recipes are powerful, but they are code, and code from a stranger can do things you did not ask for. The Splat Equation Splash on the Math shelf gives you a smaller language that is only math. You write where every splat goes and what color it is, as equations, and nothing else can happen. That is why a link with your equations in it is safe to share.

Open it with any link in the gallery (Chapter 4). Type in the box under Your own splat program in the Splash sheet (tap Splash in the bottom bar) and press Splat it. The Program list has eight famous shapes to start from.

The idea: every splat gets its own u and v

The Splash lays out its splats on a grid of two numbers, u and v, each running over a range you choose. Think of them as the coordinates of a flat sheet of points, laid out before the equations bend it into shape. They are the parameters of a parametric surface [9], not a substitution: nothing is replaced by u or v; each splat simply has its own pair. Your equations turn each splat's (u, v) into a place in space, (x, y, z). If u goes around a circle and v around a smaller circle, the grid wraps into a torus:

x = (2 + cos v)·cos u
y = sin v
z = (2 + cos v)·sin u
u = 0 .. 2pi
v = 0 .. 2pi
u, from 0 to 2π v, from 0 to 2π x(u, v), y(u, v), z(u, v) →
Each dot on the grid is one splat with its own (u, v). The equations carry every dot to its place on the torus.

Try it: the grid of (u, v), carried onto the torus

Each dot of the flat grid is one pair (u, v); the colors show where each came from (the hue follows u, the brightness follows v). The program above gives each dot a place on the torus. Press Carry them, or drag the slider, to move the dots from their flat places to their places on the torus. Tap a dot (or use the two sliders under it) to follow one and read its numbers. Drag the picture to turn the view.

The torus demo: a grid of colored dots part way from a flat sheet to a torus
Interactive in the web version: splashery.app/manual/language.html#demo-uvtorus

The fields

A program is a list of fields, each written name = value. You can type one field or several, split by ; (a text file can put one on each line). You do not have to retype everything. A field you leave out keeps what is already showing: if you type only a new hue, the shape stays and only the color changes.

Show the table: the fields of a program
Field What it is If left out
x, y, z Where each splat goes, as equations in u, v, and t. y points up. Needed
u The range of u, like 0 .. 2pi. Needed
v The range of v. 0 .. 1
hue Color as a place on the color wheel: 0 is red, 1/6 yellow, 1/3 green, 1/2 cyan, 2/3 blue, 5/6 magenta, and 1 is red again. Any number works; it wraps around every whole turn, so −0.1 gives the same hue as 0.9. Use r, g, b
r, g, b Color as red, green, and blue, each from 0 to 1. Setting any of them turns hue off; setting hue turns them off. 0.5 each (gray)
size How big each splat is, in the same units as x, y, and z: about the σ of its bell. It may use u, v, and t. 0.05
count How many splats: 100 to 10,000. 4000
spread grid spaces the splats evenly; random scatters them at random over the ranges (a galaxy, a firework). grid

Try it: change one field, and see what it controls

This builds the splats with Splat Equation's own recipe, from the fields you type, and draws them here. Pick a program, then change a field (try hue, size, count, or x). Below the picture, the page compares the new splats with the ones before, so you can see what that field moved and what it left alone. If a field cannot be read, the Splash's own message shows instead.

What changed since the last edit
The fields demo: the torus program's splats beside its twelve fields
Interactive in the web version: splashery.app/manual/language.html#demo-fields
Why this number? Twelve fields

Our choice The Splash chose which fields; saved links limit how many a Splash can keep.

The Splash's program is saved in its link as options, and a link keeps up to 64 option keys per Splash. The twelve fields, plus the program picker, the splat style (Solid, Fine, or Dots), the Light and shade switch, and the Moments of t choice, come to 16, so a fully filled Splash leaves 48 keys to spare. (The labs switch only hides the Splash from the shelf; it does not take the Moments of t definition away, so a link that opens the Splash directly still counts all 16.)

If it were chosen differently. More fields means more option keys. Up to 64 fit a link; past that, either the limit rises again (a change to how every saved link is read, which must keep old links loading) or fields share a key.

Where it lives: normalizeOptions in src/state.js; FIELDS in src/packs/splat-equation.js.

Could Splat Equation have different fields?

Not tested yet Nothing below is built.

Yes. Each field you add costs one of the 64 option keys a saved link keeps, and the Splash uses 16, so 48 new fields would fit before the limit had to rise or fields had to share a key. Candidates:

  • Opacity, so a splat could be see-through.
  • A stretch and a turn for each splat, which would make an oriented, flattened splat instead of a round one.
  • Glow, a brightness that does not depend on the lighting.
  • A part for each point, so each point could move on its own. This is the costly one: Splashes are limited to 15 parts.

Flexibility

The reader is forgiving about how you write a program:

Expressions

The equations are read by the same small, safe reader as the Graph Plotter and Surface Plotter. It understands:

Powers go first, then × and ÷, then + and −, and a power binds to the right: 2^3^2 is 29, and -u^2 is −(u²). Each expression can be up to 240 characters long (each end of a range counts on its own).

Try it: how the reader groups an expression

Type an expression in u, v, and t, or pick one. The tree shows what goes together first (deepest first), and the value is worked out at u = 1, v = 0.5, t = 0. It follows the real reader's rules for powers, implied multiplication, and bare function inputs. The tree is a simplified picture, though: the Splash's own reader also checks the text and tells you under the tree if it would refuse it (an expression may be 240 characters, and there are limits on nesting and on how many pieces it can hold).

The reader demo: the expression minus u squared plus sin 2u drawn as a tree
Interactive in the web version: splashery.app/manual/language.html#demo-reader
Why this number? The 240-character limit

Our choice Nothing in the math or the browser needs it.

A program lives in the Splash's link, and a link holds up to 12 fields, so a limit keeps links short enough to paste into a message. The longest field of the Splash's own programs is 53 characters. The limit is 240, for people who write longer formulas.

If it were chosen differently. The link could carry much more (a text option may be up to 24,000 characters), so the limit is not what stops longer fields. Two other guards would be next: expressions nest at most 32 deep and have at most 400 pieces. The cost of longer fields is longer links and slower builds, because each expression is worked out for every point at every moment (at the default, 13 positions: the 12 moments and the closing target). At 240 characters a field can take up to twice as long to build as at 120, and a link with every field at the limit grows from about 1,400 to about 1,900 characters once compressed (docs/lab/EQUATION-FIELDS.md). The Graph Plotter and Surface Plotter keep 120.

Where it lives: FIELD_MAX in src/packs/splat-equation.js (240); MAX_LENGTH (120, still the plotters' limit), MAX_DEPTH, and MAX_NODES in src/equation.js; TEXT_MAX in src/state.js.

The grammar

Here is the core grammar. Note: words in quotes must be typed exactly as written below.

A grammar is a short list of rules for what counts as a valid program. Its notation follows EBNF, a standard way to write such rules [8]:

program    = field "=" value { ( ";" | new line ) field "=" value }
field      = "x" | "y" | "z" | "u" | "v" | "hue" | "r" | "g" | "b"
           | "size" | "count" | "spread"
value      = sum                         (x, y, z, hue, r, g, b, size)
           | sum ".." sum                (u, v: numbers only, and no min or max)
           | sum                         (count: a number only)
           | "grid" | "random"           (spread)
sum        = product { ( "+" | "-" ) product }
product    = signed { ( "*" | "/" ) signed | power }   (nothing between: 2u)
signed     = { "-" | "+" } power
power      = atom [ "^" signed ]
atom       = number | "u" | "v" | "t" | "pi" | "e"
           | function "(" sum ")"
           | function bare               (sin 2u, sin u cos u, sin u^2)
           | ( "min" | "max" ) "(" sum "," sum { "," sum } ")"
           | bracket | "|" sum "|"       (not directly inside another | |)
bracket    = "(" sum ")" | "[" sum "]" | "{" sum "}"
bare       = simple { simple }           (stops at the next function or bracket)
simple     = ( number | "u" | "v" | "t" | "pi" | "e" ) [ "^" signed ]
function   = "sin" | "cos" | "tan" | "asin" | "acos" | "atan"
           | "arcsin" | "arccos" | "arctan" | "sinh" | "cosh" | "tanh"
           | "exp" | "log" | "ln" | "log10" | "sqrt" | "abs" | "floor"
                                         (one input; min and max are above)

Try it: the grammar as tracks, and a walk along them

Each rule is drawn as tracks. Follow a line from the dot on the left to the dot on the right, taking any branch. A square box means "go and follow that rule here" (tap it to jump to the rule); a round box is a word you type as written. A line that curves back underneath means "as many times as you like". Press Walk the tracks and a random walk lights up the tracks it took and writes down the program it passed. The page then asks the Splash's own reader whether it accepts what was written.

The grammar demo: the rule for a sum drawn as tracks, with a walk lit up
Interactive in the web version: splashery.app/manual/language.html#demo-railroad

Time

t is time. A tap on the Splash plays it from 0 to 2π in about 4 seconds, and the t slider in the Splash sheet holds it anywhere in between. Anything with t in it moves: u + t turns a shape, sin(t) breathes, and sin(3u − t) sends a wave around.

Why this number? The two clocks

Our choice Both of them.

There are two clocks. The math clock, t, runs from 0 to 2π, one full circle, so that sin and cos of it come back exactly to where they started, and loops are seamless. The wall clock is real time: a play of t lasts about 4 seconds, whatever 0 to 2π means to the math. The speed is eased, so a play starts and ends still.

If it were chosen differently. A different range for t would change what a loop looks like (a range of 1 would make sin(t) stop mid-wave). A different play time would only change how fast it looks.

Where it lives: PLAY_SECS in src/packs/splat-equation.js.

Behind the scenes, the Splash builds your shape at 12 moments, t = 0, π/6, 2π/6, and so on, and each copy slides every splat in a straight line to its place in the next copy (a morph, Chapter 2), so every splat is at its computed place at those 12 moments (up to the packing error described below) and slides in a straight line in between. A shape that turns dips very slightly inward between moments, about 3 percent. (With labs on, a Moments of t choice sets 13, 14, or 15 moments instead; 12 is the default, so a link that names no count builds with 12.)

Why this number? Twelve moments

Our choice, near a limit Our choice; each moment also costs one of the 15 parts.

Twelve copies of 10,000 points (the most count allows) make 120,000 splats, Splat Equation's count at the Low tier, so even the weakest device can hold the biggest program. (The kit keeps 2 percent in reserve, so on that device each copy is cut to 9,800 points.) Each moment is its own part (Chapter 2), and 12 parts plus the body fit in the limit of 15.

If it were chosen differently. More moments make a turning shape rounder, because a splat slides in a straight line between moments and so dips inward in between. The worst dip is 1 − cos(π ÷ moments): 3.4 percent at 12 moments, 2.2 percent at 15, and 0.9 percent at 24. But the points per copy shrink on the weakest device: 9,800 at 12, 7,840 at 15, and 4,900 at 24. And because each moment is a part, 15 is the most without a bigger change. Color and size step at the moments rather than sliding, so more moments would smooth color changes too. The moments demo below lets you try every count. The labs choice Moments of t offers 12, 13, 14, or 15; measured, the worst dip of the splats is 3.41, 2.91, 2.51, and 2.19 percent (docs/lab/MOMENTS.md).

Where it lives: KNOTS (the default, 12) and MOMENTS (the choices) in src/packs/splat-equation.js; the points per copy are floor(0.98 × budget ÷ moments).

Why is each moment a part, and what else could work?

Not tested yet The alternatives below are ideas, not built.

The Splash builds 12 complete copies of the shape, one for each moment of t. It shows one copy at a time and slides each into the next with the morph channel, so each copy needs its own part number, to be shown or hidden on its own. Other ways to do it:

  • Store the moments in a keyframe texture and blend them in the shader. This needs no parts and allows more moments, but it is a change to the engine.
  • Compile the formulas to a shader, so t is continuous and there are no copies at all. This works because every splat is a pure function of its own (u, v, t), with nothing shared between splats, so the graphics chip could work them all out at once. It is the larger experiment.
  • Recompute on the processor every frame. This is too slow at 10,000 or more points, with a fresh sort every frame.
Why this number? Twelve bits

Format limit It follows from how much room each splat has.

What is stored for each splat's moment is the move from its place to its target place (the displacement), as three whole numbers, one per axis, each of 12 bits: 4,096 steps across a range of ±2 Splash units. These are real binary digits, not a figure of speech. A Splash is fitted to a sphere 1.9 units across, so any move fits, and one step is about 1/1,900 of the Splash's width. Rounding to a step is the "tiny error" the manual mentions: at most half a step, as long as the move is inside the range (a move beyond ±2 units is cut off at the end of the range).

Each splat's animation data is four 32-bit numbers, and two of them are already taken (the part number with its flags, and the kind of motion). That leaves two. A 32-bit float holds any whole number up to 224 exactly, and two 12-bit numbers make 24 bits, so one float carries two axes. The third axis and a channel number share the other.

z = qx × 4096 + qy          (two 12-bit numbers, exact)
w = qz + 4096 × channel     (one 12-bit number and the channel)

If it were chosen differently. More bits would not fit two axes in one float, so the Splash would need another float per splat (every float is already spoken for) or a texture. Fewer bits would make the steps visibly coarse.

Where it lives: encodeMorphs in src/kit.js; MORPH_RANGE in src/effects.js.

Approximations in time. The shape is sampled at 12 moments (or 13 to 15, if you chose that in labs), not solved for every t. Only each splat's position slides toward the next copy, and that target is packed into 12 bits per axis over a fixed range, so tiny errors are possible. Its color, size, and rotation are built separately for each copy, so they can step at a copy boundary instead of blending. The "count" you type is the number of points sampled for each copy, not the total number of splats in the Splash (Figure 3).

One cycle of t = 12 stored copies 0 j j+1 t: 0 → 2π Twelve copies, built at t = 0, 2π/12, 4π/12, … Copy j shows and slides toward copy j + 1. The other eleven are stored but hidden. Where the splats go (the detail budget) Splash budget by device tier: Low 120,000 · Mid 240,000 · High 300,000 · Max 400,000 per copy: ⌊0.98 × Splash budget ÷ 12⌋ (Low 9,800 … Max 32,666) points per copy: the smaller of count and that budget Fine: up to 4 × count, still within the budget grid rounds down; points with no value are dropped
Figure 3. A program with t is stored as 12 copies, so the number of splats in memory is about 12 times the points in one copy. The device's tier sets the budget at the top; your count, the Fine setting, and the grid can only lower the points per copy.

Try it: scrub t, play it, and turn on the stored moments

Each dot is one splat, with u running around the ring. Turn on "Moments" to see what the Splash stores: between moments, each dot slides in a straight line, which is why a turning shape dips inward a little (3.4 percent at 12 moments).

The moments demo: a ring of dots with a wave going around it
Interactive in the web version: splashery.app/manual/language.html#demo-moments

Make it loop. Because t ends at 2π, a shape made of sin(t), cos(t), and angles like u + t ends exactly where it began. A shape with plain t in it (like y = t) jumps back at the end. sin(t/2) goes out and back once; sin(2t) twice.

A program without t stands still. Then a tap clears it and draws the splats again one by one, in the order they are made: the first value of u with every v, then the next u, and so on.

What the Splash does for you

Why it is safe

The reader never runs what you type. It breaks the text into numbers, the letters u, v, and t, the names in the lists above and the signs, and builds a small tree of arithmetic from them. There is no way to name anything else, so the worst a program can do is draw a strange shape. Your program lives in the Splash's link ("Copy" under the link in the Share tab of the More sheet, or the Share button in the header), so you can send it to anyone.

The Splash Manual, first published September 28, 2026 and updated October 11, 2026. Part of Splashery, a playground of 3D Gaussian splats that runs in your browser. Every picture here was made with Splashery itself.