2026-10-09

How it works

Every page on the site explained in plain words, with the maths and the equations behind it.

Each section below covers one page: what it does, how the algorithm or maths works, the equations that drive it, and a couple of things worth trying. Search or use the contents list to jump around, press / to search from anywhere on the page, and follow the links to open each page.

Sections are grouped by each page's main tag:

When a new project is added, add its section here too (see NEW-PROJECT.md).

This site#

web

aldus.nexus is a static site: every page is plain files served by Cloudflare, and everything you see is computed in your browser. Nothing you draw, upload or save leaves your device; saved things (badges, trips, workouts, galleries) live in this browser's storage. The only server code answers Stumble's request for a random, safety-checked link.

The landing page is an orrery. A small sun shows through the gap in the title, and every project rides one of two tilted orbit rings as a world in its own colour, lit from the sun, with its tags as specks of moons. Where each world sits comes from the clock, one lap every ten minutes, so each visit shows different projects in front; drag or swipe to spin it. Worlds sharing a tag are joined by faint constellation lines, and one world a day (picked from the UK date) wears a gold ring.

θᵢ(t) = 2π · slotᵢ ± 2π · (t mod 600 s) / 600 s + dragdepth = (sin θ + 1) / 2, size ∝ depth³·²

Every project page opens with a terminal prompt that retypes to say what the page is doing, shows live facts and numbers, and ends with links to the previous and next project sharing its tag. Sound is on by default and one button in the top corner mutes the whole site. Press ? on any page for its keyboard shortcuts.

Site pages that aren't projects, such as Terminal and Achievements, sit in the bottom Links panel with this guide, so the project lists, the orrery and the previous/next links stay projects only. Each page also guards against loading half-way: if its own script or stylesheet fails it reloads itself, at most twice, then says what went wrong, and a content security policy only lets pages load code from this site.

The site map draws every page as a star around its tag hubs, and the RSS feed lists new projects and writing. Both, and this guide's list of sections, are rebuilt on every deploy.

  • Try: drag the landing orrery until a world you haven't seen comes round, then open the left panel for its summary.
  • Try: press ? on Sorting to see everything its keys can do.

Terminal#

webtools

A shell for the whole site, in the browser. ls lists the projects in their colours (with -l for dates and tags), tag, find and cat look things up, open and random go somewhere, and about draws a tiny ASCII orrery whose worlds are placed by the clock, as on the landing page. Everything comes from the site manifest the build writes into the page, checked field by field (slugs, colours, dates and link URLs), and every character is printed as a text node so a name can never become markup. Site pages such as this one, Achievements, the guide and the site map are listed under ls links rather than with the projects. open Terminal

Each line goes through the same steps as a real shell. History expansion first swaps !!, !3 or !ls for an earlier line. The lexer then reads the characters once, left to right, with a little state: a space ends a word, 'single quotes' keep everything exactly, "double quotes" keep spaces but still expand $USER, a backslash escapes one character, and |, ; and && become operators. This is why echo "a b" keeps two spaces while echo a b keeps one: quoting decides where words split, not what the words are. The parser groups words into pipelines; each command returns lines, so grep, head, tail, wc, sort and uniq can filter them, and && runs the next pipeline only if the last one exited with status 0.

Tab completion lexes only the text before the caret, in a forgiving mode that accepts an unfinished quote, to find the word being typed and its position in the command. The first word completes command names (or filters after a |); later words ask that command for candidates such as slugs, tags or post names. One match is filled in, several are extended to their longest common prefix, and if that adds nothing the list is printed. A mistyped command gets a "did you mean" from the edit distance. See also open Signal search for more string algorithms.

d(i, j) = min(d(i−1, j) + 1, d(i, j−1) + 1, d(i−1, j−1) + [aᵢ ≠ bⱼ]), d(i, 0) = i, d(0, j) = jsuggest b when d(a, b) ≤ min(3, max(1, ⌊|a| / 3⌋)), cost O(|a|·|b|) time, O(|b|) memorycompletion: lcp(w₁ … wₖ) = longest p with p a prefix of every wᵢlexing and parsing: one pass, O(n) for a line of n characters
ls-l|grep"city map"&&echo$USERstage 1stage 2: one wordruns if status 0words, quotes kept together, operators split pipelines
One line becomes two pipelines; quotes make "city map" a single word
  • Try: ls -l | grep maps | wc -l, then !! to run it again, and history to see it numbered.
  • Try: type cat s and press Tab for the list, or tap the tab key on a phone; then look for the commands help doesn't list.

Achievements#

gamesweb

