Mosslight is a free idle game that runs in the browser. You play a healer who cannot attack. The party fights, and your job is to keep them standing. There is no account, no ads, and the game makes no network calls.
- Play it on itch.io: https://dhseadev.itch.io/mosslight
- Play it on my arcade: https://play.dhseadev.online/games/mosslight/
This post covers what's in the game, and then the part I think other web devs will find useful: running one source tree on two hosts with different security rules, and the save bug my own tests missed.
What is in the game?
Version 0.3.0 has:
- 3 regions with 24 roads between them, and 12 creatures to meet along the way
- 4 classes and 6 songs
- 18 gifts across 5 rarities
- 9 materials, 7 provisions, and a sanctuary with 6 buildings
- 8 Insight upgrades, 7 store items, 10 titles, a 21-entry Ledger, and 15 story requests
- A Sabbatical (prestige) that opens at level 50
- Offline progress for up to 12 hours at half pace
All the art is SVG generated from code, and all the sound is made live with the Web Audio API. There are no image or audio files in the build.
Who can play it comfortably?
I wanted an idle game you can leave open all day without it wearing on you:
- Two themes, Heather and Lantern, both checked against WCAG AA contrast
- 4 text sizes, a high-contrast mode, and reduced motion
- Zen mode, which hides everything except the party
- Full keyboard play
- Ailments are marked by shape as well as color
- Autosave, 3 manual save slots, and save export/import
How does one codebase run on itch.io and my own site?
itch.io gets a single self-contained HTML file. My arcade serves plain files from Cloudflare Pages. Two copies of the game would drift apart, so I keep only one.
The source lives as a normal folder: index.html, style.css, and five scripts. The arcade serves that folder as-is. A small build script turns it inside out for itch: it inlines the stylesheet and the five scripts and swaps the page's security policy. Before I changed any game code, that script's output matched the file already on itch.io byte for byte. That match is what shows the folder and the itch file come from one source.
Why didn't the itch.io file just work on my arcade?
Because of Content Security Policy, and because browsers enforce every policy a page has at the same time.
The itch.io build carried its own policy in a meta tag, allowing inline scripts:
<meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline'; connect-src 'none'; ...">
The arcade sends one header for every page, allowing only script files from itself:
Content-Security-Policy: script-src 'self'; ...
When a page has both, the browser applies both, so a script runs only if it satisfies both rules. An inline script fails 'self', and an external file fails 'unsafe-inline'. Nothing passes, so copying the itch file over would have loaded an empty page.
The fix: on the arcade, Mosslight loads its scripts as files, and its meta tag now says script-src 'self'. I kept connect-src 'none' in the meta tag on purpose. The arcade header allows a few outside hosts for other games, and the stricter meta rule shuts those out of Mosslight's page. After deploying, I tried a fetch to one of those hosts from the live page. The browser refused it and named Mosslight's connect-src 'none' as the reason.
Combining policies can only tighten, never loosen. That works for you when you want a stricter page, and against you when two rules have nothing in common.
How do saves stay separate when every game shares one website?
Every game on the arcade shares one origin, and so one localStorage. Each game keeps its keys under its own prefix, like mosslight:. The arcade's test suite checks this for every entry, and there is no way to exempt one.
Mosslight was written for the web and saved under plain key names, so it failed those checks. Now it has a small storage adapter: it uses the arcade's namespaced storage when that is there and plain localStorage everywhere else, so itch.io behaves exactly as before. Because the arcade storage is asynchronous, the adapter reads every save key into a cache before the game boots and writes through on every save.
What bug did my tests miss?
Slot 3.
The game offers manual save slots 1, 2 and 3. My first version of the adapter loaded slots 0, 1 and 2 at boot. On the arcade, anything in slot 3 would have looked empty after every reload. My test only saved to slot 1, so everything passed.
A panel of three reviewers found it. Each one read the change against a short spec, without seeing my reasoning. They also found three smaller problems:
- a boot error that would have been silently swallowed
- a failed read that could have let a new game save over a real one
- a slot that showed as full when storage was blocked
All four are fixed. The list of slots to load now comes from the same constant the Settings screen draws its buttons from:
var SLOTS = [1, 2, 3];
The test no longer types slot numbers. It reads them from the page.
How do I know the new test actually works?
I watch it fail first. A test that has never gone red on a broken build proves nothing when it goes green.
Mosslight's save test has 15 checks. Before trusting it, I ran it against three broken versions:
- the original itch.io code failed 5 checks
- the slots 0-to-2 draft failed exactly 1
- a build that crashes on boot failed 7 and named the error
Then the real version passed all 15. After that I still opened the game in both themes and looked at it, because several bugs on my site have passed every automated check and only showed up on screen.
What is not done yet?
- Mobile layouts are tested in a browser at phone sizes, not on actual phones.
- There is no cloud save. A save lives in the browser you play in, so a save on itch.io doesn't follow you to the arcade. Export and import move it by hand.
- Cinder Steps is the last region for now.
- Pet swapping, song loadouts and a codex are designed but not built.
Links
- Play on itch.io: https://dhseadev.itch.io/mosslight
- Play on the arcade: https://play.dhseadev.online/games/mosslight/
- Project page: https://dhseadev.online/projects/mosslight/
- Devlog, what Mosslight is: https://dhseadev.online/2026/09/20/mosslight-an-idle-healer-who-cannot-attack/
- Devlog, how the arcade works: https://dhseadev.online/2026/09/21/how-the-arcade-works/
- Arcade source code (public): https://github.com/DHSeaDev/dhsea-arcade
- How I add a game to the arcade: https://github.com/DHSeaDev/dhsea-arcade/blob/main/docs/adding-an-entry.md
If you play it, I'd like to hear what felt slow or confusing.
Top comments (0)