One number per lantern
The lanterns demo hangs six thousand lights over a landscape. Every colour, every flicker, every bob comes from one whole number that each lantern carries. This page is about that number: where it comes from, what the graphics card asks of it, and what breaks when it is kept in the wrong kind of box.
every colour a lantern could be →
The idea
A lantern is one number wearing a costume.
Hand a lantern the number 3 274 118 905 and it becomes a particular shade of orange, pulsing at a particular speed, slightly out of step with the lantern next to it. Hand it a different number and it becomes a different lantern.
Nothing is stored about the colour. Nothing is stored about the flicker. There is only the number, and a rule for reading it — and that is a very good deal, because a number is tiny. Six thousand lanterns is six thousand numbers, twenty-four kilobytes, and the graphics card works out everything else while it draws.
The library never picks a colour. It picks a number, and the number picks the colour.
Two halves
One half says where. The other says when.
pcg-ts builds the field. It works out where each lantern hangs, how big it is, and which number it carries — and then it stops. It has no clock at all, on purpose, so nothing it produces can move. The same seed lays out exactly the same field on any machine, today or next year.
The web page picks it up from there. The page owns the clock, and the page owns the little program that runs on the graphics card. Between the two halves there is one wire, and the only thing travelling down it is that one number per lantern.
The number
Nobody invents it. Every point already had one.
A pile of points in pcg-ts is not just positions. Every point carries a little row of named
values, and one of them is called seed — the point's name badge.
It is a scramble of where the point came from: which node made it, which number it was in the
queue, and — in a streamed world — which patch of ground it fell on. Scramble the
same things on another machine and you get the same badge, which is the currency the library's whole determinism claim is
paid in.
Reading the badge
Three questions, asked of three slices of the same number.
A 32-bit number is thirty-two yes-or-no switches in a row. The little program on the graphics card does not use it as a quantity at all — it reaches in and reads slices, the way you would read a phone number as an area code and a line.
The catch
A float cannot count that high one at a time.
Almost every number a graphics card handles is a float — the kind that can be 0.5 or 3.14. A float buys that flexibility by keeping only about seven digits' worth of detail and then remembering separately how big the number is. Small numbers get counted exactly. Big ones get rounded to the nearest step, and the step keeps doubling as the numbers grow.
The badges are not small numbers. At the demo's default seed, 5 894 of the 6 000 come back from a float changed.
How the demo shows it
Five nodes, and the fourth one is deliberate damage.
pcg-ts is a node graph: little boxes that each do one thing to a pile of points, wired nose to tail. The lantern graph is five of them, and it would be four if it were not trying to prove something.
What you are looking at
The colour is the measurement, not the decoration.
Because the hue comes straight off the small end of the badge, the spread of colour in the field is a readout of how much of the badge survived the trip. Switch the demo's picker to the float column and the field falls in on itself. Four of the five numbers below are the ones the demo's own panel prints, cooked at its default seed. The fifth is counted the same way, off the same two columns:
Two thirds of the field lands on a single hue. Three hues take nearly nine in ten. The strip at the top of this page is that collapse drawn live — and it is not a contrived worst case, it is the ordinary case for a name badge. Try another seed: the numbers barely move.
Destroyed
The colour
- Lives in the last 8 switches — the ones the rounding eats first
- 256 hues in use become about 70
- You can see this happen. That is the point of the demo
Spared
The pulse
- Lives far higher up, where the rounding only reaches by carrying — 3 lanterns in 6,000
- Every lantern still blinks at its own speed, out of step with its neighbours
- Which is the trap
It still twinkles. It is still wrong.
That second column is the reason this demo exists rather than a note in the docs. If the page had scrambled the badge again in the shader instead of reading its switches directly, the broken field would have looked exactly as random as the good one — different, but not visibly different, which is worse. Half of a wrong answer looking right is how a bug like this survives review.
And in this case the same value has to come back tomorrow, on someone else's machine. “Looks right” is not the test. The claim is about the number.
If you are wiring this up yourself
The type has to match at both ends, and it will not fail quietly.
The library hands each named column to the renderer as an attribute of its own, keeping the whole-number type. The page declares it on the other side as a whole number too, and if those two disagree the graphics driver simply refuses to draw — it does not round the badge for you behind your back.
The one thing worth knowing is that the two columns fail in different ways if a graph stops producing them. A missing whole-number column is a hard stop: the page comes up empty. A missing float column reads as zero in silence, and because it is only looked at on the far side of the picker, the demo would look perfect until someone flipped the switch. So the demo names both columns as requirements when it builds the meshes, and gets told which one went missing rather than diagnosing it from whichever picture it happened to draw.
The demo's own source is worth reading for one more reason: the long comment explaining a line that is not there. Common advice says to flag this kind of channel as an integer for the renderer. Measured on this path, the channel is already an integer without the flag, so the flag does nothing — while on a float column that same flag makes the driver reject the draw. It is not dead everywhere: on a yes-or-no channel, which is stored as single bytes, it is the thing that picks the integer path at all. A line that is a no-op in a file people copy is worse than no line.
Next
Where to go from here
The demo itself is at demos/lanterns — the picker is in the panel on the left, under “id source”, and the panel prints most of the numbers this page quotes. Dressing a roadside is the other guide, about a demo that decides what stands where rather than what colour it is. Architecture is how the library underneath is put together, and the corpus gallery is every graph in the repository, cooked and shot.