ECP-Life: from one force law to a battery
Start with particle life's force equation, ask it to conserve energy, and see what you are forced to add. Sections 1–9 use $g = 1/r^2$ so the shapes are familiar; section 10 is the model as actually built (field.html, lab.html in the repo).
The whole thing in four sentences. Particle life is one force law: $\mathbf F_{ij} = f(h_i,h_j)\,g(r)\,\hat{\mathbf u}$, a hue-dependent strength times a distance fall-off. The half of $f$ that is symmetric between the two particles is an ordinary potential force and conserves energy on its own. The half that is anti-symmetric is a "chase" that pushes both particles the same way and pumps energy in from nowhere. To close the books, every joule the chase adds is paid for by changing hues in the direction that lowers a stored hue-mismatch energy, so hue becomes a battery that the chase drains and, when it happens to be braking, recharges.
1. The seed: one force law
Particle life, in one line. Particle $i$ feels, from particle $j$,
$$ \boxed{\;\mathbf F_{ij} \;=\; f(h_i, h_j)\;g(r)\;\hat{\mathbf u}\;} $$where $h$ is each particle's hue (an angle, $0$ to $2\pi$), $r$ is the distance between them, $\hat{\mathbf u}$ is the unit vector pointing from $j$ to $i$, and:
- $f(h_i, h_j)$ is a signed strength: how hard, and in which direction, hue $h_i$ responds to hue $h_j$. Positive means "push $i$ away from $j$", negative means "pull $i$ toward $j$". In classic particle life this is a lookup table over a handful of species; here it is a smooth function of two angles. In the built model it is a tileable Perlin-noise field over the $(h_i, h_j)$ torus (§10); sections 3–9 use the simplest version, a function of the difference $d = h_i - h_j$ only, because it makes the algebra readable.
- $g(r)$ is the distance fall-off. For this whole page take $g(r) = 1/r^2$, gravity-style, so the force has the shape you already know. The code uses a smooth shape that dies at a cutoff $R$ instead (appendix), but nothing in the derivation cares.
Everything that follows comes from asking one question of this equation: what does it take for the total energy to be conserved?
2. Constants
Every number you can set. Two columns of values: the two-body gate the original codebase validates against, and the defaults of the built model (field.html). Names in the last column are the field/lab slider names.
| Symbol | Gate | Field | What it does | Name |
|---|---|---|---|---|
| $S_0$ | 1.3 | 0.2 | Baseline added to the symmetric part. Above the noise amplitude every hue attracts every other; below it some pairs repel and clumps sort by hue. | S0 |
| $a$ / amp$_S$ | 0.5 | 1.0 | Amplitude of the symmetric (liking) part: $a\cos d$ in the cosine law, or the noise amplitude in the field. | a, ampS |
| $b$ / amp$_\Lambda$ | 0.7 | 0.8 | Amplitude of the antisymmetric (chase) part. | b, ampA |
| period, octaves | — | 3, 3 | Noise lattice cells per turn of hue, and octaves. Busier matrix with more of either. | period, octaves |
| $\lambda$ | 0.55 | 0.55 | Battery size. Bigger $\lambda$ means the same power moves hue less. | lam |
| battery shape | $\Lambda^2$ | $\Lambda^2$ | $\Lambda^2$ is smooth and converges in crowds but pairs slosh; $|\Lambda|$ lets a burned-out pair coast but its cusp chatters when neighbours drive $d$ through zero (questions page, Q4). | store |
| $\kappa$ | 1 | 1 | Multiplier on the chase force. The one real "burn rate" knob. $0$ removes the chase entirely. | kappa |
| $s$ | 1 | 1 | Conserve dial, $0$ to $1$: what fraction of the chase's work the battery must pay for. | conserve |
| max $|\dot h|$ | — | 3 | Hue slew cap. With many neighbours $W$ can cancel while $P$ does not; the cap stops $P/W$ slewing hue by radians per step and books what it could not pay. | slew |
| $R$ | 1 | 1 | Cutoff distance and the unit of length. Every envelope is zero beyond it. | R |
| $\rho$, $\sigma$ | — | 0.2, 0.1 | Gaussian well: rest distance and width. The gate used a monotone $-(1-r/R)^2$ instead. | rho, sigma |
| battery range | $(1-r/R)^2$ | $(1-r/R)^2$ | Where mismatch energy is stored. Reaching the cutoff makes mismatch repel at long range: an activation barrier. Following the well makes it purely a bond property. | battLong |
| $r_0$, $k$ | 0.22, 60 | 0.1, 40 | Soft repulsive core: radius and stiffness. Stiffness sets how small $\Delta t$ must be. | r0, kRep |
| $\eta_{\text{bond}}$ | — | 4 | Bond friction on the radial relative velocity of a pair. Drains the breathing mode so bonds click into place. Its power goes to colour or to heat. | etaBond |
| $\eta$ | 0 | 0 | Global velocity damping. Removes centre-of-mass motion too. | eta |
| guard | $10^{-9}$ | $10^{-12}$ | Below this $W^2$ the battery cannot pay; the shortfall is logged. | guard |
| $\Delta t$ | 0.0006 | 0.001 | Timestep. | dt |
| $m$ | 1 | 1 | Mass, for everyone. Force is acceleration. | — |
3. Split $f$ into "liking" and "chasing" interaction.ts
Because hue is an angle, $f$ only depends on the difference $d = h_i - h_j$ (and is $2\pi$-periodic in it). Any such function splits uniquely into an even part and an odd part. This is the single most important move on the page:
$$ f(d) = \underbrace{S(d)}_{\text{even: } S(-d)=S(d)} + \underbrace{\Lambda(d)}_{\text{odd: } \Lambda(-d)=-\Lambda(d)} $$| Plain English | What it means for the pair | Reference formula | |
|---|---|---|---|
| $S(d)$ | "How much do these two like each other?" | $i$ feels it toward $j$ exactly as much as $j$ feels it toward $i$. Newton's third law holds. | $S_0 + a\cos d$ |
| $\Lambda(d)$ | "Who is chasing whom, and how hard?" | Swap $i$ and $j$ and the sign flips. Third law fails. | $b\sin d$ |
So the seed splits into two forces on $i$ (and, by symmetry, two on $j$):
$$ \mathbf F_{ij} = \underbrace{S(d)\,g(r)\,\hat{\mathbf u}}_{\text{reciprocal}} \;+\; \underbrace{\Lambda(d)\,g(r)\,\hat{\mathbf u}}_{\text{the chase}} $$Sign convention from here on: we want $S > 0$ to mean attract, so the reciprocal force on $i$ is written $-S(d)\,g(r)\,\hat{\mathbf u}$ (toward $j$).
4. The symmetric half is a potential energy.ts, forces.ts
A force that is equal and opposite on the two particles and depends only on $r$ can always be written as $-\partial U/\partial r$ for some $U(r)$. With $g = 1/r^2$:
$$ \mathbf F^{\text{bond}}_i = -\frac{S(d)}{r^2}\,\hat{\mathbf u} \quad\Longleftrightarrow\quad U_{\text{bond}}(r, d) = -\frac{S(d)}{r} $$exactly like gravity with $S$ playing the role of $Gm_1m_2$, except that the "mass product" depends on how alike the two hues are. Add a squishy core so nothing falls into $r = 0$, and you have a perfectly ordinary conservative two-body system:
$$ U_{\text{pair}} = \underbrace{k(r_0 - r)^2}_{\text{soft core},\ r<r_0} \;\underbrace{-\;\frac{S(d)}{r}}_{\text{the bond}} \qquad E = \sum_i \tfrac12|\mathbf v_i|^2 + \sum_{\text{pairs}} U_{\text{pair}} $$If $\Lambda$ were zero, we would be done: $E$ is constant, hues never need to change, and the model is a particle-life-flavoured gravity toy. Nothing interesting happens. All the life comes from the other half.
5. The antisymmetric half is not forces.ts
The chase force on $i$ is $\Lambda(h_i - h_j)\,g(r)\,\hat{\mathbf u}$. Compute the same thing from $j$'s point of view: $\Lambda$ flips sign because it is odd, and $\hat{\mathbf u}$ flips because "from $i$ to $j$" is the opposite direction. Two flips cancel, so
$$ \mathbf F^{\text{chase}}_i = \kappa\,\frac{\Lambda(d)}{r^2}\,\hat{\mathbf u}, \qquad \mathbf F^{\text{chase}}_j = \kappa\,\frac{\Lambda(d)}{r^2}\,\hat{\mathbf u}\quad\text{(the SAME vector)} $$Why can't there be a potential? A potential $U(r)$ is a property of the pair; differentiating it gives equal and opposite forces, always. A force that is the same on both cannot come from one. So the chase does work,
$$ P_i = \mathbf F^{\text{chase}}_i \cdot \mathbf v_i $$and that energy has no source. $E$ drifts upward forever. Classic particle life hides this by adding friction, so the injected energy is thrown away as fast as it arrives. This model keeps the books instead.
6. Add a battery, and charge the chase to it colour.ts
The fix has three parts: something to store energy in, a way to spend it, and a rule that spends exactly as fast as the chase earns.
6a. The store
Give the pair a third potential term that grows with hue mismatch. $\Lambda^2$ is a natural choice: it is even (so it is a legitimate reciprocal potential), zero when the hues agree, and biggest exactly where the chase is strongest. Give it the same $1/r$ shape as the bond so it fades with distance:
The built model lets you pick $B(\Lambda) = \Lambda^2$ or $|\Lambda|$. The difference is what happens at $d = 0$: with $\Lambda^2$ the gradient vanishes there and a burned-out pair overshoots and sloshes; with $|\Lambda|$ it is finite and the pair settles and coasts. Everything below is written for $\Lambda^2$; swap $2\Lambda\Lambda'$ for $\mathrm{sign}(\Lambda)\Lambda'$ for the other.
$$ \boxed{\;U_{\text{pair}}(r,d) \;=\; k(r_0-r)^2 \;-\; \frac{S(d)}{r} \;+\; \lambda\,\frac{\Lambda(d)^2}{r}\;} $$Being a potential, the battery term also produces a force, and it is worth writing down because it is the one everybody forgets. Differentiate by $r$:
| Term | Force on $i$ along $\hat{\mathbf u}$ | Direction |
|---|---|---|
| soft core | $+2k(r_0-r)$ | push apart (inside $r_0$ only) |
| bond | $-S(d)/r^2$ | pull together |
| battery | $+\lambda\Lambda(d)^2/r^2$ | push apart: a particle can lower its mismatch cost by walking away |
| chase | $+\kappa\Lambda(d)/r^2$ on both | along $\hat{\mathbf u}$ for both particles |
6b. How to spend it
Now differentiate the same potential by hue instead of distance. This is the second derivative of the page and the reason "one potential, two derivatives" is the slogan:
$$ W_i \;=\; \frac{\partial E}{\partial h_i} \;=\; \sum_j \Big[\underbrace{-\frac{S'(d)}{r}}_{\text{bond changes}} \;+\; \underbrace{\frac{2\lambda\,\Lambda(d)\,\Lambda'(d)}{r}}_{\text{battery changes}}\Big] $$Read it as: "nudge $i$'s hue by a tiny $\delta h$ and the total energy moves by $W_i\,\delta h$." Both hue-dependent terms contribute. Then let hue move downhill on $E$ at a rate we have not yet fixed:
$$ \frac{\mathrm d h_i}{\mathrm d t} = -\alpha_i\,W_i $$6c. The rule that fixes $\alpha$
Write down how $E$ changes in time. Three contributions: kinetic energy changes because forces act; $U$ changes because positions move; $U$ changes because hues move.
$$ \frac{\mathrm dE}{\mathrm dt} = \underbrace{\sum_i \mathbf v_i\cdot\mathbf F_i}_{\text{KE}} + \underbrace{\sum_i \nabla_{\mathbf p_i}U\cdot\mathbf v_i}_{\text{positions}} + \underbrace{\sum_i W_i\,\dot h_i}_{\text{hues}} $$Split $\mathbf F_i = -\nabla_{\mathbf p_i}U + \mathbf F^{\text{chase}}_i$. The potential part cancels the "positions" term exactly (that is just the usual proof that conservative forces conserve energy). What survives:
$$ \frac{\mathrm dE}{\mathrm dt} = \sum_i \big( P_i + W_i\,\dot h_i \big) = \sum_i \big( P_i - \alpha_i W_i^2 \big) $$Demand that this is zero for every particle separately, and $\alpha_i$ is no longer free:
$$ \boxed{\;\alpha_i = \frac{P_i}{W_i^{\,2}}\;} \qquad\Longrightarrow\qquad \frac{\mathrm d h_i}{\mathrm d t} = -\frac{P_i}{W_i} $$guard, $\alpha$ is set to zero and the unpaid energy is logged (appendix).7. Burn, hold, recharge
$P_i = \mathbf F^{\text{chase}}_i\cdot\mathbf v_i$ is a dot product, so it can be negative. When the chase points against a particle's velocity it is braking it, energy leaves the motion, $\alpha_i < 0$, and hue runs uphill on $E$: the battery charges. This is what makes the model interesting instead of a slow way to flatten a battery.
No memory is needed. The sign of $\alpha$ is read fresh each step from this frame's positions and velocities. Clamping $\alpha \ge 0$ "to be safe" would delete the recharge half and the battery would only ever drain.
8. A worked example with numbers
Constants from §2, and $g = 1/r^2$. Two particles at $r = 0.5$ (outside the core, so ignore it) with $h_i = 1.0$, $h_j = 0.2$, so $d = 0.8$. Say $i$ is moving along $\hat{\mathbf u}$ at $0.4$ and $j$ is moving against it at $0.1$.
| Quantity | Value | From |
|---|---|---|
| $S(d)$ liking | 1.648 | $1.3 + 0.5\cos 0.8$ |
| $\Lambda(d)$ chasing | 0.502 | $0.7\sin 0.8$ |
| $S'(d)$ | −0.359 | $-0.5\sin 0.8$ |
| $\Lambda'(d)$ | 0.488 | $0.7\cos 0.8$ |
| $1/r,\ 1/r^2$ | 2, 4 | $r = 0.5$ |
| bond energy | −3.297 | $-S/r$ |
| battery energy | 0.277 | $\lambda\Lambda^2/r$ |
| bond force on $i$ | −6.593 | $-S/r^2$ (negative: pulled toward $j$) |
| battery force on $i$ | +0.555 | $+\lambda\Lambda^2/r^2$ (positive: pushed away) |
| chase force, on both | +2.009 | $\kappa\Lambda/r^2$ (toward $i$'s side) |
| $W_i$ | 1.256 | $-S'/r + 2\lambda\Lambda\Lambda'/r = 0.717 + 0.539$ |
| $W_j$ | −1.256 | same size, opposite sign (depends only on $d$) |
Now the payment. For $i$, moving with the chase:
$$ P_i = 2.009 \times 0.4 = 0.803,\qquad \alpha_i = \frac{0.803}{1.256^2} = 0.509,\qquad \dot h_i = -0.509 \times 1.256 = -0.64\ \text{rad/s} $$$i$ is being driven, so it burns: its hue slides down toward $j$'s, shrinking $d$, shrinking $\Lambda^2$, draining the battery at exactly $0.803$ energy units per second, which is exactly what the chase is adding to its kinetic energy. Check: $\alpha_i W_i^2 = 0.509\times1.578 = 0.803$. ✓
For $j$, moving against the chase:
$$ P_j = 2.009 \times (-0.1) = -0.201,\qquad \alpha_j = \frac{-0.201}{1.256^2} = -0.127,\qquad \dot h_j = -(-0.127)\times(-1.256) = -0.16\ \text{rad/s} $$$j$ is being braked, so it recharges: $\alpha_j < 0$ and its hue moves away from $i$'s (downward, since $j$ sits below $i$ on the wheel), storing the $0.201$ per second it just lost from its motion. Each particle's books close on their own.
9. Watch it happen
The gate scenario from the codebase, live. This one uses the code's real fall-off shapes (appendix) rather than $1/r^2$, because those are what the parameters in §2 were tuned for. Blue arrows are the reciprocal force, red arrows the chase, particle colour is hue. The strip is energy over time: total should stay flat while KE and the battery trade.
10. The model as built (field.html) src/field
Everything above, with the placeholder shapes replaced by the ones that actually run, plus three additions that N-body forced. Nothing in the derivation changes; only the functions do.
10a. The matrix is a noise field over both hues
$f(h_i, h_j)$ is a tileable Perlin-noise field $F$ on the hue torus, split into its symmetric and antisymmetric parts under swapping the two arguments:
$$ S(h_i,h_j) = S_0 + \text{amp}_S\,\tfrac12\big[F(h_i,h_j) + F(h_j,h_i)\big],\qquad \Lambda(h_i,h_j) = \text{amp}_\Lambda\,\tfrac12\big[F(h_i,h_j) - F(h_j,h_i)\big]. $$That is the same even/odd split as §3, just for a function of two angles rather than their difference. $S$ is the potential, $\Lambda$ the chase. The matrix is tabulated at $128\times128$ and the GPU reads it through a periodic cubic B-spline, differentiating the spline itself for $W$. Not bilinear: a bilinear interpolant has a discontinuous slope at every cell edge, and paying $W\,\delta h$ with the wrong slope for part of a step leaks energy per crossing rather than per step. Measured: a quarter of the friction energy with bilinear, two percent (and shrinking with $\Delta t$) with the spline.
Because $S$ now depends on both hues, $\partial U_{ij}/\partial h_j$ is no longer $-\partial U_{ij}/\partial h_i$. Each particle evaluates the pair from its own side, $\text{lookup}(h_i, h_j)$ for $i$ and $\text{lookup}(h_j, h_i)$ for $j$, and takes the derivative with respect to the first argument. Exchange symmetry of the table makes the two energies agree.
10b. Radial shapes
$$ \begin{aligned} G(r) &= e^{-(r-\rho)^2/2\sigma^2}, & T(r) &= \big(1 - (r/R)^2\big)^2 &&\text{well and taper; } T \text{ reaches } R \text{ with zero value and slope}\\ V(r) &= -G\,T && &&\text{bond: repulsive inside } \rho, \text{ attractive outside, a real rest distance}\\ g(r) &= G\,T && &&\text{chase envelope: follows the well, so the chase cannot outrange the bond}\\ w(r) &= (1 - r/R)^2 \ \text{ or } \ G\,T && &&\text{battery envelope: long-range (activation barrier) or bond-only}\\ U_{\text{core}} &= k(r_0 - r)^2\ [r < r_0] && &&\text{soft core} \end{aligned} $$ $$ \boxed{\;U_{ij} = U_{\text{core}}(r) + S(h_i,h_j)\,V(r) + \lambda\,B(\Lambda(h_i,h_j))\,w(r)\;} \qquad \mathbf F^{\text{chase}}_i = \mathbf F^{\text{chase}}_j = \kappa\,\Lambda\,g(r)\,\hat{\mathbf u} $$Why the chase follows the well: with a chase that reaches $R$ and a bond that reaches $\rho + 3\sigma$, an incoming particle burns its mismatch long before the bond can deepen under it, and just picks up speed. With $g = GT$ the burn happens where the bond is, and two-body capture works (questions page, Q8).
10c. Bond friction: vibration into colour
A pair that falls into a well with nothing to shed its relative energy rings forever. The built model adds a viscous force on the radial relative velocity only:
$$ \mathbf F^{\text{fric}}_i = -\eta_{\text{bond}}\,\big[(\mathbf v_i - \mathbf v_j)\cdot\hat{\mathbf u}\big]\,G(r)T(r)\,\hat{\mathbf u},\qquad \mathbf F^{\text{fric}}_j = -\mathbf F^{\text{fric}}_i . $$Equal and opposite, so momentum is conserved and centre-of-mass motion is untouched: a coasting pair still coasts. Its power on each particle, $P^{\text{fric}}_i = \mathbf F^{\text{fric}}_i\cdot\mathbf v_i$, is routed one of two ways:
- to colour: added to $P_i$ before the $\alpha$ solve. A braked particle sees $P_i < 0$, its hue climbs, and the vibration charges the battery. What the battery cannot hold at the slew cap is booked as heat, not as a leak.
- to heat: booked as dissipated.
Either way the pair clicks into place instead of ringing. The battery is small compared to a well ($\lambda b\,w \approx 0.4$ against a depth of order 1), so most of a capture's vibration ends as heat even in colour mode; what colour does take, the chase burns back into motion.
10d. The colour solve, with its two guards
$$ P_i = \mathbf F^{\text{chase}}_i\cdot\mathbf v_i + P^{\text{fric}}_i,\qquad \alpha_i = \frac{s\,P_i}{W_i^2},\qquad \dot h_i = \mathrm{clamp}\big(-\alpha_i W_i,\ \pm\,\text{slew}\big) $$Guard: if $W_i^2 <$ guard, $\dot h_i = 0$. Slew cap: with many neighbours $W_i = \sum_j \partial U_{ij}/\partial h_i$ can nearly cancel while $P_i$ does not, and $P/W$ then slews hue by radians in one step; the cap stops that. In both cases the power the capped rate cannot pay, $sP_i - (-\dot h_i W_i)$, is booked: the friction's share to heat, the chase's share to unpaid. The conserved quantity is
$$ E + \text{heat} - \text{unpaid} = \text{const},\qquad E = \sum_i \tfrac12|\mathbf v_i|^2 + \sum_{\text{pairs}} U_{ij}. $$and its change since spawn is the residual on the ledger. Per step: clear the grid, bin particles into cells of size $R$ on a periodic box, accumulate (each particle loops its $3\times3$ neighbourhood, so every pair is evaluated twice and no atomics on floats are needed), then semi-implicit Euler: $\mathbf v \mathrel{+}= \mathbf F\,\Delta t$, $\mathbf p \mathrel{+}= \mathbf v\,\Delta t$, $h \mathrel{+}= \dot h\,\Delta t$, everything wrapped.
10e. What the two-body results say about crowds
| Two bodies | Crowd |
|---|---|
| a chase pair burns and coasts ($|\Lambda|$) or sloshes ($\Lambda^2$) | bonded pairs inside clumps do the same; with $\Lambda^2$ the clump hums |
| capture costs colour; braking loosens the bond | clumps grow by burning mismatch; a hot clump that is braked charges up and loosens |
| a cyclic chase unwinds as the battery drains | the noise matrix is full of cycles; they all unwind unless something feeds mismatch back in (a source, or collisions with unlike hues) |
| $S_0 < $ amplitude sorts hues | same: the orange band in the matrix is what makes clumps monochrome |
11. Cheat sheet
| Name | Symbol | Sections 1–9 ($g = 1/r^2$, cosine law) | As built (§10) |
|---|---|---|---|
| the seed | $\mathbf F_{ij}$ | $f(d)\,g(r)\,\hat{\mathbf u}$ | $f(h_i,h_j)$ from Perlin noise on the torus |
| liking | $S$ | $S_0 + a\cos d$ | $S_0 + \text{amp}_S\,\tfrac12[F + F^{\!\top}]$ |
| chasing | $\Lambda$ | $b\sin d$ | $\text{amp}_\Lambda\,\tfrac12[F - F^{\!\top}]$ |
| bond shape | $V(r)$ | $-1/r$ | $-e^{-(r-\rho)^2/2\sigma^2}(1-(r/R)^2)^2$ |
| chase envelope | $g(r)$ | $1/r^2$ | $-V(r)$, follows the well |
| battery envelope | $w(r)$ | $1/r$ | $(1-r/R)^2$ (barrier) or $-V(r)$ |
| battery shape | $B(\Lambda)$ | $\Lambda^2$ | $\Lambda^2$ or $|\Lambda|$ |
| pair potential | $U$ | $k(r_0-r)^2 + SV + \lambda B w$ | same |
| reciprocal force | $\mathbf F^{\text{rec}}$ | $-\partial U/\partial r\,\hat{\mathbf u}$ | same |
| chase force | $\mathbf F^{\text{chase}}$ | $\kappa\Lambda g\,\hat{\mathbf u}$ on both | same |
| bond friction | $\mathbf F^{\text{fric}}$ | — | $-\eta_{\text{bond}}(\mathbf v_{\text{rel}}\cdot\hat{\mathbf u})\,GT\,\hat{\mathbf u}$, equal and opposite |
| power to pay | $P_i$ | $\mathbf F^{\text{chase}}_i\cdot\mathbf v_i$ | $+\ \mathbf F^{\text{fric}}_i\cdot\mathbf v_i$ when routed to colour |
| hue gradient | $W_i$ | $\partial E/\partial h_i$ | same, from the spline |
| change in hue | $\dot h_i$ | $-sP_i/W_i$ | same, clamped to $\pm$slew, zero under guard |
| the ledger | $E = $ const | $E + \text{heat} - \text{unpaid} = $ const |