Every page with badges keeps two small lists in your browser: the ids of the badges you have earned, and the names and descriptions of all its badges, written the first time you open that page. open Achievements reads both lists for every project on the site and puts them in one neon cabinet, grouped by series (each project's main tag). Earned trophies glow in the project's colour with their name and description, locked ones are dim silhouettes showing only their name, and a page you have never opened keeps its shelf hidden with a link to go and reveal it. Nothing leaves the browser, and everything read back is checked: only known pages, short plain-text ids, capped names, and only ids the page really defines. It is always shown as text, never as markup.

Rings and bars show badges earned overall and per series, with pages visited and a "next easiest" page: the visited page with the fewest badges still locked. Pages only store which badges you have, not when, so the cabinet keeps its own note of when it first saw each one. That is how it makes new badges sparkle once and names the latest; within one page the list is kept oldest first, so the last entry is the newest. You can export the whole cabinet as text, or clear every badge on every page after a two-step confirmation. Try it after collecting a few on open Bloom filter or open Sorting.

complete = Σₚ earnedₚ / Σₚ knownₚ (over visited pages p)arc length = 2πr × earned / knownnext = argminₚ (knownₚ − earnedₚ), over pages with knownₚ − earnedₚ > 0latest = argmax over badges b of first-seen(b)
66%
two of three earned: two lit trophies, one silhouette, and the ring at 2/3
  • Try: earn a badge on another page in a second tab and watch it appear and sparkle in the cabinet.
  • Try: press [ and ] to step through the series, and S to show only what is still locked.

Bloom filter#

algorithms

"Have we seen this star before?" A stream of star sightings feeds a Bloom filter: m bits, all off, and k hash functions. Each new star is hashed k ways and lights k bits, drawn as rainbow beams from the star to glowing cells. To check a star, hash it the same way: if any bit is off it has certainly never been seen; if all are on, the answer is maybe. A new star whose bits were all lit by others is a false positive, drawn as a violet ghost star. Sliders set m, k and the catalogue size n, the best k is marked, and the measured false positive rate (from hundreds of never-seen test stars) is charted live against the formula. You can also type a name or tap a star to look it up. open Bloom filter

The count-min tab swaps bits for counters to answer "how often?". It keeps d rows of w counters; each sighting adds one in every row, and a star's estimate is the smallest of its d counters. Collisions only ever add, so it never undercounts, and the overcount is bounded by εN with high probability. Both sketches also run on the stream of caught stars on open HyperLogLog, as the probabilistic trio.

P(false positive) ≈ (1 − e^(−kn/m))ᵏk* = (m / n) ln 2, fill at k* ≈ 1/2m / n = −ln p / (ln 2)² ≈ 9.6 bits per item for p = 1%count ≤ estimate ≤ count + εN with probability 1 − δ, ε = e / w, δ = e^(−d)
k = 3 beams from one star: all three bits lit means "maybe".
  • Try: drag k well past the marked best k and watch the false positive curve climb again as the array fills.
  • Try: in the count-min tab, shrink the width w and tap the busiest star to see its counters overcount.

Deflate#

algorithms

The page compresses a probe's message for the long trip home in Deflate's two passes, and the bits it sends are a real raw Deflate stream (RFC 1951). LZ77 slides a telescope window over the text and replaces repeats with (distance, length) back-references, drawn as arcs. Huffman coding then builds a constellation-like tree by merging the two rarest symbols again and again, so common symbols get short codes and rare ones long codes, and no code is the start of another. Finally the bits stream home as a signal, against a bandwidth limit. Your browser decompresses the stream with its own DecompressionStream and the page shows "verified by your browser" when it comes back byte for byte, next to the size of the browser's own CompressionStream output. See it at open Deflate.

Deflate never sends the tree. Its codes are canonical: only each symbol's code length is sent, and both ends hand out codes in order, shorter lengths first and ties by symbol, so the merge tree visibly rearranges itself into the canonical one. No code may pass 15 bits; when a lopsided message would need longer ones, package-merge finds the cheapest lengths that fit. Lengths and distances use two small tables: 29 length codes for 3 to 258 and 30 distance codes for 1 to 32,768, each followed by a few extra bits that pick the exact value inside its range. A dynamic block's header is compressed too: the code lengths are run-length coded with a 19-symbol code-length alphabet (0 to 15 are lengths, 16 repeats the previous one 3 to 6 times, 17 and 18 are runs of 3 to 10 and 11 to 138 zeros), and that alphabet's own Huffman code, at most 7 bits, is sent first as 3-bit lengths in the order 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15, so the rarely used ones at the end can be left off. The page draws this header symbol by symbol. For short messages Deflate's built-in fixed code, with a 3-bit header, often wins, and the page picks whichever block is smaller (or lets you force one).

Compression removes redundancy, and with it the safety margin: one flipped bit in compressed data shifts every code that follows. So the page sends the compressed bits on through a noisy channel three ways, plain, with Hamming(7,4) and with Reed-Solomon RS(255,223), using the same code as open Error correction, and hands the bits over to that page, which inflates each lane at home to show which ones still read. The link carries the panel's noise too, its level and whether it comes as random flips or bursts of 16, so Error correction opens on exactly the channel you set here.

p = 10^(-4 + 3.4·v/100) for the noise slider v = 1..100 (p = 0 at v = 0), shared with Error correctionL = Σ p(s)·len(s), H ≤ L < H + 1, H = -Σ p(s) log₂ p(s)"abcabcabc" → a b c (3, 6)length 20 → code 269 + 2 extra bits (range 19-22), distance 100 → code 13 + 5 extra bits (97-128)next[l] = (next[l-1] + count[l-1]) · 2, codeₛ = next[lenₛ]++Σ 2^(-lenₛ) = 1, lenₛ ≤ 15 (code-length code ≤ 7)header = 3 + 5 + 5 + 4 + 3·HCLEN + Σ (code-length code bits + extra bits)ratio = compressed bits / (8 · bytes)
lengthsa 2 e 2 q 3 s 3 t 3 x 3codesa 00 e 01 q 100 s 101 t 110 x 111header0×9720×32...only lengths travel; codes are rebuilt in order at home
Canonical codes: the lengths alone fix the codes, and the lengths are run-length coded
  • Try: step back from the transmit stage to watch the merge tree rearrange into canonical codes, then the header being spelled out with the code-length alphabet.
  • Try: craft a message under 0.30 of its size with 12 or more different characters, or switch the block to fixed and see when the 3-bit header beats a dynamic one.
  • Try: raise the noise in the error correction panel until the plain bits break but Reed-Solomon still delivers, then open it in open Error correction, which starts at the same noise, to read the inflated text from each lane.

Diffie-Hellman#

algorithmsmaths

Two strangers agree a secret while every message they send is overheard. The page starts with the paint-mixing picture: Alice and Bob share a public base paint, each adds one private colour and sends the mix across the open channel, then each adds their own private colour again to the mix they received. Both end with base + Alice's + Bob's, so the colours match, while Eve, who copied both mixes, can only make a muddy colour with twice the base in it. Playback steps through all ten moves, and the paint, maths and both views swap the pots for the real numbers. open Diffie-Hellman

Underneath, paint becomes modular powers in a toy safe-prime group (8 to 64 bits, BigInt). Alice sends A = gᵃ mod p, Bob sends B = gᵇ mod p, and both arrive at s = gᵃᵇ mod p. Square and multiply makes each power cost a few dozen multiplications, shown bit by bit. Eve's brute-force race tries x = 1, 2, 3 … until gˣ ≡ A, timing her real speed and extrapolating to 2048-bit groups (brute force, baby-step giant-step and the number field sieve). A man-in-the-middle panel shows Mallory running two exchanges at once, reading and rewriting a message, until Alice and Bob compare fingerprints. The same maths powers the compact tab on open RSA art. Toy only.

A = gᵃ mod p, B = gᵇ mod ps = Bᵃ = Aᵇ = gᵃᵇ (mod p)Alice's work ≈ 2 log₂ p multiplicationsEve's brute force ≈ p / 2 tries, baby-step giant-step ≈ √pNFS cost ∝ exp(1.923 (ln p)^(1/3) (ln ln p)^(2/3))
gᵃᵇgᵇᵃEve: A, BA, B in public
Alice and Bob end on the same colour; Eve only hears A and B.
  • Try: switch to the maths view and step with , and . to watch gᵃ and Bᵃ being built bit by bit.
  • Try: start Eve on a 32-bit group, then read how long the same attack would take at 2048 bits.

Error correction#

algorithms

A probe far from home sends its message as bits, and cosmic noise flips some of them on the way. The page sends the same message three ways through the same noise: with no code, with Hamming(7,4) and with Reed-Solomon over GF(256), and shows each decoder at work block by block. Noise comes as random flips or as bursts, and a chart sweeps the noise level to show where each code breaks. Compressed bits from open Deflate can be handed over (up to 16,384 bits) to carry the probe's story on, and they make the point sharply: compressed data has no slack, so it must arrive perfect. The handoff brings Deflate's noise setting along as well: the page opens at that level and kind, random flips or bursts, labels it "noise from Deflate" in the facts, the prompt line and the note under the controls, and you can still move it. Everything in the link is treated as untrusted: the noise is read only if it is plain digits and a known kind, clamped to the slider's range and steps, and anything odd falls back to the defaults.

Hamming(7,4) wraps 4 data bits in 3 parity bits. Column j of the parity-check matrix H is j written in binary, so H times the received bits gives a syndrome that is 000 for a clean block and otherwise spells out where a single flip is. Two flips in one block fool it into flipping a third. Reed-Solomon treats bytes as numbers in GF(256) and a block as a polynomial that must vanish at α⁰ to α²ᵗ⁻¹, the roots of its generator. The receiver evaluates the block at those points to get 2t syndromes, Berlekamp-Massey finds the error locator Λ(x), a Chien search finds its roots (the bad byte positions) and Forney's formula gives the values to add back. Any t bad bytes are fixed however many of their bits flipped, which is why bursts barely bother it. More than t bad bytes are caught and the block is passed on as received.

A Deflate payload is a real raw Deflate stream, so at home each lane's decoded bits go through inflate, a small decoder built into the page, and your browser's own DecompressionStream gives a second verdict. Inflate reads the block header, rebuilds the code-length code and then the literal/length and distance codes from their lengths, and decodes symbol by symbol. A lane that arrives clean gives back the probe's words exactly, which Reed-Solomon manages at low noise. A damaged lane shows where inflate gave up and why: a code-length code that is not complete, a code that is not in the table, a copy from before the start of the message, or a stream that ends early. Sometimes a damaged stream still inflates, into confident nonsense, and the wrong bytes are marked. See also open Deflate and open Signal search.

s = H·rᵀ = H·eᵀ, Hᵢⱼ = bit i of j (j = 1..7)g(x) = Π (x - αⁱ), i = 0..2t-1, over GF(2⁸) with x⁸ + x⁴ + x³ + x² + 1Sⱼ = r(αʲ), j = 0..2t-1Λ(x) = Π (1 - Xₖx), Xₖ = α^posₖYₖ = Xₖ · Ω(Xₖ⁻¹) / Λ′(Xₖ⁻¹), Ω(x) = S(x)Λ(x) mod x²ᵗrate: Hamming 4/7 ≈ 0.57, RS(255,223) ≈ 0.87, corrects t = 16 bytesinflate: a code set is usable only if Σ 2^(-lenₛ) = 1 (or one lone 1-bit code)P(block of n bits clean) = (1 - p)ⁿ, so a plain 4,000-bit stream at p = 0.1% survives ≈ e⁻⁴ ≈ 2% of the time
rH1H2H31 0 0 1 1 1 01 0 1 0 1 0 10 1 1 0 0 1 10 0 0 1 1 1 1101= 5syndrome 101 points at bit 5: flip it back
Hamming's syndrome is the position of the flipped bit in binary
  • Try: switch to bursts and slide the noise up: Hamming falls apart first while RS(255,223) keeps the message whole, until a block passes t bad bytes.
  • Try: step with full stop through a noisy run and watch a two-flip block where the syndrome lies.
  • Try: send a message on from open Deflate at a noise you picked there, then raise the noise until the RS lane's inflated text breaks too, and read where inflate stops.

Evolution#

algorithms

A genetic algorithm keeps a population of guesses and never looks at the answer directly. In sprite mode each genome is 256 hex digits, one per pixel, and fitness is the share that already match the target; weasel mode does the same with the letters of "aldus nexus", and god mode lets you pick the parents yourself. Every generation, selection picks parents (tournament keeps the best of three random picks, roulette spins a wheel weighted by fitness), crossover splices two of them, mutation changes a few genes, and elitism copies the best few across untouched. open evolution

Walkers mode evolves bodies instead of pictures. A 50-byte genome builds a creature of 3 to 6 nodes with a spring between every pair. Some springs are bones; the rest are muscles whose rest length swings on a sine with its own evolved size and phase, all at one evolved beat, and each node has its own grip on the ground. A hand-written physics loop runs every creature at 120 fixed steps a second: sum the spring forces, add gravity, move each node, then stop anything that sinks into the ground and let friction push back on its sideways speed. Fitness is how far its centre moved right in the seconds you set, and the same selection, crossover, mutation and elitism controls breed the next generation. The best walker of each generation is replayed in neon with its gait trail, the last generation's best as a ghost, and a chart tracks best and average distance. A creature only gets anywhere if its feet slip one way and grip the other.

F = -k (|x| - L(t)) x̂ - c vL(t) = L₀ (1 + a sin(2π f t + φ))|Δvₓ| ≤ μ Jₙfitness = x̄(T) - x̄(0)P(i) = (fᵢ - f_min) / Σⱼ (fⱼ - f_min)
grippy foot, slippy foot, one muscle
A three-node walker: bones, muscles and feet with different grip.
  • Try: switch to walkers, then compare tournament and roulette selection on the same seed and watch the distance chart.
  • Try: set seconds to 5 and then 20: short runs reward a fast start, long runs reward a steady gait.

Fourier epicycles#

algorithmsmathsart

Any closed shape (a country outline, a loop through a city's streets, a photo's strongest edge or your own scribble) is walked once at an even pace and sampled at 1,024 points, each a complex number z = x + iy. The discrete Fourier transform turns those points into 1,024 spinning arrows: arrow k turns k times per loop, its length is |cₖ| and its start angle is arg cₖ. Put them tip to tail, biggest first, and the last tip traces the shape. A few circles give the rough outline, the many small ones add corners and coastline wiggles, and the error after N circles is exactly the energy of the circles left out. The FFT panel shows why this is fast: n² multiplies become n log₂ n butterflies. open Fourier epicycles

Hear the shape plays the current fit as stereo sound, x to the left ear and y to the right, at 110 Hz, using the player from open oscilloscope music: each circle becomes one overtone, so adding circles adds harmonics and an XY scope draws the fit back. SVG saves the true shape and the current fit as a vector file.

cₖ = (1/M) Σₙ zₙ e^(-2πikn/M)z(t) ≈ Σₖ cₖ e^(2πikt), over the N biggest |cₖ|error² = Σ |cₖ|² over the circles left out (Parseval)direct DFT: M² multiplies, FFT: (M/2) log₂ M butterflies
  • Try: slide the circles down until the fit drops under 95%, then press hear and add circles one at a time with ].
  • Try: draw your own shape, then save it as SVG with the fit at 10 circles.

