A fluid simulation where Jev decides every collision
TL;DR: I deleted the collision operator from a lattice-gas fluid simulation and let Jev decide every collision instead. It never violated the laws of physics it was given: 6.3 million calls, momentum conserved every time, energy conserved in 99.8%. The fluid holds up, though a fixed rule with nobody deciding anything matches it, and one sentence of prompt is the difference between water and sand.
There’s only one interesting decision in a lattice-gas fluid simulation. Particles sit on a grid, each carrying one of nine velocities. Every step they hop one cell, and whenever two of them land in the same place, something has to decide what comes out. That decision is the collision operator, and it is where all the physics lives. Everything else is bookkeeping.
My thesis was on GPU solvers for this family of methods, sixteen years ago, so I have written that operator by hand more than once. It is usually a formula: a few lines that relax the local distribution toward an equilibrium you derive on paper. This weekend I deleted it and called Jev instead.
This only works because of how fast Jev answers. One request carries 66 of these decisions, and with a hundred in flight that is thousands of decisions a second, tens of milliseconds each. Slow that down by an order of magnitude and the experiment is not worth starting.
The setup is a 128 by 48 channel with a flat plate across the flow. The grid is cut into 8 by 8 regions. Every frame, for every region, for each of the 33 kinds of particle pair that can meet, Jev picks what the pair becomes. That is 25,000 typed choices per frame, 6.3 million over the 250 frames in this clip.
What it was told
The entire physics context, verbatim:
The fluid is made of many particles on a square grid. Every step, each particle either stays where it is or hops one cell to one of its 8 neighbours. Particles in the same region meet in pairs by chance, more often the more common both kinds are. When two particles meet, they either pass each other or collide and leave in new directions. You decide what happens, for each kind of pair, in each region, as real colliding particles would.
In a collision, particles are neither created nor destroyed, and the total momentum of the pair is the same before and after. Collisions are elastic: the pair’s total kinetic energy is also the same before and after.
No Navier-Stokes, no equilibrium formula, no pressure, no viscosity, no target numbers. Each option is labelled with its effect on the pair’s momentum and energy, and outcomes that break either law are on the menu. Nothing in the code filters the answers; whatever comes back gets applied.
It never picked an unlawful one. 6.3 million calls, momentum conserved every time, energy conserved in 99.8%.
What came out
The standard checks for a lattice fluid, run in a 64 by 64 periodic box. Each isolates one term.
| target | textbook formula | no decider | Jev | |
|---|---|---|---|---|
| pressure per density | 0.333 | 0.333 | 0.333 | 0.331 |
| advection coefficient | 1.00 | 1.00 | 1.00 | 1.00 |
| viscosity | > 0 | 0.167 | 0.195 | 0.193 |
| viscosity moving / at rest | 1.00 | 0.97 | 1.00 | 0.99 |
| viscosity diagonal / axis | 1.00 | 1.00 | 1.69 | 1.70 |
| sound speed | 0.816 | 0.573 | 0.816 | 0.828 |
That third column is the one that matters. It is a fixed rule with no decisions in it at all: every outcome that conserves momentum and energy fires, same rule in every region, nobody choosing. It matches Jev to three digits on every row, and beats it on sound speed and on holding temperature.
Which is the correct result. Conservation plus the symmetry of the lattice is enough to give you Navier-Stokes behaviour in the continuum limit. That is the whole point of lattice gases and it has been known since the 1980s. Any lawful collision rule lands in the same place. The operator only controls the transport coefficients, and on those Jev matches the simplest fixed rule rather than beating it.
One sentence, two materials
Delete the elastic sentence from the prompt and run it again. Momentum still holds every time, but now the collisions bleed energy, and the fluid cools toward a standstill.
What you get is sand. A granular gas, the thing ball bearings do: pressure per density falls from 0.333 to 0.071, 89% of the particles end up at rest, and the viscosity collapses and goes 29 times more direction-dependent. That is the right physics for inelastic collisions. It is not a fluid at a fixed temperature, and nothing in the code knew the difference.
Where the decisions actually mattered
One earlier run separates the model from the fixed rule. There Jev was given a target, pressure equals density over three, and collisions free to gain or lose energy, and the fluid was started in the wrong state: every direction equally common, pressure per density at 0.667 instead of 0.333.
It pulled the fluid to 0.379 in a single frame and held 0.32 from there. A fixed lawful rule cannot do that. It has no target and no way to tell hot from cold, so from that start it sits at 0.667 and stays. Getting to a third means picking, region by region, collisions that drain motion where there is too much and add it where there is too little. That is the one place in all of this where the choosing is doing work that a dumb rule cannot.
Numbers
The plate run: 250 frames, 6.3 million calls, about 60 seconds and 50 cents per frame, $125 and four hours end to end. 384 requests per frame at 100 concurrent, two seconds each at the median. Rate limits, not compute, set the pace; the simulation itself is a few milliseconds a step on one core.
Consistency was the surprise. Millions of independent calls, each seeing only its own region, no memory of the last frame, no filter on the way back in, and the aggregate never drifted into lawlessness. The fluid is a fluid because of conservation, and conservation held because the model followed instructions six million times in a row.
Sixteen years ago I was hand-tuning this operator for the GPU. Now it’s three sentences of prompt, and the wake still shows up.