Rime Sparse , 366 particles orbiting an unreachable centre. Press volta to trigger a pull. Scatter to break. Reset to begin again.
In 1327, on the morning of Good Friday, Francesco Petrarch saw a woman in the church of Santa Chiara in Avignon. He spent the next twenty-one years writing poems about her: 366 of them, one for every day of a leap year, plus a closing prayer to the Virgin. He never spoke to Laura directly. He was not sure she knew his name. The entire arc of the Rerum vulgarium fragmenta (the Canzoniere, the scattered rhymes) is the record of a man held in permanent orbit around something he cannot reach.
I made Rime Sparse as a translation of that structure into code. Each of the 366 particles is a poem. They move through a continuous vector field, pulled periodically toward the centre (Laura, gravity, the irresistible) and repelled at the last moment by a force that ensures they never arrive. The mechanics underneath that image draw on four distinct traditions in computational physics and generative art, each with a specific history worth tracing.
Every run of Rime Sparse begins with the same number: 14031327. This is not arbitrary. Petrarch was born in 1304. He saw Laura in 1327. The seed encodes both dates concatenated.
The mechanism that seeds controls is a xorshift32 PRNG, introduced by the mathematician George Marsaglia in his 2003 paper Xorshift RNGs, published in the Journal of Statistical Software. Marsaglia was hunting for the smallest, fastest pseudorandom generators that still passed rigorous statistical tests. His insight was that three cheap bitwise operations (XOR, left shift, right shift), applied in the right order to a single 32-bit integer, could produce a full-period sequence of 2³² − 1 values with excellent statistical properties. No multiplication, no division, no lookup tables.
// xorshift32 — Marsaglia, 2003
let _s = 14031327; // seed: Petrarch born 1304, saw Laura 1327
function rng() {
_s ^= _s << 13;
_s ^= _s >> 17;
_s ^= _s << 5;
return (_s >>> 0) / 4294967296; // normalize to [0, 1)
}
The three constants (13, 17, 5) are not random. Marsaglia enumerated all triplets that produce maximal-period sequences and published a table. These three are among the most cited. The >>> 0 coercion forces JavaScript to treat the result as an unsigned integer before dividing, avoiding the sign-extension problem that would otherwise corrupt the normalization.
The importance of the seed for generative art is deep. Tyler Hobbs, writing about his 2021 Art Blocks series Fidenza, described the seed as the artwork's true identity: every piece in a long-form generative series is the same algorithm instantiated with a different starting value. Two seeds produce two different compositions. The seed is the work's permanent name. Fixing it to 14031327 means every instance of Rime Sparse begins from the same scatter of particles, the same arrangement Petrarch would have seen on the morning of Good Friday, 1327, if the particles were the poems he had not yet written.
The particles do not move randomly. They follow a flow field, a function that returns a direction vector at every point in the canvas and changes slowly over time. The tradition here begins with Ken Perlin.
In 1983, Perlin was working on Tron at Information International Inc. and was frustrated that computer-generated surfaces looked "machine-like," too clean, too regular. He developed a gradient noise function that produced smooth, continuous, organic variation across space. He presented it at SIGGRAPH in 1985 in his paper "An Image Synthesizer," and received an Academy Award for Technical Achievement in 1997 for its impact on visual effects. Perlin noise is now the substrate beneath nearly every procedurally generated terrain, cloud, fire, and marble texture in film and games.
Flow fields built from Perlin noise were formalized as a generative art technique by Hobbs, though the underlying physics comes from fluid dynamics simulation, using the same techniques applied to simulate smoke, hair, and crowd movement in film production. You sample the noise function at the particle's position to get an angle, then use that angle to push the particle. Different noise scales produce different behavior: fine-grained noise creates turbulent micro-flows; coarse noise creates broad, slow sweeps.
Rime Sparse uses a different approach: a purely analytical vector field built from six superimposed harmonic layers. This avoids the dependency on a noise library and encodes the sonnet structure directly:
function flowAngle(x, y, t) {
const nx = x / W, ny = y / H; // normalize to [0, 1]
// First quatrain (ABBA) — broad sweeping waves
const a1 = Math.sin(nx * 3.77 + t * 0.13) * Math.cos(ny * 2.31 + t * 0.09);
const a2 = Math.sin(ny * 4.71 + t * 0.11) * Math.cos(nx * 1.88 + t * 0.17);
// Second quatrain (ABBA) — spiralling harmonics
// Spatial frequencies seeded from L=12, A=1, U=21, R=18, A=1 (LAURA)
const a3 = Math.sin(nx * 0.84 + ny * 1.26 + t * 0.15) * 0.72;
const a4 = Math.cos(nx * nx * 6.0 - ny * ny * 4.5 + t * 0.21) * 0.52;
// Sestet (CDE CDE) — counter-motion, longing
const a5 = Math.sin((nx + ny) * 5.24 - t * 0.19) * 0.36;
const a6 = Math.cos((nx - ny) * 3.14 + t * 0.23) * 0.28;
return (a1 + a2 + a3 + a4 + a5 + a6) * Math.PI;
}
There are fourteen components in total, one per line of the Petrarchan sonnet. The field is grouped into three structural layers matching the sonnet form: two quatrains and a sestet. The spatial frequencies in the second quatrain are derived from the letter values of LAURA (L=12, A=1, U=21, R=18, A=1), scaled to sit in a range that produces visible curvature at the canvas scale. These references are invisible to anyone who doesn't know to look. Someone who does will feel them as a rightness, the way jazz musicians recognize a quoted melody embedded in an improvisation.
Moving a particle through a vector field requires integrating its position over time. The method matters: different integrators produce different behaviors, and the choice encodes something aesthetic as well as technical.
The most naive approach is explicit Euler integration: compute acceleration from forces, add it to velocity, add velocity to position. This is first-order and accumulates error quickly; in long-running particle systems, explicit Euler causes energy to drift upward, making simulations "heat up" over time, particles accelerating past their intended speeds.
Rime Sparse uses semi-implicit Euler: velocity is updated first, then position is updated using the new velocity instead of the old one. This reversal is a small change that substantially improves energy conservation; semi-implicit Euler conserves a modified form of energy exactly, which means the simulation neither overheats nor drains.
// Semi-implicit Euler integration
// velocity updated first, position from new velocity
const damp = Math.pow(0.92, dt * 60); // frame-rate-stable exponential decay
p.vx = p.vx * damp + (fx + ax) * dt; // flow force + volta attraction
p.vy = p.vy * damp + (fy + ay) * dt;
p.x += p.vx * dt; // position from updated velocity
p.y += p.vy * dt;
The damping term deserves attention. A naive implementation would write p.vx *= 0.98, multiplying velocity by a constant every frame. But this ties the damping rate to the frame rate: at 60 fps a particle slows at one rate; at 120 fps it slows twice as fast. The correct form is exponential decay with a time-normalized exponent: Math.pow(factor, dt * 60). The dt * 60 rescales the per-second decay rate to the actual elapsed time, ensuring behavior is identical regardless of refresh rate. This is a subtle bug that afflicts most tutorial implementations and produces simulations that behave differently on different hardware. Getting it right is the difference between something that feels like physics and something that feels like code.
The Petrarchan sonnet is defined by a structural turn, the volta, that occurs between the octave (first eight lines) and the sestet (final six). Before the volta, the poem establishes a problem, an image, a feeling. After it, the poem pivots: a counter-argument arrives, an emotional reversal, a sudden shift in register. The volta is the moment the poem becomes more than description. It is where the sonnet earns its form.
In the Canzoniere read as a whole, every poem is itself a volta, a turn toward Laura, a moment of longing that is also a moment of knowing longing will not be satisfied. The collection's two-part structure (poems written during Laura's life, poems written after her death) is the largest volta of all: the first half reaches toward the beloved; the second half discovers she was never reachable in the first place.
The simulation's volta is a periodic pulse that draws every particle toward the centre. It fires automatically every fourteen seconds (the number of lines in a sonnet) and can be triggered manually. The pull is modeled as a simple attractive force scaled by the volta's strength:
// Volta: sinusoidal pulse, period = 14 seconds
function voltaStrength(t) {
const manualAge = t - manualVoltaTime;
if (manualAge >= 0 && manualAge < VOLTA_PERIOD) {
return Math.sin((manualAge / VOLTA_PERIOD) * Math.PI); // manual override
}
const phase = (t % VOLTA_PERIOD) / VOLTA_PERIOD;
return Math.max(0, Math.sin(phase * Math.PI)); // automatic
}
// Attraction toward Laura — force proportional to volta
const af = volta * 220;
let ax = (dx / dist) * af;
let ay = (dy / dist) * af;
// Counter-repulsion at the centre — the beloved is unreachable
const repelR = Math.min(W, H) * 0.06;
if (dist < repelR && volta > 0.04) {
const repel = (1 - dist / repelR) * 180 * volta;
ax -= (dx / dist) * repel;
ay -= (dy / dist) * repel;
}
The repulsion at the centre is the piece's governing metaphor made mechanical. The closer a particle gets to Laura, the stronger the force that pushes it away. At maximum volta strength, the attraction and repulsion nearly cancel at the repel radius; particles orbit at that distance, never arriving, never escaping. This is not a metaphor added to the code after designing the physics. It is the physics itself. Any attractive-force system requires a repulsive core or every particle collapses to a point. The geometry of unrequited love and the geometry of stable orbits are the same geometry.
The Canzoniere's two-part structure, in vita and in morte, is encoded in the colour. Particles near the centre burn warm amber (Laura living, present, almost reachable). Particles at the edges cool toward azure (Laura gone, mourned, fixed in memory). The transition is continuous, not categorical: every particle's hue is a function of its distance from centre, scaled by the volta's current strength so that during the pull, the whole field warms toward amber (life returning, the beloved briefly present) before cooling again as the volta fades.
function particleColor(x, y, volta, speed) {
const dist = Math.sqrt((x-cx)*(x-cx) + (y-cy)*(y-cy)) / maxR;
let tc = Math.min(1.0, dist);
tc = tc * (1 - volta * 0.78); // volta draws toward vita / warm
// vita amber: r=248 g=192 b=72
// morte azure: r=88 g=128 b=220
let r = 248 + tc * (88 - 248);
let g = 192 + tc * (128 - 192);
let b = 72 + tc * (220 - 72);
const boost = 1 + volta * 1.85; // white flash at volta peak
r = Math.min(255, r * boost);
g = Math.min(255, g * boost);
b = Math.min(255, b * boost);
...
}
This is not a novel technique. Color-as-field-annotation goes back at least to Harold Cohen's AARON in the 1970s, and to the data-mapping traditions of scientific visualization. What is specific here is the choice of axes: the vita/morte axis, a literary structure transplanted into colour space, keyed to vita and morte instead of velocity or simple distance. The colour says what the motion implies.
Petrarch invented something with the Canzoniere that had not existed before: a lyric sequence in which the poems accumulate into a portrait of a consciousness over time. He spent twenty years revising it. The final manuscript, left unfinished at his death in 1374, contains 366 items arranged with great deliberateness: 366, the count of a leap year, the count that makes the collection feel slightly uneven, one day longer than it needs to be. Scholars have argued about whether that extra poem is a sign of incompleteness or a structural choice. I think he knew exactly what he was doing: the feeling of having one poem too many, one fragment that doesn't resolve, is the feeling of being Petrarch.
The simulation cannot reproduce that feeling precisely. It reproduces the mathematics of it: the pull toward an unreachable centre, the periodic recurrence of longing, the way the fragments distribute themselves across a field they cannot escape. Particle 366 drifts the same as particle 1; they don't know each other. The structure emerges from the field, not from any individual element's knowledge of the whole.
Which is, again, exactly how the Canzoniere works.
Rime Sparse
366 particles (one per poem in Petrarch's Canzoniere) orbiting an unreachable centre. Press volta to trigger a pull. Watch the amber warm toward the centre, cool toward the edges, fade again.
View artifact →