A real-time, high-performance 2D molecular dynamics and chemistry simulation engine — built in pure JavaScript, running at 60 FPS in any browser.
No WebGL. No frameworks. No runtime dependencies. Just physics, stochastic chemistry, and raw CPU performance.
This is a heuristic visualization of chemistry, not a quantum-mechanically accurate simulator.
The Chemical Chaos Engine uses stochastic models and normalized pseudo-units to create emergent, visually compelling molecular behavior. Reactions are not driven by activation energy thresholds or DFT calculations — they fire with tunable probabilities based on proximity, temperature, and local electronegativity. Think of it as a molecular sandbox: the rules are chemically inspired, not chemically derived.
Known boundaries of the model:
- Not quantum-mechanically accurate (classical approximation)
- Uses normalized pseudo-units, not SI
- Temperature is a kinetic energy proxy, not a full thermodynamic state variable
- Reactions are stochastic (probability-gated), not activation-energy-based
- Physics runs on the main JavaScript thread — Web Worker / OffscreenCanvas migration is a future goal
The typical web physics approach: You reach for Matter.js or Box2D. These model circles with mass, not atoms with chemistry. Valency, electronegativity, covalent bond order, formal charges, aromaticity — all absent. Grafting organic chemistry on top of a generic engine means fighting the library at every step.
The Chemical Chaos Engine approach (Domain-First): The physics engine was designed around chemistry, not the other way around. Every particle is a real atom — element, valence electrons, formal charge, reactivity. The physics loop is a reaction solver that runs VSEPR geometry, bond order constraints, and 30 organic reaction pathways at every frame. Spawn Carbon, Hydrogen, and Oxygen at high temperature and watch them spontaneously assemble into alcohols, aldehydes, and carboxylic acids — without scripting a single reaction manually.
| Feature | Details |
|---|---|
| ⚛️ Atom-Inspired Chemistry Model | Valency enforcement (C=4, N=3, O=2, H=1), dynamic hypervalency (S, P, N), formal charges via FC = V − N − B/2 |
| 🔗 Bond Order System | Single, double, and triple bonds with per-order Lennard-Jones repulsion and geometry constraints |
| 🔥 Organic Reactions | ~30 reactions: SN1/SN2, Aldol, Diels-Alder, Esterification, Keto-Enol, Cope [3,3], free-radical polymerization, Wittig, Michael addition, and more |
| 🧪 Ionic & Acid-Base Chemistry | Full pH tracking from live H₃O⁺/OH⁻ counts, neutralization, generic proton donation, HCl strong-acid dissociation, water auto-ionization |
| 💡 Aromatic Systems | Hückel rule detection, π-conjugation propagation, EAS and SNAr reactions, aromatic turbulence immunity |
| 🌡️ Thermodynamics | Stochastic reaction rates and particle velocities scale with global temperature (0–400 K); local thermal zones create spatial gradients |
| 🗺️ Spatial Hash Grid | 4096-bucket bitmask hash; O(N) collision detection; pooled Int32Array neighbor cache — zero GC, frame-counter invalidation |
| 🧮 Semi-Implicit Euler | Symplectic integration for long-term energy conservation |
| 🔗 Disjoint Set Union | Near-constant O(α(N)) molecular identity tracking for ring and structure detection |
| 🎯 Temporal Amortization | Heavy workloads (pH, organic reactions, structure detection) frame-sliced across a 60-frame cycle to hold render budget |
| 🔍 "HPC" In-Engine Profiler | Per-frame kernel timing (CPU/ALU/BW/RAM/GPU tags) with 150-frame rolling averages |
| 🎆 Dynamic Visuals | sp³/sp²/sp orbital clouds, R/S chirality labels, formal charge overlays, electronegativity bond gradients, dynamic pH background |
| 🏆 Achievement System | 50+ unlockable chemistry milestones (molecule detections, reaction counts, physics events) |
| 🧬 1707 Molecule Database | Hill-notation keyed registry for real-time structural recognition |
Measured on Chrome V8, Windows 11, 1920×1080 — physics on a single CPU thread.
| Scenario | Particle Count | Active Bonds | Physics Latency | Render Latency | GC Pauses | FPS |
|---|---|---|---|---|---|---|
| Idle | 0 | 0 | < 0.1 ms | ~0.5 ms | 0 | 60 |
| Light | 200 | 150 | ~0.5 ms | ~2.0 ms | 0 | 60 |
| Medium | 600 | 500 | ~1.8 ms | ~4.5 ms | 0 | 60 |
| Heavy | 1200 | 1100 | ~4.2 ms | ~8.0 ms | 0 | 60 |
| Stress | 2500 | 2300 | ~11.0 ms | ~14.0 ms | Minimal | 35-45 |
** Kernel Telemetry (1048 particles, desktop, uncapped):**
- Total physics latency: ~5 ms/frame
- ChemistryAndGrid: ~2 ms (38%)
- Constraint Solver × 6: ~1.3 ms (26%)
- Compute-bound: 87% · Memory: 9% · IO: 4%
Desktop (Ryzen 5600X, uncapped): 250 particles @ 180 FPS · 1200 particles @ 65 FPS · 3000 particles @ ~20 FPS, Brave Browser, 0 competing Tabs, 32 GB DDR5 Memory Mobile (Snapdragon 8 Gen 3): 80–570 particles @ 60 FPS (hardware-capped), Brave Browser, 0 competing Tabs, 12 GB RAM
Because the Chemical Chaos Engine adheres to a zero-dependency runtime philosophy, you do not need a build pipeline to run it.
The checked-in demo loads minified assets by default (for example css/style.min.css and js/*.min.js). The readable *-org.js source files are included next to them and can be regenerated with php build_chaos.php when PHP is available.
git clone https://github.com/gotili/chemical_chaos_engine.git
cd chemical_chaos_engine_git
# Open index.html directly in any modern browser (Chrome, Firefox, Edge)(If documentation overlays fail to load over
file://, use a local server:)npx serve .
npm install # Installs devDependencies only: jsdom + ESLint (zero runtime deps added)
npm test # Runs the headless public-API and invariant smoke suite in Node.jsnpx eslint js/ # Flags critical issues; tolerates HPC-optimized code patternsTo interact with the running engine:
Open the HUD (⚙ icon) in the bottom-right corner. Pin it open (📌) to adjust temperature (0–400 K), reactivity (0–3.0), viscosity, turbulence, atom spawn ratios, and toggle orbital/charge visualizations. Use the Interaction Tool dropdown to switch between 6 cursor modes: Grab, Push, Pull, Cut, Destroy, and Push+Destroy.
The engine uses a Hybrid OOP-SoA (Structure of Arrays) architecture — not a pure one or the other.
- Particle objects (
_chemAtoms) hold metadata, bond topology, and chemistry state (type, bonds[], formal charge, isHydronium, etc.). The developer-facingCHAOS_APIinteracts with these. - Typed Array buffers hold the high-frequency physics data that the inner loop reads 60× per second.
// HIGH-FREQUENCY DATA — typed arrays (CPU cache-friendly, GC-invisible)
const _physicsBuf = new Float32Array(MAX_SOA_PARTICLES * 4); // INTERLEAVED: [x, y, vx, vy]
const _typeBuf = new Uint8Array(MAX_SOA_PARTICLES); // element type index
const _flagsBuf = new Uint32Array(MAX_SOA_PARTICLES); // bitmask: isAtom | isRadical | isAromatic...
const _invMBuf = new Float32Array(MAX_SOA_PARTICLES); // 1/mass (pre-inverted)
const _radBuf = new Float32Array(MAX_SOA_PARTICLES); // collision radius
// LOW-FREQUENCY DATA — JavaScript objects (clean API, readable code)
// particle.x, particle.bonds, particle.isHydronium, particle.formalCharge, ...MAX_SOA_PARTICLES = 15 000. Every frame: syncToSoA() → physics loop → syncFromSoA(). No allocations in between.
Per-Frame Chemistry Index: Before each reaction pass, a single O(N) gather populates element-specific registries (window._chemC, window._chemO, window._chemN, etc.). Organic reaction solvers iterate only over their element type — an O-solver never touches Carbon atoms. This dramatically reduces the constant factor of the chemistry step.
Spark/Effect Object Pooling: getSpark() / recycleSpark() eliminate GC stutter during explosions. Previously, visual effects allocated hundreds of new objects per detonation, causing frame drops ~3 seconds later. Pooling eliminates this entirely.
| Module | Size | Responsibility |
|---|---|---|
chaos-engine-core.js |
~318 KB | Physics loop, SoA buffers, spatial grid, bond solver, constraint solver, CHAOS_API |
chaos-organic-reactions.js |
~177 KB | ~30 organic reaction pathways (SN1/SN2, Aldol, Diels-Alder, acid-base, tautomerism, ...) |
chaos-chemistry-logic.js |
~25 KB | Valency tables, bond order, electronegativity, hybridization, formal charge calculation |
chaos-molecules.js |
~107 KB | 1707-molecule Hill-notation database |
chaos-events.js |
~125 KB | Achievement system (50+ milestones) |
chaos-spawner.js |
~15 KB | Molecule spawner templates & presets |
chaos-orbital-viz-org.js |
~22 KB | sp³/sp²/sp orbital probability cloud renderer |
chaos-math-utils-org.js |
~8 KB | Fast approximations, sanitizers, trig LUTs, vector helpers |
chaos-engine-ui.js |
~76 KB | Engine-facing HUD bindings (sliders, toggles) |
ui-core-org.js |
shared UI shell | Parent-compatible UI helpers copied from the main website |
standalone-docs-org.js |
release adapter | Local/file-url documentation overlay path bridge |
sequenceDiagram
autonumber
participant Browser as Browser Frame
participant UI as HUD / User Input
participant Core as Engine Core
participant SoA as SoA Buffers
participant Grid as Spatial Grid
participant Chem as Chemistry Solver
participant Organic as Organic Reactions
participant Solver as Force and Constraint Solvers
participant Draw as Canvas2D Draw
rect rgb(40, 40, 60)
Note over Browser,Draw: Render task
Browser->>Core: requestAnimationFrame animate(timestamp)
UI->>Core: Input state is already captured
Core->>UI: updateFPS() and updatePH()
Core->>Draw: draw current particle state
Draw->>Draw: Background, grid, flow field, redox overlays
Draw->>Draw: Cull visible particles and batch bonds by atom type
Draw->>Draw: Draw bonds, atoms, orbitals, labels, sparks, ripples
Core-->>Browser: setTimeout(updatePhysics, 0)
end
rect rgb(25, 45, 65)
Note over Browser,Grid: Physics task and SoA hot path
Browser->>Core: updatePhysics task
Core->>SoA: syncToSoA() copies particle objects to typed arrays
Core->>Core: Advance frame counters and refresh dirty topology
Core->>SoA: Integrate motion with environmental forces
SoA->>Grid: Rebuild spatial hash buckets
SoA->>Chem: Gather atom registries every 2 frames
SoA->>Core: syncFromSoA() for object-facing chemistry and trails
end
rect rgb(55, 40, 25)
Note over Core,Organic: Frame-sliced chemistry schedule
Core->>Chem: Run scheduled chemistry passes
Chem->>Chem: pH, charges, radicals, structure and molecule passes
Chem->>Organic: Dispatch scheduled organic solvers
Organic->>Core: Create or break bonds and mark topology dirty
end
rect rgb(25, 55, 35)
Note over Core,Solver: Forces, constraints, and final sync
Core->>Solver: Apply molecular forces, bonding, constraints, geometry fixes
Solver->>SoA: Write corrected positions and velocities
SoA->>Core: syncFromSoA() publishes final object state
end
Reactions are stochastically evaluated every 10 frames (6×/second at 60 FPS), decoupled from frame rate. All probabilities scale with local temperature and the global reactivity multiplier (0–3.0×).
| Category | Reactions |
|---|---|
| Substitution | SN1 (carbocation ionization at tertiary/secondary C), SN2 (backside attack, Walden inversion) |
| Acid-Base (4 types) | Neutralization (H₃O⁺ + OH⁻ → 2H₂O), generic proton donation (H-X + :B), HCl strong-acid dissociation, water auto-ionization (H₂O + H₂O ⇌ H₃O⁺ + OH⁻) |
| Elimination | E1/E2 thermal elimination (>300 K) — strips adjacent H and Cl to form C=C |
| Rearrangements | Keto-Enol tautomerism, cumulene → alkyne (1,3-H shift), Cope sigmatropic [3,3] |
| Pericyclic | Diels-Alder [4+2] cycloaddition |
| Addition | Electrophilic addition (Markovnikov), alkene hydration, epoxidation, hydrogenation |
| Elimination/Condensation | Aldol condensation, Claisen condensation, esterification, amide/peptide bond formation, alcohol dehydration |
| Radical | Free-radical polymerization (initiation → propagation → termination), radical halogenation |
| Redox | Combustion, oxidation/reduction, disulfide bridge formation (S-S) |
| Aromatic | EAS (electrophilic aromatic substitution), SNAr |
| Organometallic/Carbonyl | Michael addition (1,4-conjugate), Wittig reaction (P-ylide → C=C) |
| Biological | Phosphorylation, thioester exchange, imine formation |
| Special | Halohydrin formation, Cannizzaro (balanced), ring closure, hydrolysis |
| Geometric Strain | 3-membered ring (Baeyer strain, 70% auto-break), 4-membered ring (1% torsional), cumulene instability |
The test suite validates core physical and chemical invariants — without ever opening a browser. A jsdom wrapper (tests/run-node.js) mocks Canvas, ResizeObserver, and requestAnimationFrame, booting the full engine headlessly in Node.js.
npm test=== Engine Initialization ===
✓ should initialize the global CHAOS_API
✓ should create the global typed array buffers (SoA Architecture)
=== 1. Physics & SoA Data Integrity ===
✓ should correctly update particle positions based on velocity
✓ should synchronize boolean flags to SoA bitmask
=== 2. Basic Chemical Laws ===
✓ should enforce steric hindrance (repulsion) between non-bonded atoms
=== 3. Organic Reactions ===
✓ should form a covalent bond between two close radical atoms
=== 4. Thermodynamics ===
✓ should increase kinetic energy when temperature is raised
Results: 7 / 7 tests passed.
The browser-based runner at
tests/index.htmlalso remains available for interactive visual output.
Pure OOP (an array of {x, y, vx, vy} objects) forces V8 into hidden class lookups during the inner loop — constant cache misses under thousands of particles. Pure SoA (only typed arrays) would make bond topology and chemistry logic a maintenance nightmare.
The hybrid solution: slow-path object API + fast-path typed arrays. Particle objects hold bond topology, chemistry state, and all developer-facing properties. Typed arrays hold only the 4 high-frequency physics values. The syncToSoA() / syncFromSoA() bridge runs once per frame. Result: near-native inner loop speed with a clean, readable chemistry API. MAX_SOA_PARTICLES = 15 000 is pre-allocated at startup — the physics loop never touches the heap.
Inside updatePhysics(), no JavaScript heap allocations occur. Temporary storage uses pre-allocated scratchpad buffers (_scratch, _scratch2). Bond arrays are fixed-size typed arrays. Spark/ripple visual effects use an object pool (getSpark() / recycleSpark()) — explosions that previously caused GC frame drops ~3 seconds later are now completely smooth.
If you contribute, this rule is non-negotiable: a single new Array() inside the physics tick causes a measurable frame-rate drop under V8's JIT.
The engine evaluates reactions probabilistically, not via activation energy thresholds. This is a deliberate design choice: it allows the reaction system to be fully parameterizable via the reactivity slider (0–3.0×), produces visually compelling emergent behavior, and avoids the computational cost of continuous energy surface evaluation.
The trade-off is physical accuracy: the engine is a heuristic chemistry simulator, not a molecular dynamics code in the quantum sense. The Temporal Amortization Scheduler frames chemistry work across a 60-frame cycle to maintain the render budget, decoupling reaction evaluation from frame rate.
The engine uses a 32-bit xorshift-multiply PRNG (not Math.random()) for all stochastic reactions. This makes simulations reproducible: exporting a snapshot via CHAOS_API.exportConfig() includes the PRNG seed, allowing any state to be precisely restored with importConfig(). The Base64-encoded snapshot captures all parameter values, particle positions, velocities, and bond topology.
Contributions are welcome. Because the simulation loop is performance-sensitive, please keep changes small, documented, and covered by tests where behavior changes.
Core architectural rules:
- Zero Allocation in Hot Loops: Inside
updatePhysics()and reaction solvers, never usenew Object(),new Array(),Array.prototype.map, or string concatenation. Use_scratch/_scratch2scratchpads. - Typed Arrays First: New particle state must be encoded as bitmasks in
_flagsBuf. Do not readparticle.somePropertyinside physics hot loops. - Module Boundaries: New organic reactions →
chaos-organic-reactions.js. New chemistry logic →chaos-chemistry-logic.js. Do not extendchaos-engine-core.jswith higher-level chemistry. - No Object.keys() in Hot Loops: Do not use
Object.keys()orarray.filter()insideupdatePhysics()ordraw(). New chemical properties belong in_chemAtomsobjects — not new TypedArrays, unless accessed >10 000 times per frame. - Documentation: Any new simulation mechanic must be documented with JSDoc-style comments explaining the chemical or physical reasoning behind the logic — not just what the code does.
- Run Tests: Execute
npm testbefore any commit. New reaction types must include a corresponding test case intests/engine.test.js.
See
CONTRIBUTING.mdfor bug reporting templates and issue guidelines.
| Document | Purpose |
|---|---|
docs/chaos-engine.html |
Full interactive documentation (Overview, Chemistry, Internals, Advanced) |
docs/BENCHMARK_RESULTS.md |
Detailed performance benchmark methodology and raw numbers |
docs/HPC_HANDOFF_NOTES.md |
Core architectural decisions and maintenance warnings |
CONTRIBUTING.md |
Bug reporting and code standards |
CITATION.cff |
Machine-readable citation metadata |
If you use the Chemical Chaos Engine in your research or publications, please cite it as:
@software{bourier2026chaos,
author = {Bourier, Dr. Felix Sébastien},
title = {Chemical Chaos Engine: 2D High-Performance Browser-Based Molecular Dynamics},
year = {2026},
version = {32.0.0},
url = {https://bourier.biz}
}See CITATION.cff for the full machine-readable citation.
Released under the MIT License. Copyright © 2026 Dr. Felix Sébastien Bourier.
Chemistry doesn't belong in a generic physics engine.