Publishing and Forking

How a game goes from your screen to a URL, and what forking gives you

Starting

Opening any game in the editor, changing it and running it needs no account at all: what you change lives in your browser and is built without being stored. What you cannot do without an account is keep the result. Creating, forking and saving need you signed in, and each account gets 1 GB of storage; verifying a OneJS purchase with your invoice or order number doubles that to 2 GB.

/new asks for a name and nothing else. It publishes a starter game and opens the editor on it, so the first thing you see is your own game running rather than an empty file.

The starter is a real, complete game: one screen, one loop, a cover, and nothing else. It is the smallest thing worth reading.

The editor

/g/<id>/edit, for any game on the site. Write, press Run, and the preview reloads with your build.

Run starts from the file you are looking at. Open a cover scene, or a screen you are building on its own, and Run shows that rather than the game. A file that cannot start a build, a manifest or a stylesheet, runs whatever your game builds from instead and says so rather than looking like Run ignored the click.

Run does not save. The site compiles what is in your tab and hands the JavaScript straight back to the preview frame, which runs it on the game's own origin. Nothing is stored, the published game is untouched, and closing the tab is the end of it. That is what makes reading a game here an active thing: change a number, run it, see what it does.

On a game you own, your typing is saved automatically as you go. That draft is yours: it is not built, not shown on your game's page, and nobody else can read it. Closing the tab and coming back picks it up where you left off.

Publish is the separate, deliberate step. It builds the draft, keeps the result as a new version, and makes it the thing everybody else runs.

Publishing is a build. The site compiles your source with esbuild, so there is no build step on your machine and no second way for a game to come into being. A failed build changes nothing: the compile error comes back with the file, line and column, and the version already published keeps running. You cannot publish a broken game over a working one, and a draft that will not compile is still saved, so nothing is lost by stopping mid-thought.

You can also publish from your own machine against the same endpoint the editor uses. Same path, same result.

A game's URL is its id

/g/<id>. Not the name, not your handle, not both.

That id is the game's identity everywhere: it labels the origin the game is served from and it prefixes its stored files. Making the public URL match means there is one way to resolve a game rather than three.

The cost is a URL that does not say what it points at. The benefit is that renaming is free. Handles change, games get renamed, and under a name-based scheme each of those breaks every link anybody shared. Your name is on the game's page, where it does not have to be part of an address to mean something.

Covers

Every publish records your game and keeps the clip as its card. You do not have to do anything, and there is nothing to upload.

Keep the tab visible while it records. A backgrounded or covered window stops being drawn, and a game that is not being drawn cannot be recorded. If that happens the editor says so and records again when you come back.

Your own cover scene

A file called cover.tsx beside your source is recorded instead of the game. It is a normal oj program: same runtime, same imports, mount() like anything else. Write one when your opening seconds are a menu, or when the game looks best doing something a player has to reach.

Two things make a card good, and both are easier in a scene than in the game.

Compose at the card's shape. A card is 16 by 9. A game laid out for play, with a sidebar or a tall stage, gets cropped into that. Declare the shape you want and lay the scene out to fill it.

Make it loop. A card plays on repeat forever, so a clip that does not join up announces its wrap every few seconds. Anything periodic loops for free: motion along a closed path, a cycle that ends where it started. For animation that never repeats, such as scrolling noise, blend the field with a copy one period behind on a ramp from 0 to 1, so the end of the clip is where the beginning came from. examples/fire/cover.tsx does exactly that and is worth reading.

Settings

Cover settings live under cover in oj.json. Both keys are optional, and a game with no cover block records for six seconds at its own stage shape:

{
    "schema": 1,
    "name": "Pop",
    "entry": "index.tsx",
    "stage": { "size": [600, 600], "fit": "letterbox" },
    "cover": { "seconds": 6, "stage": [960, 540] }
}

seconds is how long the clip runs: a whole number from 1 to 10, six by default. Anything outside that range is brought back into it rather than refused, so a 30 records ten seconds. Three is usually too short: the wrap arrives before anybody has finished looking.

