# MOLLY COOL You are fourteen angstroms tall with a proton in one hand and a vacuum in the other. Shoot charge across the room, wrestle atoms together until they snap, and try to put out a chain reaction before it eats the field — and you. **Play:** `python3 serve.py` → http://localhost:5178 No dependencies. No build step. Vanilla JS + Canvas 2D. --- ## Controls | | | |---|---| | **WASD** | move | | **mouse** | aim | | **left click** | proton bolt — pushes charge in (**+**) | | **right click** | vacuum bolt — rips charge out (**−**) | | **space** (hold) | grab two things and squeeze them together | | **Q** | heat — area shove, breaks bonds | | **R** | retry | | **shift+A** | jump straight to CASCADE | Aim at your own feet to charge **yourself** — that's how you go neutral and slip through a membrane. ## The rules, none of which the game will tell you - **Opposites attract, likes repel.** Bolts are charged too, so they *curve* through the field. Bank shots are real. - **Give spends, take earns.** Eight protons orbit your hand. Fire them all and you're dry — the left hand always works, for free. - **Bonds can't form on their own.** Every pair has an activation barrier that parks them at arm's length. Your hands are the only way over it. - **The membrane blocks anything charged and ignores anything neutral.** It has no guard and no keyhole. You get through by becoming the right kind of thing. - **Sour air keeps shoving protons onto everything.** Including you. - **Keep feeding one atom and it goes critical.** A fuse, then a blast that charges everything nearby — which can tip *those* over. In a dense field one seed can take the whole screen. - **Radicals are the only pure white thing in the game.** They have no mind: every 0.32s a radical grabs its nearest neighbour, completes itself, and hands the wound on. You can't shoot one dead. You **terminate** it by wrestling two together — radical pairs recombine barrierlessly, so it's fast. - **Two radicals that drift into each other quench on their own.** Herding is a legitimate tactic. ## Structure Seven wordless tutorial rooms (the cold open), then **CASCADE** — eight arenas of escalating density. Each arena spends a fixed number of ignition waves; put the fire out and it clears. Let it burn and the field melts down. Density is the difficulty dial. Everything else follows from it. ## Layout ``` src/config.js ALL feel constants. Tune the game here. src/physics.js storm, charge, the activation barrier, zones, membrane src/bonds.js the wrestle — grab, squeeze, SNAP src/bolts.js laserhands src/radicals.js the enemy src/overload.js fuses, detonations, the cascade src/arena.js CASCADE — waves, meltdown, clear src/elements.js twelve elements: colour, valence, radius, mass src/juice.js hitstop/trauma budget (a cascade must never freeze the screen) test/*.test.mjs headless sim tests — `node test/sim.test.mjs` ``` ## Tests ``` node test/sim.test.mjs core physics, radicals, molly, regression node test/arena.test.mjs arena waves, win/lose, difficulty ramp node test/overload.test.mjs fuses, cascade, juice budget, perf ``` The sim is seedable (`seedRandom`), so runs are reproducible. Tests drive the real physics headlessly in Node — no browser, no canvas. ## Tuning The three knobs that matter, in order: 1. **`JITTER_STRENGTH`** — the life of the world. 2. **`SQUEEZE_RATE` / `BARRIER_FORCE`** — the wrestle. The whole game's feel in two numbers. 3. **`SNAP_TRAUMA` / `HITSTOP`** — the payoff. Two constants have non-obvious constraints, documented where they live: `BARRIER_FORCE` must stay above `MAX_CHARGE_FORCE` (or atoms bond themselves and the wrestle is skipped), and `RAD_SPLIT_R` must stay above the field's natural packing distance (or outbreaks never bloom). ## Dev note `serve.py` strips a `/v/` prefix off request paths, and `index.html` imports the entry under a fresh one each load. Browsers cache the *parsed* ES module by URL, so without this you can edit a file, hard-reload, and still be running yesterday's code.