Game of life#

algorithmsmathsart

The page runs four families of cellular automata on one grid you can draw on, step through or pop out onto a second screen. Life-like rules give each cell 8 neighbours and a rule such as B3/S23: a dead cell with exactly 3 live neighbours is born, a live one with 2 or 3 survives, everything else dies. You can paste any pattern as RLE text (the LifeWiki and Golly format); it is checked, capped at 1000 × 1000 cells and 64 KB, and a rule = in its header switches the rule too. Lenia makes life continuous: every cell holds a value from 0 to 1, the neighbourhood is a smooth ring of radius R, and a bell-shaped growth curve pushes each cell up when the ring sum is near μ and down when it is not. The ring sum is a convolution, done with a fast Fourier transform so a step costs about n log n instead of n × R², which keeps the soft Orbium gliders smooth on a phone.

Langton's ant turns by the colour it stands on (R right, L left, N none, U about), moves that cell on to the next colour and steps forward; RL wanders for about 10,000 steps and then builds a highway forever, and several ants can share the grid. Elementary 1D rules look at a cell and its two neighbours, 8 patterns in all, so a rule is just 8 bits and there are 256 of them; stacking each new row under the last draws Rule 90's Sierpinski triangle, Rule 30's randomness and Rule 110's colliding particles. The numbers and charts change with the family: births and deaths for life, mass and a value histogram for Lenia, coloured cells per colour for ants and pattern usage for 1D rules. open Game of life, and see Maths art for more pictures grown from tiny rules.

alive′ = alive ? n ∈ S : n ∈ B, n = live neighbours of 8A′ = clip(A + Δt · G(K ∗ A), 0, 1), Δt = 0.1G(u) = 2 exp(−(u − μ)² / 2σ²) − 1, Orbium: μ = 0.15, σ = 0.015, R = 13K(r) = exp(4 − 1 / (r(1 − r))), r = d / R, ΣK = 1K ∗ A = F⁻¹(F(K) · F(A)), cost ≈ n log ndir′ = dir + turn[c], c′ = (c + 1) mod kx′ᵢ = bit number (4l + 2c + r) of the rule, Rule 30 = 00011110₂ for patterns 111, 110, …, 000
Rule 30: 111 110 101 100 → 0 0 0 1 …
The first four of Rule 30's eight patterns: each picks the cell below it.
  • Try: pick Lenia, then Orbium swarm, and turn the colour to growth to see where the gliders feed and fade.
  • Try: Langton's ant with RL at full speed and wait for the highway, then add four more ants and watch it break.

HyperLogLog#

algorithms

Count the distinct stars in a rotating galaxy with a few hundred bytes. Sweep the net through the stream: every caught star is hashed to 32 bits, the first p bits pick one of m = 2ᵖ registers, and the register keeps the longest run of leading zeros seen in the rest. A run of k zeros turns up about once in 2ᵏ⁺¹ stars, so long runs mean many distinct stars, and repeats can never change anything. The estimate combines all registers with a harmonic mean, with linear counting for small numbers. A galaxy collision merges two sketches by taking the max of each register. open HyperLogLog

A new panel frames it as the probabilistic trio. The same caught stars also feed a Bloom filter, which answers "seen this one before?" and saturates visibly when its fixed bit array fills up, and a count-min sketch, which answers "how often?" and only ever overcounts. A comparison tab puts the three next to an exact set: question, memory and the one direction each can be wrong in. The sketch code comes from open Bloom filter.

E = αₘ m² / Σⱼ 2^(−M[j]), αₘ ≈ 0.7213 / (1 + 1.079 / m)small range: E = m ln(m / V) when E ≤ 5m/2 and V > 0 empty registersstandard error ≈ 1.04 / √mmerge: M[j] = max(M_A[j], M_B[j])
  • Try: warp to a million stars, then open the trio: HyperLogLog is still within a few percent while the 2 KiB Bloom filter says maybe to everything.
  • Try: guess the count before the reveal, then compare with 16 and 4096 registers.

K-means#

algorithms

K-means drops k suns into a cloud of points and repeats two steps until nothing moves: every point joins its nearest sun, then every sun moves to the mean of its points. Each step can only lower the total squared distance, so it always settles, but only on a nearby answer. Random starts can put two suns in one cluster, so k-means++ picks each new sun with a chance proportional to its squared distance from the suns so far. The page lets you break it on purpose: the wrong k, a bad corner start, outliers from a supernova, and rings and spirals, which it slices like a pie because nearest-sun cells are always convex. open K-means

The fix sits under the charts: DBSCAN on the same data, side by side with k-means. It needs no k, only a radius eps and a count minPts. A point is core when at least minPts points sit within eps of it; a cluster is everything you can reach by hopping between core points, which the page plays as a flood fill. Border points are reached but too sparse to carry the flood on, and points no flood reaches are noise, drawn as grey crosses, so supernova outliers stop dragging anything. The k-distance chart sorts every point's distance to its (minPts - 1)th neighbour; eps belongs just above the knee where that curve shoots up. Both methods are scored against the generator's true groups with the adjusted Rand index. The same idea of following links between neighbours shows up in PageRank, and SVD layers finds the directions a cloud stretches in.

