---
name: fradiation
description: Prepare, check, package and deploy browser-game builds for Fradiation Games (www.fradiation.games) — Unity, Godot, Three.js/Vite and plain HTML5. Use when making a game build for Fradiation, writing or fixing fradiation.json, adding Fradiation mutations or scoreboards, wiring in the Fradiation SDK, deploying a build or rolling one back, or checking why a build won't upload.
---

# Fradiation Games builds

Fradiation Games (https://www.fradiation.games) hosts browser games. Every game runs on its own origin, `https://<slug>.containment.cloud`. Builds are uploaded on the game's Manage page, in a browser, or deployed from a terminal by `check-build.mjs deploy` with an API token in `FRADIATION_TOKEN`.

Your job: produce a build that passes the upload rules, a valid `fradiation.json`, and correct SDK calls. Then deploy it if `FRADIATION_TOKEN` is set, or hand it to the human.

**If the `fradiation` MCP server is connected, use it.** It has the docs (`read_doc`), the rules (`get_build_rules`, `check_manifest`) and, once the human has signed in through it, their games: `create_game`, `begin_upload` / `finish_upload` (hash the files, PUT each to the presigned URL it returns with plain curl, then finish: no token, no downloaded script), `upload_cover` (one presigned URL), `make_build_live`, `start_playtest`, `get_feedback`. The rules below still apply. If it isn't connected and the human wants it, point them at https://www.fradiation.games/docs/agents#connect-your-agent.

## Hard rules

The authoritative list is https://www.fradiation.games/docs/build-rules.json. If it disagrees with this file, it wins. Fetch it when a limit matters.

- **Entry at the top.** `index.html` sits at the top of the build folder, or the file named by `entry` in `fradiation.json`. One wrapping top-level folder is dropped automatically. Don't rely on it.
- **Relative paths only.** A build is served at `https://<slug>.containment.cloud/<buildId>/<path>`. Reference assets as `assets/x.js` or `./x.js`. Never `/assets/x.js`; it breaks.
- **Limits.** 1,000 files, 500 MB total, 200 MB per file.
- **Path rules.** Relative, forward slashes, at most 240 characters. Every folder and file name starts with a letter, digit or underscore, and uses only `A-Z a-z 0-9 . _ space ( ) + -`. No name ends in a dot or a space. So no dotfiles.
- **Allowed file types**, by extension: `html htm js mjs css json map wasm data pck bin unityweb mem txt xml svg png jpg jpeg gif webp avif ico ktx2 basis glb gltf mp3 ogg wav m4a mp4 webm woff woff2 ttf otf`. Anything else is refused.
- **Pre-compressed files.** A `.br` or `.gz` copy of an allowed type is allowed (`Game.wasm.br`). It is served with the right `Content-Encoding`.
- **Junk is skipped silently:** `__MACOSX`, `.DS_Store`, `Thumbs.db`, `desktop.ini`, `.git`, `._*`. Don't ship anything else that starts with a dot.
- **Fill the window.** The game canvas is 100% width and height and handles resize. The site letterboxes the frame to the `viewport` aspect ratio. Don't hard-code a canvas size.
- **Bundle everything.** No scripts, fonts or assets from other origins. The game can still `fetch` external APIs.
- **Sandboxed frame.** The game can't reach the main site, can't submit forms and can't be embedded elsewhere. It talks to the site only through the SDK.
- **No threads.** Threaded builds don't run on the site yet (`SharedArrayBuffer` isn't available inside the site's frame). Build single-threaded: Unity multithreading off, Godot 4.3+ with Thread Support off, Emscripten without pthreads. Leave `threads` false.
- **Storage.** `localStorage`, `IndexedDB` and Unity's cache belong to the game's own origin and persist across builds.

## Workflow

1. **Identify the engine.** Unity has `ProjectSettings/ProjectVersion.txt`. Godot has `project.godot`. A `package.json` with `three` or `vite` is Three.js/Vite. Otherwise it is plain HTML5. The engine values are `unity`, `godot`, `three`, `html5`.
2. **Configure the build** for the engine (see Engines below).
3. **Write `fradiation.json`** with the `$schema` line (see the template below). Ask the human for the game's native stage size if you can't tell.
4. **Add SDK calls** for the mutations and boards you declared.
5. **Build.**
6. **Check.** Run `node check-build.mjs <folder>`. The script sits next to this file; call it by that path. It needs Node 20 or later. Fix every error and run it again until the exit code is 0. Fix the project or its build settings, never the output folder.
7. **Package.** Run `node check-build.mjs pack <folder> [out.zip]`. It checks the folder and zips it the way the uploader expects.
8. **Deploy or hand off.** If `FRADIATION_TOKEN` is set, deploy (see Deploying below). Otherwise tell the human to upload at `https://www.fradiation.games/dev/<slug>`: drop the zip, or pick the folder. Give them the path and draft patch notes to paste into the uploader (up to 2,000 characters, shown in the game's update log).

Other commands:

```bash
node check-build.mjs <folder-or-zip>             # check
node check-build.mjs <folder-or-zip> --json      # machine-readable report; read this, don't scrape the text
node check-build.mjs pack <folder> [out.zip]     # check, then zip
```

A draft game always runs its newest build. If the game is already released, the uploader offers **Go live now** or **Upload only**. The limit is 20 uploads a day, browser uploads and deploys together.

Hand-off message, when there is no token:

```text
Build ready: <path to zip or folder>
Upload at https://www.fradiation.games/dev/<slug>
Patch notes to paste:
- <what changed, one line each>
```

## Deploying

With the MCP server connected, upload with `begin_upload`, the PUTs and `finish_upload` instead; the rules below about `--live` and `--playtest` apply to its `live` and `playtest` too. Without it, deploy only when `FRADIATION_TOKEN` is set in the environment. The human makes the token under **Agents & tokens** (https://www.fradiation.games/dev/agents). It acts as them, on their own games or on one game.

```bash
node check-build.mjs deploy <folder-or-zip> [--game <slug>] [--playtest] [--live] [--notes-file notes.txt] --json
node check-build.mjs builds [--game <slug>] --json           # builds: id, live, testing, pinned, notes, url
node check-build.mjs live <buildId> [--game <slug>] --json   # make a build live: ship, or roll back
node check-build.mjs playtest <buildId> [--game <slug>] --json   # send a build to testers
node check-build.mjs whoami --json                           # the token's developer, games and expiry
node check-build.mjs cover <image> [--game <slug>] --json    # set the cover: PNG/JPEG/WebP/AVIF/GIF, cropped to 800x600
```

- `deploy` runs the check first and uploads nothing if it fails. Then it uploads only the files the game doesn't have yet, and seals the build.
- `--game` can be left out when the token only works for one game. `whoami` lists the games a token reaches.
- `--playtest` sends the build to the game's playtest: testers get it on their next load. `deploy` warns (`no-playtest`) if the game has none.
- `--live` makes it live. A draft is always live (only its developer sees it). On a game in containment, released or buried, players get a live build on their next load. **Don't pass `--live`, or run `live`, on such a game unless the human asked for it.** Prefer `--playtest`.
- `--notes-file` or `--notes`: patch notes, up to 2,000 characters. Draft them from the commits since the last deploy.
- `--viewport <w>x<h>` is used only when `fradiation.json` has no viewport.
- Build ids can be shortened to a unique start of 4 or more characters.
- Exit code 0 is success. Read the JSON: `ok`, `buildId`, `live`, `playtest`, `url`, `testers`, `newFiles`, `uploadedBytes`, `warnings`. On failure: `stage` and `errors[].code`:
  - a checker code at stage `check`: fix the project, rebuild, deploy again;
  - `invalid`: the site refused the build; the message names the file or field;
  - `auth` or `no-token`: the token is missing, wrong, revoked or expired. Ask the human for a new one in the environment;
  - `forbidden`: not their game, or a one-game token used for another game;
  - `rate`: 20 uploads today, or the storage limit. Stop and tell the human;
  - `network`, `upload`, `server`: try once more, then stop.

The token is a secret. Never print it, echo it, log it, write it to a file, commit it or put it in a build. Never ask the human to paste it into the chat: ask them to set `FRADIATION_TOKEN` where you run.

Report after a deploy:

```text
Deployed <game> build <buildId> (<newFiles> new files, <uploadedBytes> bytes)
Testers: <testers link>   or   Live: <url>   or   Sealed, not live: node check-build.mjs live <buildId>
```

## Engines

### Unity

1. Download https://www.fradiation.games/kits/fradiation-unity-kit.zip and unzip it into the project folder. The zip holds an `Assets/` folder that merges with the project's. It adds:
   - `Assets/Fradiation/Editor/FradiationBuild.cs`: menu **Fradiation > Build for Fradiation** and the batch method `FradiationBuild.Build`.
   - `Assets/Fradiation/Runtime/Fradiation.cs`: the C# API.
   - `Assets/Plugins/WebGL/Fradiation.jslib`: the bridge to `window.fradiation`.
   - `Assets/WebGLTemplates/Fradiation/`: a WebGL template that fills the frame, loads `fradiation-sdk.js` and calls `fradiation.ready()` once Unity starts.
2. Put `fradiation.json` in the project root. The build copies it to the output.
3. Close the project in the editor. The editor version needs the WebGL Build Support module.
4. Build:

   ```bash
   Unity -batchmode -quit -projectPath . -buildTarget WebGL -executeMethod FradiationBuild.Build
   ```

   `Unity` is the path to the editor executable for the project's version. Add `-logFile -` to print the log. Add `-fradiationOut <folder>` to change the output folder. The process exits with code 1 when the build fails.
5. Check `Builds/Fradiation` (or the `-fradiationOut` folder).

The kit sets the Fradiation template, Brotli compression, decompression fallback off and data caching on. Leave them. Do not turn on decompression fallback: `.br` files are served with the right encoding, so it isn't needed. Leave native multithreading off. The kit is tested with Unity 6 (6000.x). The APIs it uses exist in Unity 2021.3 and later.

### Godot

1. Add a Web export preset named `Web` (Project > Export). Install the export templates. Commit `export_presets.cfg`.
2. Load the SDK in the exported page. Download https://www.fradiation.games/sdk/fradiation-sdk.js into the project root, next to `fradiation.json`, and add `<script src="fradiation-sdk.js"></script>` to the preset's HTML head include. See https://www.fradiation.games/docs/godot.md.
3. Export, copy the manifest, check:

   ```bash
   mkdir -p build
   godot --headless --export-release "Web" build/index.html
   cp fradiation.json build/
   cp fradiation-sdk.js build/
   node check-build.mjs build
   ```

The output name sets the other file names (`index.js`, `index.wasm`, `index.pck`). Use Godot 4.3 or later with Thread Support off in the preset. Godot 4.0 to 4.2 only export threaded builds, which don't run on the site yet.

### Three.js and Vite

1. Set `base: "./"` in `vite.config`. Without it, Vite writes root-absolute paths and the build breaks.
2. Put `fradiation.json` and static files in `public/`. Vite copies them to the top of `dist/`. Don't put dot-folders there.
3. Reference runtime assets relatively (`./models/ship.glb`) or through `import.meta.env.BASE_URL`. Never `/models/ship.glb`.
4. Size the renderer to the window and handle `resize`.
5. Get the SDK: download `fradiation-sdk.mjs` (and `fradiation-sdk.d.mts` for TypeScript, next to it) into `src/`, or put `fradiation-sdk.js` in `public/` and load it with a script tag.
6. Build, then `node check-build.mjs pack dist`.

Other bundlers follow the same rule: a relative public path, and `index.html` at the top of the output.

### Plain HTML5

1. Put `index.html`, `fradiation.json` and the assets in one folder. Every reference is relative.
2. Copy `fradiation-sdk.js` next to `index.html` and load it with a script tag.
3. Check the folder.

### All web engines: find root-absolute paths

`check-build.mjs` warns about root-absolute URLs it finds in the build's HTML, CSS and JS (code `absolute-path`). Treat each one as a bug. It skips minified lines, so also search the source:

```bash
grep -rnE '(src|href)="/[^/]|url\(/[^/]' <folder> --include=*.html --include=*.css
```

And look for `fetch("/...")` and `new URL("/...")` in the code.

## fradiation.json

Put it at the top of the build. A web upload reads `entry`, `viewport`, `threads`, `mutations` and `boards`. Title, slug, engine, description and AI disclosure come from the game's details form. Any other field is ignored, so a `$schema` line is harmless.

```json
{
  "$schema": "https://www.fradiation.games/fradiation.schema.json",
  "viewport": { "width": 1280, "height": 720 },
  "mutations": [
    {
      "key": "first-blood",
      "name": "First Blood",
      "description": "Take down your first enemy.",
      "tier": "alpha",
      "icon": "skull"
    }
  ],
  "boards": [
    { "key": "high-score", "name": "High Score", "order": "desc", "format": "number", "max": 1000000 }
  ]
}
```

Fields:

- `entry`: string, default `index.html`. The file must exist at the top of the build.
- `viewport`: `{ width, height }`, integers, width 160 to 4096, height 120 to 4096. Without it, the uploader asks for a stage size (default 1280x720).
- `threads`: boolean, default false.
- `mutations`: at most 30, with at most 3 `gamma` and 1 `cherenkov`. Each has:
  - `key`: matches `^[a-z0-9-]{2,40}$`.
  - `name`: 1 to 40 characters.
  - `description`: 1 to 120 characters.
  - `tier`: `alpha`, `beta`, `gamma` or `cherenkov`.
  - `icon`: one of the values of the `icon` enum in https://www.fradiation.games/fradiation.schema.json. Read the enum there. Never invent a name.
  - `secret`: optional boolean.
- `boards`: at most 10. Each has:
  - `key`: same pattern as a mutation key.
  - `name`: 1 to 40 characters.
  - `order`: `desc` (higher is better, the default) or `asc`.
  - `format`: `number` (the default) or `time` (milliseconds).
  - `max`: optional positive number. Scores above it are rejected as implausible.

Mutations and boards are upserted by key when a build is sealed. Every key the code uses must be declared here.

## SDK

The SDK is not on npm. Download a file and ship it inside the build:

- https://www.fradiation.games/sdk/fradiation-sdk.mjs (ES module)
- https://www.fradiation.games/sdk/fradiation-sdk.js (script tag, sets `window.fradiation`)
- https://www.fradiation.games/sdk/fradiation-sdk.d.mts (types for the ES module; save it next to it)

ES module:

```js
import { fradiation } from "./fradiation-sdk.mjs";

const { connected, player, mode } = await fradiation.ready(); // mode: "live" | "preview" | "playtest"
await fradiation.unlockMutation("first-blood");               // { signedIn, unlocked }
await fradiation.submitScore("high-score", 12345);            // { signedIn, isBest, best, rank }
fradiation.track("reached-level", { level: 3 });              // playtest timeline; ignored elsewhere
await fradiation.askForFeedback("How was that boss?");        // { opened }; playtests only
```

Script tag:

```html
<script src="./fradiation-sdk.js"></script>
<script>
  window.fradiation.unlockMutation("first-blood");
</script>
```

Unity C#, through the kit. The calls are fire-and-forget, and in the editor and on other platforms they only log:

```csharp
Fradiation.UnlockMutation("first-blood");
Fradiation.SubmitScore("high-score", 12345);
Fradiation.Track("reached-level", "{\"level\":3}");
Fradiation.AskForFeedback("How was that boss?");
```

GDScript, through `JavaScriptBridge` (the SDK script must already be on the page):

```gdscript
var fradiation = JavaScriptBridge.get_interface("fradiation") if OS.has_feature("web") else null

func unlock_mutation(key: String) -> void:
	if fradiation:
		fradiation.unlockMutation(key)

func submit_score(board: String, value: float) -> void:
	if fradiation:
		fradiation.submitScore(board, value)
```

Notes:

- Outside the site, every call resolves harmlessly with defaults. Never gate gameplay on `ready()` or on a signed-in player.
- `unlockMutation` is idempotent. `unlocked` is true the first time.
- The site keeps each player's best score per board.
- `track` names are cut to 80 characters. The `askForFeedback` prompt is at most 200.
- Uncaught errors and unhandled rejections are reported automatically once connected.
- In `preview` (the developer's draft) calls are checked but not kept. In `playtest` they are logged for the developer, not checked or kept.
- Developers get no rads from their own games, but they can unlock their own mutations.

## Read more

Every doc page is raw Markdown at `/docs/<slug>.md`.

- https://www.fradiation.games/docs/builds.md: build rules and the uploader.
- https://www.fradiation.games/docs/agents.md: deploying with a token, and the upload API.
- https://www.fradiation.games/docs/fradiation-json.md: the manifest.
- https://www.fradiation.games/docs/sdk.md: the SDK.
- https://www.fradiation.games/docs/unity.md: Unity.
- https://www.fradiation.games/docs/godot.md: Godot.
- https://www.fradiation.games/docs/web.md: web builds (Three.js, Vite, plain HTML5).
- https://www.fradiation.games/docs/playtests.md: private test links and feedback.
- https://www.fradiation.games/llms.txt: index of every doc.

## Don't

- Don't invent icon names or `fradiation.json` fields. Use the schema.
- Don't use root-absolute paths (`/assets/x.js`).
- Don't enable Unity's decompression fallback.
- Don't ship dotfiles or files of a type that isn't on the allowed list.
- Don't hard-code a canvas size.
- Don't build with threads (Unity multithreading, Godot Thread Support, pthreads). They don't run on the site yet.
- Don't fix a build by editing the output folder. Fix the source, rebuild, check again.
- Don't deploy without `FRADIATION_TOKEN` already set, and don't go looking for tokens elsewhere. Never print or store the token, or ask for it in the chat.
- Don't make a build live on a game players can see unless the human asked. Send it to testers instead.
- Don't give the game any access to the main site, or talk to the parent page except through the SDK.
