# MOLT **[▶ Play it](https://eltociear.github.io/molt-game/)** — no install, no account, no backend. Arena survival where your only weapon is taking your own armour off. ![MOLT gameplay](gameplay.gif) *22 seconds of real play, recorded straight off this build — see [`capture.py`](capture.py).* --- ## Controls | | | |---|---| | **Mouse** | move — you accelerate toward the cursor | | **Space** *or* **Click** | **MOLT** | | **R** | restart | That's the whole control scheme. One verb. ## The idea Chitin grows on you constantly. It is armour — three visible plates, each one soaks a hit — and it is also a weight, costing you up to 42% of your top speed at full thickness. You cannot stop it accreting. **Molting** is the only thing you can do about it, and it does three things at once: 1. **Dashes** you clear of whatever is closing in, 2. **Detonates** a shockwave that kills everything inside it — the bigger the shell you shed, the bigger the blast, 3. **Drops the shell on the floor, where it stays.** Permanently. Solid. So your escape, your only offence, and your worst long-term problem are the same button. Molt early and you are fast, naked and unarmed. Molt late and you get a huge blast but you have been slow for ten seconds to earn it. And every time you do it, the arena gets a little more crowded with your own history — by wave 6 you are fighting inside a maze you built yourself, out of things that used to be you. That is the entire game. Everything else exists to put pressure on that one decision. ## What is hunting you | | | | |---|---|---| | ▲ | **Chaser** (cyan) | walks straight at you. Shells block it, so shells are cover. | | ➤ | **Darter** (magenta) | hesitates, flashes white, then charges. The flash is your cue. | | ⬢ | **Borer** (green) | ignores you and eats your shells. | The borer is the pressure valve. Without it the arena silts up with your own shells until you cannot move; with it, cover you were relying on is quietly being chewed away behind you. It is the only enemy you are sometimes glad to see. ## Scoring Survival pays continuously. Kills pay in a **square** — clearing 4 hunters in one shockwave is worth 160, not 40. Holding your molt to catch a crowd is where the score actually is, and it is also the most dangerous thing you can do. Best score is kept in `localStorage`. Nothing leaves your machine. ## Design notes - **Attrition selects for whatever does not engage.** Borers were originally uncapped, and the game slowly turned green: everything that hunts you also walks into your shockwave and dies, while borers feed out at the edges and ignore you entirely, so they were the only thing that reliably survived. By wave 5 the arena was a passive crowd that was neither a threat nor interesting. Borers are now capped at three concurrent. The bug was only visible over a couple of minutes of continuous play — recording the gameplay clip is what surfaced it. - **One button, three effects.** The first version had a dash, a bomb and a wall as three separate abilities. Collapsing them into one input is what made the game interesting: you can't take the part you want without the parts you don't. - **The level is your own exhaust.** Shells are not decoration — they are solid to you, solid to hunters, and permanent. The arena at minute three is a readout of every panic decision you made in minutes one and two. - **Molting twice in one spot thickens the existing shell** rather than stacking a second disc inside it. Stacked discs read as a rendering fault and could box the player in with no counterplay. - **Fixed 120 Hz timestep** with an accumulator, so physics is identical on a 60 Hz and a 144 Hz display, and a backgrounded tab cannot spiral. - **Seeded RNG for the simulation, a separate stream for sparks.** `?demo=1` runs a seeded attract bot, so it plays the same run every load. Screen-shake originally drew from that same generator — and shake is applied in `draw()`, which runs once per *frame*, so a 144 Hz display consumed the stream at over twice the rate of a 60 Hz one and the "seeded" run diverged by display. Visual randomness now has its own source. The gameplay clip is recorded from this mode, so it cannot drift from the build. - **No assets.** Every sound is synthesised in WebAudio at runtime and every graphic is canvas 2D. The whole game is one HTML file with no external request, which is also why it works offline and starts instantly. ## Verified Smoke-tested headless on all three required engines — Chromium, Firefox and WebKit (Safari) — checking for page errors and that the simulation actually advances: see [`test.py`](test.py). ``` chromium ok no page errors wave 4 @ 45s {"wave": 4, "kills": 24, "shells": 10, "score": 955} firefox ok no page errors wave 4 @ 45s {"wave": 4, "kills": 23, "shells": 13, "score": 901} webkit ok no page errors wave 4 @ 45s {"wave": 4, "kills": 24, "shells": 16, "score": 873} ``` The three runs are close but not identical: `requestAnimationFrame` pacing differs per engine, so the accumulator lands a step either side of the sample point. The simulation itself is fixed-timestep, so play speed does not. ## Run locally Any static server, or just open the file: ```sh python3 -m http.server 8000 # then open http://localhost:8000 ``` ## Licence MIT.