J = Σⱼ Σ_{x ∈ Cⱼ} ‖x - μⱼ‖²μⱼ = (1 / |Cⱼ|) Σ_{x ∈ Cⱼ} xP(pick x) = D(x)² / Σ D(y)² (k-means++)N_ε(p) = { q : ‖q - p‖ ≤ ε }, p is core ⇔ |N_ε(p)| ≥ minPtss = (b - a) / max(a, b)DBSCAN with a grid of ε cells: about O(n · neighbours) instead of O(n²)
corebordernoise
A core point has at least minPts neighbours inside eps; a border point is reached but cannot pass the flood on; noise is never reached.
  • Try: pick rings in the DBSCAN section, then drag eps down until one ring breaks into pieces and watch the k-distance chart.
  • Try: tick outliers and compare the noise count with how far the k-means suns get pulled.

Light lab#

algorithmsartgames

A 2D recursive raytracer. Each ray is a start point and a direction: the tracer finds the nearest surface it hits, draws a line to it, decides what light does there and calls itself with the new ray, until the light escapes, is absorbed or reaches the bounce limit. A mirror reflects by flipping the part of the direction that points into the surface. Glass refracts by Snell's law, a little light also reflects (Fresnel), and past the critical angle it is trapped by total internal reflection. Glass has a slightly higher index for violet than for red, so a prism fans white light into a rainbow, and near a black hole rays are stepped forward in small moves while gravity turns them. open light lab

The beam puzzles ask you to turn mirrors and prisms until every star is lit; some stars only accept one colour. They come in three packs of five or six, all checked to have a solution and not to start solved. The editor lets you place a source, mirrors, prisms, walls, splitters, stars and black holes, mark pieces as turnable or fixed, run the same check, and share the level as a link: the whole level is packed into a few dozen bytes, base64 after the # in the address, and strictly validated and size-capped when someone opens it. The check scores every try by how far the light misses: for each star, the closest any usable ray comes to it, less the star's radius. It sweeps a coarse grid of whole-degree angles (the grid step grows with the number of turnable pieces, so it stays near 60,000 tries), then refines the 40 best near misses by pattern search: nudge one piece a step each way, keep any move that brings the light closer, halve the step when nothing helps. If that fails it shifts the grid by half a step, then halves the step, down to every whole degree, which is exhaustive. It runs in 20 ms slices with a progress bar so the page never stalls, and gives up after 20 seconds of search.

r = d - 2 (d · n) nn₁ sin θ₁ = n₂ sin θ₂sin θ_c = n₂ / n₁n(λ) = A + B / λ²R ≈ R₀ + (1 - R₀)(1 - cos θ)⁵, R₀ = ((n₁ - n₂) / (n₁ + n₂))²miss(θ₁…θₖ) = Σ_stars max(0, d_min − r), solved ⇔ miss = 0grid step s = smallest whole degree with Π ⌈Pᵢ ÷ s⌉ ≤ 60,000 (P = 180° mirror, 120° prism)
n₁ = 1glass
Snell's law: violet bends more than red, and a little light reflects.
  • Try: the Spectrum pack, where red and violet stars each need their own edge of the rainbow.
  • Try: build a level with three or four turnable pieces, press check and watch it refine its closest misses, then share the link.

Minimax#

algorithmsgames

Two players take turns: max wants the final score high, min wants it low. The page draws every possible game as a radial constellation. Leaves are stars whose brightness is their score, and values flow back to the root one ring at a time, with max rings taking the highest child and min rings the lowest. Alpha-beta gets the same answer while skipping branches that cannot change it: it keeps α, the score max is already sure of, and β, the score min is already sure of, and turns the rest of a node's children to dust as soon as α ≥ β. Sorting the best moves first prunes the most, down to roughly the square root of the tree. open Minimax

Below the tree you can play tic-tac-toe or Connect Four against the search. Tic-tac-toe is searched to the very end, so minimax and alpha-beta never lose; Connect Four stops a few plies ahead and scores the position with a heuristic (open lines of two and three, plus centre control). Each computer move replays its real search tree, with nodes searched, the share pruning saved against plain minimax, and the true thinking time. The third opponent, Monte Carlo tree search, uses no heuristic at all: it plays thousands of random games, picks which branch to try next with UCB1, and plays the move it visited most. Its tree grows live, star brightness showing visits. The same MCTS code powers the decision panel on Monte Carlo.

v(n) = max over children c of v(c) at max nodes, min over c of v(c) at min nodes, v(leaf) = scoreαβ(n, α, β): at max nodes α ← max(α, v(c)), at min nodes β ← min(β, v(c)); stop when α ≥ βplain minimax visits bᵈ leaves; alpha-beta at best b^⌈d/2⌉ + b^⌊d/2⌋ - 1UCB1(i) = wᵢ / nᵢ + c √(ln N / nᵢ), c = √2
582pruned5
max takes 5 on the left; once the right min node sees 2, its last branch cannot matter and is pruned
  • Try: drag the move order slider from worst to best and watch the leaves evaluated fall towards the best-case line.
  • Try: play Connect Four against MCTS with 500 and then 20,000 playouts and see how much stronger the extra random games make it.

Monte Carlo#

algorithmsmaths

Throw meteors at a square without aiming and count how many land inside a circle: the share inside matches the share of the square the circle covers, π/4, so four times that fraction estimates π. The law of large numbers says the average settles on the true value, but slowly, with typical error shrinking like 1/√n, which is the band on the convergence chart and why 200 runs at once pile up into a narrowing bell curve. Stratified and Halton sampler bots spread their points evenly and converge faster, and side panels estimate π with Buffon's needles, integrate a curve, and watch random walkers spread out like √t. open Monte Carlo

The newest panel is Monte Carlo for decisions. Monte Carlo tree search plays random games of tic-tac-toe from a small position, keeping wins and visits for every move. Each playout walks down its growing tree by UCB1, which adds an exploration bonus to each move's win rate, so promising moves get most of the playouts and weak ones still get the odd check. The board shows each move's win rate and visits live, the chart tracks win rate per move as playouts grow, and the move visited most is the one MCTS plays. The search code and game rules are shared with Minimax, where you can play against it.

π ≈ 4 × inside / totalerror ≈ √(p(1 − p) / n) ∝ 1/√nπ ≈ 2ln / (d × crossings) (Buffon: needles of length l on lanes d apart)UCB1(i) = wᵢ / nᵢ + c √(ln N / nᵢ), c = √2
80%45%20%random playout
playouts flow mostly to the strongest move; UCB1 keeps sampling the rest
  • Try: pick "block and fork" and watch the one saving move climb to the top of the win-rate chart.
  • Try: set c to 0 so MCTS only exploits, then to 3, and compare how the playouts are shared out.

Neural network#

algorithms

A small network learns live on stars you place or on presets (spiral, moons, rings, clusters, XOR). Each neuron takes a weighted sum of the layer before plus a bias and squashes it with an activation such as tanh. Two inputs, a star's x and y, flow through the hidden layers to one output per colour; softmax turns the outputs into probabilities and training lowers the cross-entropy loss. Backpropagation uses the chain rule to blame each weight, and gradient descent with momentum nudges every weight the other way. The nebula is the network's answer at every point of the plane, and the step-through shows one star going forward and backward with real numbers. open neural network

Draw a digit is the same machine, bigger: 256 inputs (a 16 × 16 pad), 64 ReLU neurons, 10 outputs. There is no downloaded dataset: when the page opens it renders a few thousand digits itself, in the site's fonts, the device's fonts and as pen strokes, each rotated, slanted, scaled, shifted and given a different stroke width, then trains in a background worker and keeps the weights in the browser. Your drawing is centred by its centre of mass and shrunk to 16 × 16 the same way, and the guess updates live as a bar chart of probabilities. The confusion matrix counts, for test digits it never trained on, which digit it guessed against which was true. Compare open sorting for another algorithm you can step through.

