Go to file
type-two 6263672134 Molly Cool: arcade science-action vertical slice
A nanoscale arcade game. Shoot charge across the room, wrestle atoms
together until they snap, and contain a chain reaction before it eats
the field. Vanilla JS + Canvas 2D, no dependencies, no build step.

Core systems:
- Charge: give/take as the only two verbs. Opposites attract, likes repel.
- Laserhands: ranged proton/vacuum bolts with real travel time. Bolts are
  charged bodies, so they curve through the field — bank shots are real.
  Give spends from an 8-proton ring, take earns them back.
- The wrestle: bonds can't form on their own. Every pair has an activation
  barrier that parks them at arm's length; Molly's hands are the only way
  over it. Hold, squeeze, release -> SNAP.
- Radicals: the enemy, with no mind. Every 0.32s one grabs its nearest
  neighbour, completes itself, and hands the wound on. Can't be shot dead —
  terminated by wrestling two together (barrierless, so 0.38s vs 1.56s).
  Two that drift into contact self-quench, which gives outbreaks a natural
  equilibrium instead of unbounded growth.
- Overload: keep feeding one atom and it goes critical. The blast tips its
  neighbours, so density decides whether you get one pop (34 atoms) or 84
  detonations from a single seed (116 atoms).
- CASCADE: eight arenas of escalating density with finite ignition waves.
  Clear the field to advance; sustained neglect melts it down.
- Membrane, sour/soapy zones, and the seven wordless tutorial rooms.

Non-obvious constraints, documented at each constant:
- BARRIER_FORCE must exceed MAX_CHARGE_FORCE, or atoms bond themselves and
  the wrestle (the whole game) gets skipped.
- Bond lock distance scales with atom radius. A flat value meant two carbons
  could never bond at all, since collision held them further apart than the
  lock — invisible because hydrogen worked fine.
- RAD_SPLIT_R must exceed the field's natural packing distance (set by the
  barrier), or outbreaks can only crawl and never bloom.

Testing: 39 headless sim tests run the real physics in Node — no browser,
no canvas. The sim draws all randomness from a seeded RNG so runs are
reproducible. Perf is 0.32ms/sim-step at 116 atoms.

Dev server strips a /v<timestamp>/ path prefix that index.html adds to its
entry import, because browsers cache the *parsed* ES module by URL and
no-cache headers alone don't touch it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 15:36:54 +10:00
.claude Molly Cool: arcade science-action vertical slice 2026-07-19 15:36:54 +10:00
src Molly Cool: arcade science-action vertical slice 2026-07-19 15:36:54 +10:00
test Molly Cool: arcade science-action vertical slice 2026-07-19 15:36:54 +10:00
.gitignore Molly Cool: arcade science-action vertical slice 2026-07-19 15:36:54 +10:00
BUILD_PLAN.md Molly Cool: arcade science-action vertical slice 2026-07-19 15:36:54 +10:00
index.html Molly Cool: arcade science-action vertical slice 2026-07-19 15:36:54 +10:00
NEXT_LEVEL.md Molly Cool: arcade science-action vertical slice 2026-07-19 15:36:54 +10:00
README.md Molly Cool: arcade science-action vertical slice 2026-07-19 15:36:54 +10:00
serve.py Molly Cool: arcade science-action vertical slice 2026-07-19 15:36:54 +10:00

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.pyhttp://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<timestamp>/ 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.