stage is [width, height], the shape the cover is laid out at when it should differ from the game's. A card is shown at 16 by 9, so [960, 540] is the shape that fills one exactly.

What actually gets recorded

Worth knowing before you tune the length, because the answer is not the same in both cases.

With a cover.tsx, the scene is built and started fresh, and recording begins on its first frame. The clip is the scene's first seconds seconds, every time.

Without one, the card is the game already running in the preview, captured from wherever it happens to be. It is not restarted first. That is deliberate: restarting clears the canvas, and a game that is not being drawn hard at that instant records as an empty stage. An author published once and got a card of five identical frames of background, which is the failure this avoids.

So the first seconds seconds from a known start is something only a cover scene gives you.

Making a card loop

A card plays on repeat forever, so a clip that does not join up announces its wrap every few seconds.

The reliable way is a cover.tsx whose animation period divides seconds. The scene's clock starts when it mounts and recording starts one frame later, so a two second cycle recorded for six seconds wraps invisibly. Pick a period that divides the length exactly: 1, 2, 3 or 5 seconds into a 10 second clip.

This does not work without a cover scene. The game is recorded mid-flight, so there is no start to count a period from, and the same animation lands on a different phase every publish.

A cover.html, cover.png or cover.mp4 beside your source still wins over a recording, for anyone who would rather draw their own. See Shipping Files.

Forking

Every game shows its source, and almost every game can be forked. That is the point of the site rather than a side effect: the fastest way to understand a technique is to open the game that uses it, change a number, and watch what happens.

A fork is a new game with a new id, owned by you, starting from the source you were reading. The original is untouched, and nothing you do to your copy reaches it.

If you publish a fork, say what it came from. Nothing enforces that; it is just the thing that keeps a catalog of forks worth reading.

Forking is the step past running. Anybody can change a game and watch it run; a fork is how the change gets a URL, which is why it needs the package and an account to own it.

Turning it off

The Publish panel has a No fork toggle. With it set, nobody but you can fork the game or open it in Unity, and both offers disappear from its page. Reading and running it are unaffected: the game still shows its source and still plays for anyone.

Tick it if your game ships art, audio or fonts you did not make. A fork copies those files into somebody else's game, and a licence that lets you use an asset frequently does not let you pass it on.

Be exact about what the toggle does, because the gap matters. It stops the site handing anyone a reusable copy of your game. It does not stop somebody saving a file out of the game while it runs, and nothing here could: the browser has to download an asset in order to draw it, so every file your game draws reaches every player. If a licence requires that nobody can obtain the file at all, a web game is the wrong place to use it.

What is stored, and what a publish touches

Your source and your assets are stored separately.

A publish re-uploads source and leaves assets alone unless you changed them. That is why adding a sprite and editing a line of code are different sizes of operation, and why a game with a lot of art still publishes instantly.

Removing an asset in the editor deletes it on the next publish. Adding one uploads it. Files you never touched stay as they are.

The preview is not the game page

The editor preview reloads with a cache-busting parameter, because published bundles are served with a short cache and an author pressing Publish should not be shown the previous build for the next five minutes. It looks exactly like a publish that silently failed, which is why the preview sidesteps it.

The public game page uses the normal cache. If you have just published and a hard refresh still shows the old build, give it a moment rather than saving again.

Playing

A game starts on its own when its page loads. The container is one download per visit rather than one per game, so there is nothing to click through.

Two deliberate exceptions. A browser asking for Save-Data or reporting a slow connection gets a Play button instead, because that request is specific and a game nobody asked for is exactly what it meant. And a game does not take keyboard focus on load: holding the keyboard from the moment a page appears would stop space and the arrow keys scrolling, and would drop a screen reader inside the game. Touching the game hands focus over, which is what somebody about to play does anyway.

If your game reads the keyboard, that focus rule is worth knowing: a player who has not clicked yet is not sending you keys. Games that need the keyboard usually say so on their first screen.