zⱼ = Σᵢ wⱼᵢ aᵢ + bⱼ, aⱼ = f(zⱼ)pₖ = e^(zₖ) / Σⱼ e^(zⱼ) (softmax)L = -log p_y (cross-entropy for the true class y)∂L/∂zₖ = pₖ - [k = y]∂L/∂w = ∂L/∂a · ∂a/∂z · ∂z/∂ww ← w - η · ∂L/∂w, with momentum v ← 0.9 v - η ∂L/∂waccuracy = Σ diagonal of the confusion matrix / total
  • Try: sigmoid with 4 layers on the spiral, and watch the first layer's gradient vanish.
  • Try: draw a sloppy 4 that leans like a 9 and watch the probability move between the two bars.

PageRank#

algorithms

A pilot jumps along one-way lanes between star systems. With chance d they follow a random lane out of the current system, otherwise they warp anywhere. The share of time spent at each system over a long flight is its PageRank, so a system matters when systems that matter point to it. Power iteration gets the same numbers without the ship: start everyone equal, hand each system's rank out along its lanes, add the warp share, repeat until it settles. Dead ends leak rank like black holes unless it is shared out again, and spider traps swallow it without warps. open PageRank

The page also ranks this site. It reads the site manifest embedded in the page, makes every project a star in its own colour, links two projects both ways with a weight equal to the number of tags they share, and links every project to and from the landing page and the How it works page. Weighted power iteration then sizes each star by its rank, the leaderboard lists the winners, and clicking a star opens that page. The tag toggles show how much each kind of link matters. The flood of rank along links has a cousin in the DBSCAN flood fill on K-means.

PR(i) = (1 - d)/N + d Σ_{j → i} PR(j) / L(j)PR(i) = (1 - d)/N + d Σⱼ PR(j) · wⱼᵢ / Wⱼ, Wⱼ = Σₖ wⱼₖwⱼᵢ = |tags(j) ∩ tags(i)| between projects, 1 for landing and How it works linkserror after k rounds ≈ dᵏ
w = 2w = 1
Projects sharing more tags are joined more strongly, so rank flows between them faster.
  • Try: untick the landing and How it works links to rank projects on shared tags alone.
  • Try: drop damping to 0.5 and watch the site leaderboard flatten out.

RSA art#

algorithmsmathsart

Put n points on a circle and join each point i to (i × k) mod n: k = 2 draws a cardioid, k = 3 a nephroid, and sliding k through fractions sweeps between them. The same circle then turns into RSA: encryption maps each point i to iᵉ mod n, decryption to iᵈ mod n, and the round-trip overlay shows every point coming home. Key cards build p, q, n, φ(n), e and d; a message plays back character by character through square and multiply; an eavesdropper factors n by trial division and extrapolates to 2048-bit keys; and a hidden message waits for whoever finds d. open RSA art

A Diffie-Hellman tab shows the other door the same modular powers open. Using a group built from your key's own primes (or a 24-bit prime), Alice and Bob agree s = gᵃᵇ mod p step by step, and the powers of a generator g are drawn as a chord path that visits every point on the circle once. Eve can brute force the discrete log of a 16 to 32-bit group, and Mallory can sit in the middle until the fingerprints are compared. The full version with the paint-mixing picture is open Diffie-Hellman. Toy only.

c = mᵉ mod n, m = cᵈ mod nn = p q, φ(n) = (p − 1)(q − 1), e d ≡ 1 (mod φ(n))square and multiply: ⌊log₂ e⌋ squarings + (ones in e − 1) multipliesDiffie-Hellman: s = (gᵇ)ᵃ = (gᵃ)ᵇ mod p
  • Try: set k to 2, then 3, then let it drift and watch the cusps multiply.
  • Try: in the Diffie-Hellman tab, step through the exchange in your key's p group, then switch to q and see a different generator walk the circle.

Shortest path#

mapsalgorithms

The page races seven path-finding algorithms across a real city's streets, from A to B, and replays each search: Dijkstra grows outward in rings of equal cost, A* leans towards B using the straight-line distance as a guess, bidirectional Dijkstra searches from both ends, greedy best-first races for whatever looks closest, breadth-first counts junctions, and two experimental searches grow from seeds and rays. Routes are costed by driving distance, driving time (street speeds with traffic, plus junction delays) or walking distance, and every route is checked against the true shortest. The plate under the title names the run's city in English and in its own language and script, with its country, layout and the A and B points. The A* lab underneath re-runs A* with a weight on the guess, from 0 (Dijkstra) to 5 (greedy), with the frontier coloured by f.

Add stops between A and B (the via box, or press S and click the map) and every algorithm searches leg by leg in order; the legs table shows each leg's shortest cost and how many junctions each algorithm expanded on it. The iso button shades the map by travel time from A, with rings at 5, 10, 15 and 20 minutes, using the same code as open isochrones; it is computed in a Web Worker, so the race keeps playing smoothly while the rings are worked out, and they appear as soon as they are ready. Each run adds its city to an A*-friendly league, ranking cities by how small a share of Dijkstra's work A* needs; straight, well-connected streets let the guess steer, while rivers and dead ends fool it. The address bar always holds a link to the run on screen (?city=&route=&from=&to=&via=), so the share button can pass it on.

Dijkstra: d(v) = min over arcs u→v of d(u) + w(u, v), expand the smallest d firstA*: f(n) = g(n) + w · h(n), h(n) = straight-line distance to B (÷ top speed for time)admissible when w · h(n) ≤ true cost to B; then route ≤ w × shorteststops: cost(A → s₁ → … → B) = Σₖ shortest(sₖ → sₖ₊₁)isochrone: t(v) ≤ τ for τ = 5, 10, 15, 20 min from Aleague score = nodes(A*) / nodes(Dijkstra), averaged over runs; lower is friendlier
  • Try: run the same city twice with a stop that forces a detour, then compare the legs table.
  • Try: turn on iso for a driving run and see how far 20 minutes gets you from A compared with the route length.

Sorting#

algorithmsmusic

Eleven algorithms sort the same shuffle one after another, each with its own colour and sound, then race side by side at the same pace. The classics (bubble, selection, insertion, shell, merge, a middle-pivot quicksort and heap sort) work by comparing pairs and swapping or writing values. Radix sort never compares at all: it deals the values into ten piles by their last digit, gathers the piles back in order, and repeats for the tens and hundreds. Timsort, the sort inside Python and Java, looks for runs that are already in order, tops short runs up with binary insertion, and merges them, galloping ahead (jumping 1, 3, 7, 15 places) when one run keeps winning. Bogosort is the joke: check, shuffle everything, check again, until its give-up timer runs out. QuickSort plays last with its own lab: pick the pivot rule and partition scheme, watch the recursion tree grow, ask what if a different pivot had been chosen, or be the pivot yourself. Pause any time to turn the bars into blocks you can throw about. open Sorting

The stability view tints equal values with a shared colour and numbers them by where they started. A stable sort (merge, insertion, bubble, radix, Timsort) finishes with each colour still numbered 1, 2, 3 from left to right; the others can scramble them, and each sheet says which kind it is. Stability is what lets you sort by one key and then another without losing the first order. Related: open Binary search uses the same halving that makes merge sort and QuickSort fast.

T(n) = 2T(n/2) + n ⇒ T(n) ≈ n log₂ n (balanced split: merge sort, a good QuickSort pivot)T(n) = T(n − 1) + n ⇒ T(n) ≈ n²/2 (worst split: sorted input, first-item pivot)QuickSort with random pivots: E[comparisons] ≈ 1.39 n log₂ nRadix sort: T(n) = d × (n + 10) for d digits, no comparisonsAny comparison sort needs at least log₂ n! ≈ n log₂ n − 1.44 n comparisonsBogosort: E[shuffles] = n! / (c₁! c₂! … cₖ!) where cᵢ items share a value; 48! ≈ 1.2 × 10⁶¹
beforestable sort1122312312
Stability view: equal values share a colour and keep their 1, 2, 3 order in a stable sort.
  • Try: turn on the stability view and compare heap sort with merge sort on the same shuffle, then watch bogosort give up after a few hundred shuffles of the 10⁶¹ it expects.
  • Try: pick nearly sorted input and watch Timsort find long runs and gallop through its merges.

SVD layers#

algorithmsmaths

An image is a grid of numbers, a matrix A. The singular value decomposition splits it into a sum of layers, each one column pattern times one row pattern, so every layer is stripes or plaid, and the singular value σᵢ says how much of the picture it carries. Sorted biggest first, the top k layers are the best possible rank k picture, stored in k(m + n + 1) numbers instead of m n. The page computes it by hand with one-sided Jacobi rotations in a background worker, and the geometry view shows the same thing for a 2 by 2 matrix: rotate, stretch, rotate, turning a circle into an ellipse. open SVD layers

