"Computational processes are abstract beings that inhabit computers. As they evolve, processes manipulate other abstract things called data. The evolution of a process is directed by a pattern of rules called a program. People create programs to direct processes. In effect, we conjure the spirits of the computer with our spells. A computational process is indeed much like a sorcerer's idea of a spirit. It cannot be seen or touched. It is not composed of matter at all. However, it is very real."
— Hal Abelson, Structure and Interpretation of Computer Programs
I first heard cellular automata mentioned a couple years ago while working through Lex Fridman's podcast archives. In the flow of a dense conversation, those two words are both familiar enough to slip past without much scrutiny, while at the same time, combined into a phrase, esoteric enough to gloss over as something you'd probably need a CS degree to care about.
But some ideas have a quality of persistence, they work their way up through layers of attention until they insist on being noticed. After the phrase popped up across multiple episodes, it raised itself enough to demand attention. So I googled it (these were the pre-ubiquitous-LLM days), and like everyone else who's ever done so, I ran straight into Conway's Game of Life1.
The page loaded an empty, gray grid. Echoes of Minesweeper. I haphazardly clicked on a few grey boxes, toggling them to yellow, and clicked start. I don't remember what I expected, probably some little animation, maybe a few blinking squares. That's not what happened.
Patterns unfolded. Expanded, contracted and merged. I watched one particular pattern (a Glider) traverse the screen, collide with another structure and annihilate. Three simple rules2 were producing something that felt surprisingly organic.
My first thought was, "This is amazing."
My second thought was, "I want to make that."3
r-pentomino, a Methuselah pattern: a small initial configuration that takes an unusually long time to stabilize into a static or periodic state, often producing complex intermediate behavior and typically ending with a much larger population than it started with.
It didn't take long by the now-defunct standards of those pre-vibe-code days. I built a limited implementation over a weekend of tinkering. And, as goes with first attempts, it wasn't very good, but something about it charmed me. I found physics metaphors falling out of the logic of the system: space, time, causality.
Here's a snippet from the earliest version:
// app.tsx
export default function GameOfLife() {
const [space, setSpace] = useSpace()
const [tick, setTick, flow, setFlow] = useTime()
useSpaceTime(space, setSpace, tick)
const violateCausality = (location, state) =>
setSpace((prev) => {
const next = [...prev]
next[location] = state
return next
})
// ...
}
I've returned to the project to chisel away at the details a few times. With each iteration, the metaphor has done more than just help make the lines of code more legible. It’s started drawing boundaries through a kind of ontology that's made the system as a whole more legible.
// universe.tsx — the current entry point as of the time of writing
export default function Universe() {
return (
<div className={`${css['universe']} ${css['CMBR']}`}>
<Title />
<PhysicsProvider>
<EntropyProvider>
<Field>
<Matter />
</Field>
<ViolateCausality />
</EntropyProvider>
</PhysicsProvider>
</div>
)
}
A number of threads have fallen out of it that feel worth pulling on, but the most compelling one?
I summoned a demon.
As the project evolved, I found myself more interested in the metaphor than the implementation details. I let it inform the code's structure in a kind of concept-led feedback loop. When a function name or variable didn't fit the metaphor, I treated that discomfort as information. Maybe the name was wrong, or maybe the boundary around the functionality was wrong. I would rename a symbol, then adjust the code until the shape of the abstraction matched the pressure of the name. In doing so, the logic became cleaner, the abstractions more well defined, and one particular hook began to emerge as the axis of the program.
But before we visit that point, let's first look at the pieces that had to exist before any summoning could occur. The beginning of all things:
// hooks/use-initial-conditions.ts
const ON = 1
const OFF = 0
type Charge = typeof OFF | typeof ON
type FieldState = Charge[][]
export const useInitialConditions = (dimension: number): Physics => {
const [field, setField] = useState(() => initField(dimension))
const transition = () =>
setField((field) =>
field.map((column, y) =>
column.map((charge, x) => {
const self = { x, y }
const interactions = observe(self, field, dimension)
return evaluate(charge, interactions)
})
)
)
return { field, transition, violateCausality: setField }
}
const observe = (
self: { x: number; y: number },
field: FieldState,
dimension: number
): number =>
[
[-1, -1], [0, -1], [ 1, -1],
[-1, 0], /*self*/ [ 1, 0],
[-1, 1], [ 0, 1], [ 1, 1],
].reduce((acc, [offsetX, offsetY]) => {
const otherX = self.x + offsetX
const otherY = self.y + offsetY
const inD1 = otherX >= 0 && otherX < dimension
const inD2 = otherY >= 0 && otherY < dimension
const inSpace = inD1 && inD2
const otherCharge = inSpace ? field[otherY][otherX] : OFF
return acc + otherCharge
}, 0)
const evaluate = (charge: Charge, interactions: number): Charge => {
if (charge === ON && interactions < 2) return OFF
if (charge === ON && interactions > 3) return OFF
if (charge === OFF && interactions === 3) return ON
return charge
}
These functions raise some surprising questions about the nature of observation. But that's a thread we'll need to save for another time. For now, we need to take a small detour into an adjacent realm of physics.
Entropy comes in different flavors, and if you're unfamiliar with them, making sense of the distinction helps inform where we're headed.
Thermodynamic entropy describes how physical systems move from order to disorder. Think of a cup of hot coffee cooling to room temperature. The heat doesn't disappear, it spreads out. It becomes less concentrated in the space of the cup and more evenly smeared about the room. At different scales, this is the same reason your desk gets messy, stars die, and your knees, on average, ache a little bit more than they did yesterday.
Information entropy, on the other hand, measures unpredictability in symbolic systems. Claude Shannon defined it as a measure of surprise. Imagine a 6-sided die and a coin. You'd be slightly more surprised to guess "2" and see it come up on a roll, than you would be to guess "tails" and see it come up in a toss. Rolling the die has more information entropy. This is the foundation of modern communication and compression.
These seem like very different things. One physical, one abstract. But when you start squinting at them in a certain way, the boundary starts to blur.
In 1867, James Maxwell proposed a thought experiment to probe the Second Law of Thermodynamics, the principle that physical entropy always increases in isolated systems.
He imagined a tiny, intelligent being capable of observing individual gas molecules and operating a door between two chambers. This little demon would watch particles approach the door, measure their velocities, and selectively allow fast (hot) particles to move one way while allowing slow (cold) particles the other way.
Through this sorting, the demon creates a temperature difference, creating order, without expending energy. This result would violate the Second Law.
The modern resolution comes from information theory. Rolf Landauer showed that erasing information incurs a thermodynamic cost; deleting a single bit generates heat.
(There's an actual equation that quantifies this, but I don't have the math to understand it, so I won't pretend to. I'm happy to take Rolf at his word.)
The demon stores information about particles it observes, and eventually that memory has to be cleared. When information is erased, entropy increases, preserving the Second Law.
This insight bridges the two types of entropy. Every bit stored, every computation performed, every process that manipulates information, all of it has a thermodynamic cost.
Landauer's resolution preserved the Second Law and it showcased something about the nature of computation itself. The demon's pattern — observe, compute, update memory, generate heat — isn't specific to sorting gas molecules in a thought experiment. It's a universal pattern of any computational process operating in the physical world.4
So, now we can return to the central point in the program — useMaxwellsDemon:
// use-maxwells-demon.ts
const compute = (entropy: boolean, transition: Physics['transition']) => {
let id: NodeJS.Timeout
if (entropy) id = setInterval(transition, ENTROPIC_STEP)
return () => clearInterval(id)
}
export const useMaxwellsDemon = (
transition: Physics['transition']
): [boolean, React.Dispatch<React.SetStateAction<boolean>>] => {
const [entropy, setEntropy] = useState(false)
useEffect(() => compute(entropy, transition), [entropy, transition])
return [entropy, setEntropy]
}
If you consider this hook from the perspective we’ve been building, a familiar pattern can be found. To see it, trace the call stack.
The demon in this hook lies dormant until entropy flips to true,5 then the demon begins to repeatedly call compute. The side effect of this computation is a call to the transition function, which allows the system to progress from the current state to the next.
The observe function examines neighboring particles, counting interactions. The evaluate function applies Conway's rules to these results to determine each particle's state. Finally, setField updates the system's memory with the next configuration.
The metaphor describes the underlying structure.
observe → evaluate → update memory
From a functional perspective, the distinction between Maxwell's original demon and useMaxwellsDemon starts to collapse. If the demon is defined by its functional role, rather than its substrate, then useMaxwellsDemon doesn't just resemble the thought experiment, it instantiates the same abstract machine Maxwell and Landauer described. The substrate differs, but the causal structure is the same. This function call is an entropic spell cast over the rest of the program.
Both processes convert information entropy into physical entropy. They gather data about a system's state, compute based on that information, and generate heat as an inevitable byproduct.
useMaxwellsDemon is not Maxwell’s demon in the narrow thermodynamic sense. It does not sort molecules or reverse a temperature gradient. But it does instantiate the broader pattern exposed by the demon: observation, rule-governed evaluation, memory update, and physical cost. The metaphor becomes useful precisely where the abstract transition function meets the material machine running it.
"Words are pale shadows of forgotten names. As names have power, words have power. Words can light fires in the minds of men. Words can wring tears from the hardest hearts. There are seven words that will make a person love you. There are ten words that will break a strong man's will. But a word is nothing but a painting of a fire. A name is the fire itself.”
— Patrick Rothfuss, Name of the Wind
In programming, as in myth, calling a thing by its true name taps into power.
For much of this repository's history, the functionality we've been exploring here was rolled up inside a hook called useEntropy, which is a perfectly obvious name for a custom hook that wraps useState and returns [entropy, setEntropy].
But that name conflated two discrete concepts. It lumped together the information entropy of the simulation with the thermodynamic cost of running it. Letting those two concepts run together hid the specific principles coming into contact at that point in the program. This ambiguity was always there. I could sense it in the way that the metaphor never felt quite right, but I couldn't put my finger on it.
It wasn't until I revisited Maxwell's thought experiment while wrestling with this tension that the problem of the entropy distinction started to dawn on me.
I speculatively renamed the symbol, stepped back to take in the change, and noticed the metaphor take on a more coherent shape. The name changed the way I understood the program.
entropy.useMaxwellsDemon does more than allow a user to toggle state and run the program. It mediates the relationship between abstract information and real-world effect.
The tempting question is which direction the arrow points: does computation ride on entropy or does entropy fall out of computation as a side effect?
I think framing it that way assumes the wrong sort of relationship, though. In a running program, there is not first an abstract computation and then, somewhere downstream, a physical cost. Nor is there merely heat without some meaning. There is one event available under two descriptions.
Inside the program, transition observes a symbolic field, applies the rules, and writes the next field into memory. Outside the program, charges move and some energy is dissipated as heat. The first description is informational. The second is thermodynamic. They are distinct, but not separable.
This is the loop I was reaching for. The computation provides a description of how the universe can evolve; entropy is the signature of that evolution. Without one, you can't have the other.
Abelson calls computational processes "abstract beings" — invisible agents animated by rules we write. It sounds a little over the top, but I'm convinced that there's something to it.
What this project revealed is that computation isn't some ghostly abstraction floating above the physical world. It costs energy. It creates heat. It produces entropy. That is: it does work. And the act of writing a program, of specifying a pattern that can evolve and manipulate symbols, is a way of directing that work. Of describing structure that has a bearing in the physical world.
Authoring a program that describes a causal pattern begs uncomfortable (or fascinating, depending on where you stand) questions about the degree to which we are subject to such abstract principles ourselves, running on rules we're fundamentally unable to see beyond.
None of this is news to physics. I've heard many of these ideas described and described well6. But arriving at it this way — line by line, function by function — made it feel real in a different way. It made the ideas less abstract and taken on authority, more earned and constructed. This project allowed me to connect some dots that were previously relegated to the unsatisfying realm of vague intuition.
What started as an exploration into cellular automata became something stranger and more satisfying. The symbols that compose the program are not just informational representations of a metaphor, they're causal bindings on physical reality. Demons, these thermodynamic mediators between information and heat, aren't unique to Conway's Game of Life or Maxwell's original thought experiment, they're the signature of Landauer's principle. They are everywhere, have always been everywhere, orchestrating every computational process. Most remain unnamed, hidden in plain sight behind familiar labels like useState, event loop, or "I think, therefore I am."7
To borrow from Abelson one last time: when useMaxwellsDemon is invoked, it's not simulating a demon, it's employing one. It runs on your laptop, it heats up your room, and in that heat, we feel the weight of its spell.
Play this Game of Life implementation here, or a more robust version here. ↩
Rule One: A live cell with less than 2 neighbors dies (loneliness)
Rule Two: A live cell with more than 3 neighbors dies (overpopulation)
Rule Three: A dead cell with exactly 3 neighbors comes to life (reproduction) ↩
I'm using "demon" here not to refer to Maxwell's specific particle-sorting mechanism, but to name the thermodynamic-informational bridge that Landauer revealed exists in all computation—the inescapable relationship between information processing and heat generation. ↩
In the application, entropy is not merely a measurement of disorder inside the field. In code, it's a boolean switch: when false, the universe is a static configuration; when true, the demon begins applying the transition function. Conceptually, though, that switch marks the contact point between the simulation's informational entropy, states being observed, evaluated, and overwritten, and the thermodynamic entropy produced by the material machine doing that work. I started pulling on this thread in a separate draft note, Time's Bootstraps, about how temporal sequence emerges between static configurations in the Game of Life. ↩
One of Joscha Bach's many descriptions of software as spirit. In a similar way that the renaming of useMaxwellsDemon was a pivot point in the application, Joscha's reference to the Abelson quote in that talk was deeply influential for the shape of this essay. ↩
The leap from JavaScript to Descartes requires accepting a computational theory of mind—the view that consciousness itself arises from computation. While current technologies seem to be hinting that this debate may one day be settled, it's still a contentious claim. But it also seems to me the only ontology that can actually work. If thought is computation, and all computation follows the thermodynamic pattern Landauer described, then even our most fundamental certainty—"I think"—is another expression of this entropic process. ↩