kelvin@cluxnei:~/blog$ cat emergent-chemistry.md
I did not program water
Nowhere in the code is there a line saying that water is H₂O. What there is, is the octet rule. Water is what the octet rule produces. This text opens up a chemical bonding simulation and shows three bonding models that are not labels over the same physics: each one changes what the atoms do, and the structures appear on their own.
In this text
What a bond is, from the start
The whole simulation runs on a single force, the same one that holds a magnet to a fridge and keeps an electron bound to a nucleus: electrostatic attraction. Coulomb's law has exactly the shape of Newton's gravitation, with charge in place of mass:
q are the charges and k the Coulomb constant. Opposite charges attract, like charges repel, and the strength falls with the square of the distance. In the simulation k is tuned, because the point is to watch chemistry happen in seconds rather than at true scale.
But electric charge does not explain why sodium wants to give an electron away and chlorine wants to take one. That comes from the valence shell: an atom's outermost electron shell. Atoms behave as though they want that shell full, which almost always means eight electrons. That is the octet rule.
Out of that rule comes the number the code calls tendency: how many electrons are missing or left over to complete the shell. Its sign decides everything that follows.
tendency > 0 wants to take non-metals: H, O, N, C, Cl tendency < 0 wants to give metals: Na, Cu, Zn, Al tendency = 0 wants nothing noble gases: He, Ne, Ar, Kr, Xe, Rn
An atom does not choose a bonding model: the model follows from who is touching whom. Two that want to take will share. One that wants to give and one that wants to take will transfer. Two that want to give have nothing to trade with each other, and a third arrangement resolves it.
Those three arrangements are what the simulation implements, and this is where it stops being only ionic.
The scenario that does nothing
Before looking at what happens, it is worth looking at what does not happen. One of the ten scenarios is the noble gases, helium through radon, and it does absolutely nothing. The atoms collide and bounce, and that is all.
The detail that matters: this is not a special case in the
code. There is no if (nobleGas) return; anywhere. Each
model's eligibility is a single question, asked about the tendency:
ionic requires tendency ≠ 0 covalent requires tendency > 0 metallic requires tendency < 0
A noble gas has tendency exactly zero. It fails all three as an arithmetic consequence, not through a hand-written exception. The nothing is emergent in the same way the molecules are, and that is why it works as proof: if the rule were wrong, something would bond here.
Ionic: transfer
Sodium has one electron spare in its outer shell. Chlorine has one vacancy. Sodium hands it over, chlorine accepts, and now both are electrically charged with opposite signs:
There is real electron transfer in the model: the net charge of both changes, and from there it is Coulomb's law from earlier that holds the pair together.
And here is the first consequence nobody programmed. The moment sodium hands over its electron, its tendency becomes zero. It stops being eligible for a second ionic bond. The same goes for chlorine, which has completed its shell.
In other words: ionic bonding saturates on its own. Salt does not grow into a lattice, it stops at the pair.
Covalent: share
When both atoms want to take, nobody transfers anything. Each bond becomes a shared pair that counts toward both octets at once. Net charge stays at zero, because no electron changed owner:
Two hydrogens and one oxygen. Not because somebody wrote "water", but because oxygen has two vacancies and hydrogen has one.
This is the part I was most pleased to see working. The limit on how many bonds each atom accepts is not in a table. It is read from the tendency, which is the octet's own count of vacancies:
One line of code. And the whole of valence falls out of it:
H 1 vacancy → 1 bond O 2 vacancies → 2 bonds N 3 vacancies → 3 bonds C 4 vacancies → 4 bonds
Those are the numbers everyone memorises at school. Here they were never written down: they are the same variable that already existed to decide who gives and who takes, read a different way.
Metallic: let go
Two metals touching have a problem: both want to give. No transfer is possible and there is no pair to share. Nature's way out is a different one: the valence electrons simply leave, forming a delocalised sea that belongs to the whole, and the nuclei become cations immersed in it.
The electrons do not go to a specific atom: they go to the collective. That is why metal conducts electricity and is malleable.
The structural consequence is the most dramatic of the three. Because the bond is non-directional and has no coordination limit, every metal that touches joins the same aggregate. There is no "brass molecule". There is a lattice that grows for as long as atoms are available.
That is exactly what the video at the top shows, and it is the opposite of salt, which saturates at the pair. The difference between those two screens is a question about the sign of the tendency.
There is a fine detail in the code worth quoting, because it is easy to get wrong: metallic eligibility looks at the neutral atom's tendency, not the current one. A cation that has already given up its electrons has tendency zero and still remains part of the lattice. Had the check been on the current tendency, the electron sea would fall apart the instant it started working.
The geometry that separates the three
Each model also has its own geometry, and it is not decoration: it is what makes the three screens look different before you have counted how many atoms are in each group.
| model | rest length | stiffness | tolerance |
|---|---|---|---|
| covalent | 0.72 | 0.22 | 0.6 |
| metallic | 0.92 | 0.05 | 2.5 |
| ionic | 1.00 | 0.08 | 1.5 |
The length is measured in fractions of the contact distance between the two atoms. Reading the table as chemistry:
- Covalent is the shortest and by far the stiffest, with the least slack. Sharing a pair of electrons pulls the nuclei close and does not let the bond wobble. That is why it is directional and produces molecules with a definite shape.
- Metallic is the loosest of the three, with stiffness
0.05and tolerance2.5. The atoms slide over one another without breaking the bond. That is malleability, which is literally the property of deforming metal without snapping it. - Ionic sits in the middle, and rests at exactly the contact distance.
Who is even allowed to react
One scenario throws the entire periodic table into a single box. It is useful for showing something the dedicated scenarios hide: the bonding model does not only change how atoms join, it changes who is allowed to take part at all.
This cost me a failed attempt while building this post. I wanted to use the soup as a controlled comparison of the three models, but it mixes two variables: who can bond, and how the structure grows. The image would suggest that metallic aggregates less, when in fact it aggregates far more, just with fewer eligible atoms. The dedicated scenarios exist precisely to isolate one thing at a time.
3D without WebGL
The simulation has a 3D mode, and it uses neither three.js nor WebGL.
engine3d.js projects the scene by hand onto the same 2D
<canvas> as everything else.
The maths is classic perspective: a point in camera space is divided by its own depth, so the further away it is, the smaller it gets.
FOC is the focal distance and v the point already in camera space. Points with vz below the near plane are rejected, otherwise the division blows up and the object shows up inverted behind the camera.
The rest is an orthonormal basis assembled per frame, depth ordering so things are drawn back to front, and an area cap on the backbuffer.
It is a few hundred lines. It could be one line of
import * as THREE, and the reason it is not is the same reason the
project exists: the fun is in understanding the arithmetic. Worth noting the
contrast with the universe
simulation, which uses three.js without embarrassment, because there the
hard problem was the physics and not the drawing.
What this simulation is not
As in the previous piece, the part that usually goes missing. This simulation is not quantum chemistry, and the list of what it does not do matters for what it claims.
- There are no orbitals. The octet rule is implemented as a count of vacancies, not as the filling of energy levels. There is no hybridisation, so the bond angles of water or methane do not come out right by construction.
- There is no real bond energy. Stiffness is a spring parameter tuned so the scene looks right, not an enthalpy in kJ/mol.
- There is no real temperature. A scenario parameter sets initial velocities, but there is no thermal equilibrium and no Maxwell-Boltzmann distribution.
- Radii are fixed per element and do not respond to the ionisation state, even though in reality a cation shrinks considerably when it loses its outer shell.
What it does, and what this post argues, is something else: showing that three simple rules over a single variable, the tendency, produce on their own the saturation of salt, the valence of water and the lattice of brass. It is an argument about emergence, not a quantitative prediction.
How to know it is not luck
Every scenario is seeded. The same seed gives the same scene, in 2D and in 3D, which makes the behaviour arguable rather than anecdotal. And there are 50 tests across three files, on Node's own test runner, with no framework:
| file | tests | covers |
|---|---|---|
bonding.test.mjs | 25 | the three models produce different chemistry, the z === 0 invariant in 2D, seed determinism |
chemistry.test.mjs | 12 | valence, tendency and octet against the periodic table |
physics.test.mjs | 13 | Coulomb forces, elastic collisions, energy at the walls |
One of them strikes me as the most honest of the set: "switching bond type recalculates the atom's chemistry". It exists to guarantee that the three models are not three appearances of the same thing.
The simulation lives at cluxnei.dev/atomic-ionic-reactions/, runs entirely in the browser, and lets you change scenario, bonding model and seed before it starts. The source is on GitHub.
kelvin@cluxnei:~/blog$