The PCA section runs the same SVD code on a cloud of points instead of pixels: one row per point, one column per dimension. Centre every column, split the table as U Σ Vᵀ, and the rows of Vᵀ are the principal directions, the ways the cloud stretches most. The page draws them as glowing axes on a turning 3D cloud, shows the variance each one explains as bars, and projects the cloud onto the top two; the flatten button slides every point onto the PC1-PC2 plane so you can see what is thrown away. The data are seeded clouds in the style of K-means (a tilted galaxy disc, a helix, clusters in 5D, a dust ball in 6D) plus the Neural network presets lifted into its five input features x, y, x², y², xy.

A = U Σ Vᵀ = Σᵢ σᵢ uᵢ vᵢᵀ, σ₁ ≥ σ₂ ≥ … ≥ 0energy(k) = (σ₁² + … + σₖ²) / Σ σᵢ²X_c = X - 1 μᵀ = U Σ Vᵀλᵢ = σᵢ² / (n - 1), share(i) = λᵢ / Σ λⱼscores = X_c V = U Σ, 2D view = (σ₁ u₁, σ₂ u₂)
PC1PC2
The principal axes of a cloud and the share of variance each one explains.
  • Try: the dust ball in 6D: every bar is about equal, so no flat view can keep much.
  • Try: the helix, then flatten it: one long axis and two equal round ones.

Maths art#

mathsart

Type an equation, a word, a number or a date and it becomes moving geometric art. Equations go through a small parser built into the page that turns the text into a tree of operations and evaluates it for thousands of values of t, so nothing typed is ever run as code, even when it arrives in a shared link. One expression is a polar curve, the radius r at angle t; an x = …; y = … pair is a parametric curve; and p drifts slowly with time so the shape keeps moving. Anything that is not an equation is hashed into a 32-bit seed that picks a style and its numbers: a Lissajous figure, a spirograph, a rose, a flow field of particles following smooth noise, or a Clifford attractor where one point jumps by a fixed rule millions of times. The same text always grows the same picture.

You can save the frame as a PNG, or as an SVG when the drawing is a curve (the SVG keeps the rainbow segments and the glow as vector paths, so it stays sharp at any size; flow fields and attractors are clouds of dots, so they are PNG only). Copy link puts the input, style and speed in the address, and the page checks every value when it opens a link. The gallery keeps up to 24 favourites with small JPEG thumbnails in this browser, checked again each time the page loads. open Maths art, and see Game of life for patterns that grow from tiny rules.

Lissajous: x = sin(at + δ), y = sin(bt)Spirograph: x = (R − r) cos t + d cos((R − r)t / r), y = (R − r) sin t − d sin((R − r)t / r)repeats after r / gcd(R, r) turns of the wheelRose: r = cos(kt), k = n / dClifford: xₙ₊₁ = sin(a yₙ) + c cos(a xₙ), yₙ₊₁ = sin(b xₙ) + d cos(b yₙ)trail half-life = ln 0.5 / ln(1 − fade) frames
  • Try: type x = sin(3t); y = sin(4t + p), wait for a shape you like and press Save SVG.
  • Try: type your name, add it to the gallery, then switch the style to Attractor and compare.

Artist#

art

Open artist paints a picture under a foil cover that you scratch away. Each painting is built from simple shapes (ellipses, polygons and strokes) whose colours come from named roles such as sky, water and subject, so every style can recolour the same scene. Your scratching is the input: the first stroke places the subject, the stroke direction sets the light and the brushwork, the spread of your strokes sets the scale, and the total distance scratched adds detail and extra figures. All of it feeds a seeded random number generator, so a painting can be repeated exactly.

After the shapes are drawn, each style reworks the pixels. Oil and Renaissance smear short strokes along your scratch direction; Japanese woodblock and pop art snap every pixel to a small palette (nearest colour by weighted distance, with ordered Bayer dithering where it suits) and add outlines where the brightness changes sharply. Newer styles: Watercolour blurs, thins the colour towards the paper and pools pigment at the edges; Pointillism rebuilds the picture from thousands of dots of pushed-apart, palette-snapped colour; Stained glass cuts the canvas into a jittered grid of Voronoi panes, fills each with its average colour and leads the seams; Pop art flattens to bold inks with Ben-Day dots and a heavy keyline. Newer subjects are an owl, a whale, a waterfall, a village by the sea, flying a kite and fishing from a pier. Save PNG exports the finished painting at 4x; Save framed PNG adds its frame and a caption.

light angle θ = atan2(y_end − y_start, x_end − x_start)nearest colour = argminₖ 0.3(r − rₖ)² + 0.59(g − gₖ)² + 0.11(b − bₖ)²edge = √(Gₓ² + G_y²), Gₓ = L(x+1, y) − L(x−1, y)pane(p) = argmin_s ‖p − s‖² over seeds s in the 3 × 3 neighbouring cellswatercolour = (c(1 − w) + paper · w)(1 − min(0.35, edge/160)), w ≈ 0.32
  • Try: a waterfall in stained glass, scratching hard to make the panes smaller.
  • Try: the same subject in pointillism and pop art, then Save framed PNG.

Atlas#

maps

A pixel-art world where only the countries I've visited are above water. Turn on Flights and every route becomes a glowing great-circle arc: from home in Manchester to each country, the shortest set of links that joins every city (a minimum spanning tree), or every visit in date order once visit dates are added. Each hop's length comes from the haversine formula, which treats the Earth as a sphere of radius 6371 km; the page adds them up, finds the furthest hop and says how many times round the equator that is. A great circle is the shortest path on a sphere, which is why the arc to Tokyo bends north over Scandinavia and Siberia instead of following a straight line on the map.

