Engineering

One file per game, and no build step

Every playable game on this site is one HTML file. The markup, the styles, the game logic, the artwork instructions and the comments explaining all of it live in the same document. There is no bundler, no framework, no shared stylesheet and no build step. You can open any of them in a text editor and read the whole game top to bottom.

This is unusual enough that it needs defending, so here is the defence, including the part where it costs me something real.

What it buys

The file is the deployable. There is no state in which the source works and the built output doesn't, because they are the same bytes. An entire category of bug — it works locally, it breaks in production — cannot occur, and an entire category of tooling does not need to exist.

It loads instantly, on anything. One request, no dependency waterfall, nothing to parse before the page can paint. This is the single most important property for a game whose whole distribution model is a link somebody sends a friend. You have a couple of seconds before that friend closes the tab, and spending them fetching a framework is a poor use of them.

It cannot rot. A self-contained page written today will run in a browser in fifteen years. A page depending on a build toolchain will not survive its own dependency tree that long; anyone who has returned to a three-year-old project and tried to install its dependencies knows the shape of that afternoon. These games have no dependency tree to survive.

It is readable. A future maintainer — realistically me, having forgotten everything — opens one file and finds the whole game, with the reasoning written next to the code that embodies it. The comment explaining why the pool physics sub-steps at two fifths of a ball's radius sits directly above the loop that does it. That is worth more than any amount of architecture.

What it costs

Duplication, and it is not free.

Every game has its own copy of the palette. Every game has its own back-to-the-hub control. Every game has its own header, its own settings sheet, its own local-storage helper. There are a dozen copies of very nearly the same forty lines of CSS on this site, and when the accent colour changes it changes in a dozen places.

I have made my peace with this by being specific about what kind of duplication it is. Duplicated code is a maintenance cost. Duplicated decisions would be a correctness problem — and the decisions are not duplicated, because they are all one decision written down a dozen times in a form that cannot drift silently. The way I keep it honest is that the things which genuinely must not drift are checked mechanically at deploy time rather than trusted to discipline.

The clearest example: every game page must carry a working control back to the storefront. The games share no chrome component, so nothing structural enforces it — a new game could easily ship without one, and the only person who would notice is a player who is now stuck. So the deploy refuses to run unless every game page carries that control, pointing at the right place. The duplication stays; the risk it creates is converted into a check.

That is the general pattern for building this way. You do not avoid duplication; you enumerate what duplication could break, and you assert each of those things.

The rule that keeps it from becoming a mess

There is one real discipline holding the whole approach together: anything genuinely shared is shared deliberately and explicitly, as a small module served at the site root, and everything else is copied.

The game catalogue is one such module — the single list of what games exist, what they are called and where they live. Both the storefront and the private workshop page import it. It is the one place that knows the answer, and it is served in a way that guarantees a returning visitor never runs a stale copy against fresh markup.

The test for whether something deserves that treatment is not "is this duplicated." Almost everything is duplicated. The test is: would two copies of this ever be allowed to disagree? A colour can disagree for a day and nobody dies. A list of which games exist cannot disagree at all — a stale copy on somebody's device would show them a game that is not there or hide one that is. Those go in a shared module. Everything else gets copied and checked.

Shipping, and holding things back

The other half of building this way is deployment, and it is where the philosophy stops being an aesthetic preference and starts having consequences.

The problem is specific to a repository where the source is the site: if you deploy the folder, you deploy the folder. Everything in it. The database schema, the operator scripts, the design documents, the unfinished games — all of it becomes live at a real URL, and the only thing preventing that is somebody remembering to move things aside before each deploy. That is not a safeguard. It is a habit, and habits fail on the day you are in a hurry.

So the deploy does not upload the repository. It assembles an output directory from an allow-list, and the allow-list is the operative word: a top-level entry the script has never been told about does not ship by default — it aborts the deploy. The day somebody adds a folder of secrets, the process stops there instead of on the live site. Nothing ships because nobody thought about it; everything ships because somebody decided it should.

Then it checks its own work, twice. Before uploading, it scans the assembled directory and rejects anything that should never be there — no scripts, no schemas, no markdown, no certificates, no package files, anywhere in the tree. After uploading, it fetches every held-back URL from the live site over the public internet and fails loudly if any of them answers anything other than a refusal.

That last step is the one I would recommend to anybody. It is easy to be confident that a thing is not deployed. It is a completely different kind of confidence to have just fetched the URL from outside and been told no.

The half-finished games

The site currently has several games that are finished, sitting at real URLs, and deliberately not public. They are gated at the edge — a server-side check, not a client-side overlay, because an overlay is not a gate; it is a curtain in front of a page anyone can read with view-source.

The deploy asserts that too. Every gated path is fetched after the upload and must answer with a refusal, and separately the gate itself must be present and actually routed to. A gate that exists but is not wired into the routing would fail open silently, which is the worst failure mode available: everything looks correct and nothing is protected.

Being able to hold work back confidently is what makes it possible to build in the open at all. The games that are ready are live; the ones that are not are three commits from being live, sitting at their real addresses, verified as unreachable after every single deploy. That is a much better state than a branch nobody has merged in two months.

Would I do it again

For this kind of site, without hesitation. The property that matters is that the game and the file are the same object — you can read it, copy it, archive it, or open it in fifteen years, and the thing you get is the thing that was running.

For a large application with many contributors and shared components, no; the duplication would be genuinely costly and the tooling would earn its keep. But that is a different project, and one of the small pleasures of working alone is being allowed to notice that most advice about how to build software is advice about how to build software with a team, and that quite a lot of it stops applying when there isn't one.