kelvin@cluxnei.dev:~/blog

kelvin@cluxnei:~/blog$ cat emergent-chemistry.md

2026-09-13 EN ~20 min read ler em português

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.

Brass, metallic bonding. Copper and zinc. Both donate electrons, so no molecule ever closes and what grows is a lattice. Hold on to this image: it will contrast with the next two sections, and the only thing that changes between them is the bonding rule.

In this text

  1. What a bond is, from the start
  2. The scenario that does nothing
  3. Ionic: transfer
  4. Covalent: share
  5. Metallic: let go
  6. The geometry that separates the three
  7. Who is even allowed to react
  8. 3D without WebGL
  9. What this simulation is not

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:

F= k q1⁢q2 r2

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.

Noble gases. No bond forms, under any of the three models. There is not a single connected pair on screen, and that is exactly what chemistry predicts.

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.

Why start with the negative. A simulation that produces beautiful structures may be producing beautiful and wrong ones. A case where it has to refuse to do anything is harder to fake by accident than a case where it has to do something.

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:

Na+Cl ⟶ Na+ + Cl−

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.

Table salt, ionic bonding. Small saturated units scattered through the box. Compare it with the brass at the top: same engine, same force, different rule.

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:

2⁢H +O ⟶ H2 O

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:

covalent capacity = tendency

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.

Water, covalent bonding. Groups of two and three atoms, and nothing bigger. Oxygen takes two hydrogens and closes; hydrogen takes one and closes. The saturation comes from the octet, not from an arbitrary cap.

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.

n⁢Cu ⟶ n⁢ Cu+ + n⁢ e−

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.

modelrest lengthstiffnesstolerance
covalent0.720.220.6
metallic0.920.052.5
ionic1.000.081.5

The length is measured in fractions of the contact distance between the two atoms. Reading the table as chemistry:

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.

Periodic soup under ionic bonding: many aggregates formed
Periodic soup, ionic. Ionic accepts any atom with a non-zero tendency, which is nearly everyone except the noble gases.
The same soup under metallic bonding: the screen is emptier
The same soup, metallic. The screen gets emptier, not fuller. Metallic accepts only metals, and in a sample of the whole table they are a minority. The non-metals are left loose because they have nobody to react with under this rule.

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.

k= FOCvz x′= k⁢vx y′= k⁢vy

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.

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:

filetestscovers
bonding.test.mjs25the three models produce different chemistry, the z === 0 invariant in 2D, seed determinism
chemistry.test.mjs12valence, tendency and octet against the periodic table
physics.test.mjs13Coulomb 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.

A note on the videos. They were captured under software rendering, which darkens the scene considerably. They all went through the same levels adjustment, with no cropping. The physics was not touched.

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$