Day/night shades the live night side. The Sun is overhead at latitude δ (the solar declination, between 23.4° N and 23.4° S over the year) and at a longitude that moves 15° west every hour, nudged by the equation of time. Everywhere more than 90.83° from that point is past sunset (the extra 0.83° is refraction and the Sun's radius); caps of 84°, 78° and 72° round the opposite point mark civil, nautical and astronomical twilight. It redraws every minute. When dates are added to the places list, a timeline appears with a replay that fades in cities as they were first visited. open atlas, and see the same sky maths from the ground in open live sky.

a = sin²(Δφ/2) + cos φ₁ · cos φ₂ · sin²(Δλ/2)d = 2R · asin(√a), R ≈ 6371 kmsin δ = sin ε · sin λₛ, ε ≈ 23.44°subsolar longitude = αₛ − GMSTnight: angle from subsolar point > 90.83°; twilight bands at 96°, 102°, 108°
great circle vs straight line on the map
On a flat map the shortest route curves towards the pole.
  • Try: turn on Flights, pick "Shortest links" and hover the list to find the furthest hop on the map.
  • Try: turn on Day/night near the solstice and watch the terminator tilt over the poles.

Isochrones#

algorithmsmaps

An isochrone is a line of equal travel time. Pick a city and walking or driving, tap any street, and the page runs Dijkstra's algorithm from the nearest junction with no destination at all: it settles every junction in order of travel time and stops once the next one is more than 20 minutes away. Walking is street length at 5 km/h; driving uses each street's speed with city traffic plus the delays for signals, stop signs and give way, the same costing as open shortest path, whose map loader this page shares. Playback replays the wave spreading out, and a glowing ring lights up at 5, 10, 15 and 20 minutes, in rainbow order from the centre. The plate under the title names the city in English and in its own language and script, and follows every new start.

To turn junction times into an area, every reachable street is sampled along its length with an interpolated time, and each sample claims the grid cells within a short walk of it (50 m on foot, 100 m from a car). That gives a travel-time field over the city; the rings are its contours, traced with marching squares, and the area is a count of cells. All of this runs in a Web Worker: the street graph is copied to the worker once (it keeps the last three), and each start sends back the junction times, the field, the area curve and the four rings as transferred buffers, so even a 20 minute drive across Madrid never freezes the map while you pan or zoom. The compute card says where it ran; if workers are unavailable, the same code runs on the page. Each city you run is kept for the visit, so the area against minutes chart compares them: a grid grows like a diamond, a lagoon or a river bites chunks out of the rings, and a fast road throws out a long finger of reach. A start point comes from the seed, so ?seed= (with ?city=, ?mode= and ?at=lat,lon) reproduces a run.

t(v) = min over arcs u→v of t(u) + c(u, v), settled in increasing t until t > 20 minc(u, v) = length / 1.39 m/s walking, or street time + junction delay drivingT(p) = min over street samples s of t(s) + |p − s| / 1.39 m/sA(τ) = cell² · #{ cells with T ≤ τ }cost: O((V + E) log V) for the V junctions inside the 20 minute ring, off the main thread
20 min15105
Rings of equal travel time from one junction: contours of the field T.
  • Try: run Venice walking, then Madrid or London driving, and watch the area chart split apart.
  • Try: tap a street right next to a river or the sea and see the rings flatten against it.
  • Try: pick Madrid driving and drag the map around while it computes: the page stays smooth.

Live sky#

mapsmaths

The sky above any city I've been to, right now. Stars have fixed right ascension α and declination δ; the local sidereal time says how far the sky has turned, so each star's height and compass bearing follow from one spherical triangle. The Moon and planets come from Kepler orbits: six orbital elements per planet that drift slowly with time (Paul Schlyter's set). Kepler's equation turns the evenly growing mean anomaly M into the eccentric anomaly E, which gives the position on the ellipse; rotating by the node, inclination and perihelion puts it in ecliptic coordinates, and subtracting the Earth's position shows it from here. Jupiter and Saturn also pull on each other: their periods are close to 5:2, and that near resonance, the great inequality, swings Saturn by up to 0.8° and Jupiter by 0.3° over about 900 years. A dozen sine terms in their mean anomalies, added to each planet's longitude (and Uranus's), correct it, so the 2020 great conjunction now lands within about three hours of the published time instead of fifteen, and the 1623 one on the right day at the right 5' separation.

The Sun gets more care, because its place sets the seasons. The Earth's orbit comes from an abridged VSOP87 series (about 190 periodic terms in powers of τ, millennia from 2000), run on Terrestrial Time via ΔT (about 69 s now). That is the geometric longitude; where the Sun appears needs two more steps. Nutation, a wobble of the Earth's axis driven mostly by the Moon's 18.6-year node cycle, moves the equinox by up to 17" (Δψ) and tilts the equator by up to 9" (Δε), so RA and Dec use the true obliquity ε = ε₀ + Δε. Aberration, from the Earth's 30 km/s orbital speed, shifts the Sun back 20.5". Together they are worth up to a quarter of an hour of the Sun's motion: the March, June, September and December points of 2024 to 2027 now land within a minute of the published times, where they used to come up to 17 minutes early. The Moon and planets get the same nutation and true obliquity, and sidereal time adds the equation of the equinoxes, Δψ cos ε.

The solar system view uses the same heliocentric positions, seen from far above the Sun's north pole: real orbits as faint rings, lit planets, distances from the Sun and Earth, and how long light takes to cross them. Orbits are squashed to the square root of their size so Mercury and Neptune fit together, or shown at true scale. The meteor shower calendar lists the major annual showers with their peak, radiant, ZHR and speed, and checks whether each radiant is above the horizon during tonight's dark hours for the chosen city; the rate you see is roughly ZHR × sin(radiant height). The Earth meets each dust trail at the same point of its orbit, so each peak is a solar longitude λ☉ (140.0° for the Perseids); its calendar date drifts about six hours a year and jumps back after a leap day, so the page solves for the moment the Sun gets there each year. That solve uses the geometric longitude referred to the fixed J2000 equinox, not the apparent one: the tables describe where the Earth is in its orbit, which neither aberration nor the wobble of the equator changes. On the map, names never overlap: planets, then stars from brightest down, then constellations each try a few spots round their point and skip any that would cover a name already placed, so a phone shows fewer names and a big screen nearly all of them. The constellation quiz lights up one constellation among its neighbours (a stereographic projection about its centre) and asks for its name from four nearby ones. open live sky, and see the day and night line from space in open atlas.

M = M₀ + n · t, n = 360° ÷ PM = E − e · sin E (solved by Newton's method)x = a(cos E − e), y = a · √(1 − e²) · sin EP² ∝ a³ (P in years, a in AU)Δλ_J ≈ −0.332° · sin(2M_J − 5M_S − 67.6°) − 0.056° · sin(2M_J − 2M_S + 21°) + …Δλ_S ≈ +0.812° · sin(2M_J − 5M_S − 67.6°) − 0.229° · cos(2M_J − 4M_S − 2°) + …L = Σₖ τᵏ · Σ Aᵢ cos(Bᵢ + Cᵢ τ), λ_geo = L + 180° (VSOP87)Δψ ≈ −17.20″ sin Ω − 1.32″ sin 2L☉ − 0.23″ sin 2L☾ + 0.21″ sin 2ΩΔε ≈ 9.20″ cos Ω + 0.57″ cos 2L☉ + 0.10″ cos 2L☾ − 0.09″ cos 2Ωλ_app = λ_geo + Δψ − 20.4898″ ÷ R, ε = ε₀ + Δελ☉(t_peak) = λ_shower, t ← t + (λ_shower − λ_J2000(t)) ÷ 0.98565°/daysin h = sin δ · sin φ + cos δ · cos φ · cos H, H = LST − αrate ≈ ZHR · sin h
  • Try: scroll to How it works on Live sky: ten steps from clocks to the Sun's nutation, each with this sky's own numbers filled in, and a Kepler orbit you can stretch until Newton's method needs five steps.
  • Try: press O to play the orbits and watch Mercury lap the Earth; switch to "Inner planets, true scale".
  • Try: pick the Geminids and see their radiant rise over your city, then compare the map's names on a phone and a wide screen.

Oscilloscope music#

algorithmsmathsart

An oscilloscope in XY mode moves its dot right as the left channel rises and up as the right channel rises. So to draw a shape with sound, walk once around it at a steady pace and send x to the left speaker and y to the right one, many times a second. The page does this for a circle, a star, a heart, Lissajous figures, text traced to its letter outlines, a country's coastline (the same outlines as open Fourier epicycles), a curve from maths or a seed, or anything you draw on the scope. The scope is not drawn from the maths: it reads the sound back from an analyser on each output channel, alongside the two waveforms. open Oscilloscope music

Maths or seed works like open Maths art and uses its parser, which turns the text into a tree of operations, so nothing typed is ever run as code, even from a shared link. x = …; y = … is a parametric curve and r = … (in t or θ) is a polar one. The page checks after how many whole turns of 2π the curve comes back to where it started and walks it once per period in its own timing, or closes it with a straight line if it never repeats; spikes such as tan near its poles are clipped. Any other text, a word, a number or a date, is hashed into a seed that grows a closed curve: spinning harmonics with s-fold symmetry, a rose, a spirograph or a twisted Lissajous figure. The same text always gives the same curve and the link keeps it (?curve=, or ?seed= alone). There are presets, a random button and R, and errors show next to the input while the last good curve keeps playing.

Each channel repeats once per loop, so it is a sum of harmonics, whole multiples of the base frequency f. The page works out those Fourier coefficients and gives each ear its own band-limited wave, with both oscillators started on the same clock so they stay locked. Sound is stored as samples, 44,100 or 48,000 a second, and a harmonic above half that rate (the Nyquist limit) would fold back as a false tone, so it is dropped: sharp corners round off at high notes. Musical mode steps the base frequency through a melody, and spin detunes x slightly so the phase between the ears drifts and the figure turns.

L(t) = x(f·t), R(t) = y(f·t)parametric: (x, y) = (x(t), y(t)), polar: (x, y) = (r(θ) cos θ, r(θ) sin θ)t ∈ [0, 2πT), T = smallest whole number with z(t + 2πT) = z(t), T ≤ 12harmonics seed: z(t) = Σⱼ aⱼ e^(i(nⱼt + φⱼ)), nⱼ = 1 + s·mⱼ (s-fold symmetry)x(t) = Σₖ aₖ cos(2πkft) + bₖ sin(2πkft), k = 1, 2, 3, ...aₖ = (2/M) Σₙ xₙ cos(2πkn/M), bₖ = (2/M) Σₙ xₙ sin(2πkn/M)k·f < fₛ / 2 (Nyquist: at f = 110 Hz and fₛ = 48 kHz, k ≤ 218)fit = 1 - √(power left out / total power)
L = x, R = y
two waveforms in, one shape out
  • Try: pick the star, set harmonics to 4, then raise the frequency and watch the tips round off as the shape fit falls.
  • Try: choose maths or seed, type r = cos(2.5θ) (it needs 2 turns to close), then type your name and press R for random curves.

Visualiser#

musicartmaths

Open visualiser plays music (a built-in synth loop, your own file, a browser tab or the microphone) and feeds it through the browser's analyser, which runs a fast Fourier transform (FFT) 60 times a second. The FFT splits the last 2048 samples into 1024 frequency bins; adding up the bins in a range gives the energy of a band, so bass, mids and highs are just three sums. A kick is counted when the bass jumps clearly above its own running average, and the tempo is the median gap between recent kicks. Every scene is a small piece of maths whose numbers are pushed around by those bands.

Three newer scenes: Fourier epicycles take a closed outline (a star, a heart, a trefoil, a music note), compute its discrete Fourier transform and draw it with a chain of spinning circles, one per term; each circle's radius swells with the band that matches its speed, so the bass bends the big slow circles and the highs the small fast ones (more on epicycles in open Fourier epicycles). Orbiting sun is the landing page orrery: the site's own projects ride two tilted rings round a warm sun that pulses with the bass, and every kick swings the worlds outwards before they settle back. Lorenz attractor integrates the Lorenz equations for several neon riders that start together: loud music takes more steps per frame (faster), and raises ρ, while quiet passages pull the riders back together and kicks push them apart, so the spread of the bundle follows the music. Sound follows the site-wide setting: muting silences the output, but the analysis keeps running so the scenes still dance.

Eₖ = (1/N) Σₙ xₙ e^(−2πikn/N) (FFT bin k of N samples)band energy = Σ_{k=a}^{b} |Eₖ| / ((b − a) · 255)beat when bass > 1.25 · avg(bass) + 0.04, avg ← 0.92 · avg + 0.08 · bassz(t) = Σₖ gₖ cₖ e^(ikt), cₖ = (1/N) Σₙ zₙ e^(−2πikn/N), gₖ = 0.75 + 0.6 · band(|k|)dx/dt = σ(y − x), dy/dt = x(ρ − z) − y, dz/dt = xy − βz (σ = 10, β = 8/3, ρ ≈ 24 + 14 · bass)
Circles riding on circles draw the outline; the music sets each radius.
  • Try: pick Lorenz attractor and stop the music: the riders drift back into one bundle; start it again and watch the kicks split them.
  • Try: Fourier epicycles with the built-in track, then with a song heavy on hi-hats.

Ribbon run#

gamesmusic

Open ribbon run is a rhythm game drawn as one neon line. Obstacles arrive on the beat and you press the matching arrow as each one reaches you; a press within a small window of the beat is a hit, and very close is perfect. With the built-in or a generated track the beats are known in advance. With your own file the whole song is decoded first and run through onset detection: it measures the low-passed energy in short frames, takes the rise from one frame to the next (spectral flux), and marks a beat wherever that rise is a local peak well above its average over the surrounding second. With a tab or the microphone it hears kicks live and predicts the next ones from the median gap between them. The music reacts too: misses detune and muffle it.

Generated tracks are new synth songs in the style of the built-in one, made from a seed: the seed drives a small random number generator that picks the tempo (96 to 140 bpm), the key and mode, a four-chord progression and the kick, snare, hat, bass and arp patterns, and the course is charted on the track's own kicks and snares. The daily challenge hashes today's UK date into the seed, so everyone gets the same song and the same course that day, at normal difficulty; your best for today and your streak of consecutive days played stay in this browser. Music follows the site-wide sound setting. See also open visualiser.

flux(f) = max(0, E(f) − E(f − 1)), onset if flux(f) > 1.8 · mean(flux over ±1 s) and a local peakhit if |t_press − t_beat| ≤ window (0.24 s easy, 0.19 s normal, 0.15 s hard), perfect if ≤ 0.06 sseed = FNV-1a("ribbon-run:daily:" + YYYY-MM-DD in Europe/London)h ← (h ⊕ byte) × 16777619 mod 2³², starting from h = 2166136261streak = number of consecutive days d, d − 1, d − 2, … played, from today (or yesterday if today is still to play)
  • Try: play today's daily run, then share the date with a friend and compare scores.
  • Try: type your name as a generated track seed: the same name always gives the same song.

Trip planner#

toolsmaps

A calendar of days out, saved in the browser. Give a trip its city and each day shows that city's street map from the site's shared map files. Places with a pin (paste coordinates or a map link, or pick a spot on the map) are joined in list order by the shortest walk along the streets. Each pin snaps to the nearest street corner, then A* search runs in a Web Worker: it always expands the corner with the smallest metres walked so far plus the straight-line distance still to go. A straight line is never longer than the real walk, so the answer is the true shortest route while most of the city is never looked at. Each leg shows its distance and a walking time at 5 km/h; places off the map fall back to straight-line haversine distance, drawn dashed.

The trip city's time zone comes from the shared places list. The page shows its local time and how far ahead or behind you it is, and under every timed item the same moment in your own time, worked out for that date so summer time on either side is right. open trip planner, and race the same kind of search across a city in open shortest path.

f(n) = g(n) + h(n), h(n) = straight-line distance to the goal ≤ true distancetime = distance ÷ 5 km/hΔ = offset_city(t) − offset_you(t)your time = city time − Δ
  • Try: set the trip city to Kyoto, pin three temples on one day and reorder them with the arrows to find a shorter walk.
  • Try: paste a Google Maps link with @lat,lon in it as a pin.

Workouts#

tools

Open workouts is a training log that lives entirely in your browser. Each session holds exercises, and each exercise holds sets of reps and kilograms (or minutes and kilometres for cardio). From that it adds up volume (reps times weight), time trained, streaks and which muscle groups you have worked, and paints them on a pixel body.

Strength is tracked with an estimated one-rep max (1RM): the most you could probably lift once, worked out from a set of several reps. Two common formulas are shown side by side. Epley adds a thirtieth of the weight for every rep; Brzycki scales the weight by how far the reps are from 37. They agree exactly at 10 reps, Brzycki reads a little lower below that, and both get less reliable past about 10 reps. A single rep counts as the 1RM itself. Strength over time picks one exercise and draws, as step lines, the best weight and best estimated 1RM you had reached by each day, so every step up is a new personal best. The calendar shades each of the last 12 months of days by minutes or by volume, in four levels relative to your busiest day; tap a day to see what you did and open it.

volume = Σ repsᵢ × kgᵢ1RM (Epley) = w × (1 + r / 30)1RM (Brzycki) = w × 36 / (37 − r), r < 37best by day t = max over days ≤ t of 1RMshade level = ⌈4 × v / vₘₐₓ⌉, 1 to 4
Epleyweight
Running bests step up with each new personal best
  • Try: log a few sets of the same lift on different days, then pick it in Strength over time and watch the steps.
  • Try: switch the calendar between minutes and volume, and use the arrow keys to walk through the days.

Stumble#

web

Open stumble drops you on a random corner of the web. When you press the button, a small Worker on this site picks a source at random: Hacker News (mostly Show HN posts), Lobsters, or Wiby, a search engine for small old-school personal sites. You can switch any of the three off; the Worker only accepts those three exact names and uses all of them if the list makes no sense. From the chosen source it takes one link.

Before you see the link it has to pass several checks. It must be a plain https address with no login details, port or raw IP, not a file download, and not one of a short list of sites that are no fun to land on (social feeds, paywalls, video hosts). The domain is then looked up through Cloudflare's filtered DNS: if the filter refuses to give it a real address, it is known malware or phishing (and, in safe mode, adult content), so it is skipped. The Worker then fetches the page itself, reads its title, description and preview image, and checks again if a redirect took it somewhere else. In safe mode it also skips pages that say they are for adults. If a candidate fails, it tries another, up to six times. Likes, history and your saved-for-later list stay in your browser; the saved list can be exported as a text file.

P(source) = wₛ / Σ wₛ over the sources you picked, w = HN 0.5, Lobsters 0.25, Wiby 0.25P(Show HN | HN) = 0.7tries ≤ 6
  • Try: turn off everything but Wiby for a run of tiny hand-made sites.
  • Try: press B to save a site for later, then export the list as text.