new-minigame
Add a new mini game to the Mini Game Portal (folder, game.json manifest, pages, thumbnail, tests, docs). Use when the user wants to start a new game or add a page/tool to an existing game.
Files1
View on GitHubSKILL.md.claude/skills/new-minigame/SKILL.md
GitHubAdd a mini game to the portal
The portal and the Rsbuild config discover games from src/games/<id>/game.json.
No central registry needs editing.
Steps
- Pick a short id (
[a-z0-9_-]), createsrc/games/<id>/. - Write
game.json(schema:src/portal/manifest.ts):{ "id": "<id>", "title": "My Game", "version": "0.1.0", "description": "One or two sentences for the portal card.", "tags": ["three.js"], "pages": [ { "id": "play", "entry": "pages/play.ts", "title": "My Game", "label": "Play" }, { "id": "level-viewer", "entry": "pages/level-viewer.ts", "title": "My Game · Levels", "label": "Levels", "dev": true } ] }playis served at/games/<id>/; other pages at/games/<id>/<page-id>.html.dev: truepages show as tool buttons on the portal card.- Links must be relative (the build may be hosted in a sub-folder): portal =
../../, sibling page =<page-id>.html, main page =./. Never use a leading/. - The game builds into its own self-contained
dist/games/<id>/(own Rsbuild environment, picked up automatically). Load runtime files relative to the page (new URL('models/x.glb', location.href)) or import them, never from the portal root; a game-only file in the rootpublic/needs agamePublicFilesentry inrsbuild.config.ts. - The play page's main menu (and pause / results screens) must offer an All games button when
portalUrl()(src/shared/portal-link.ts) is not null - installed as a PWA there is no browser back button.
- Entry files: the default HTML template has
<div id="root">. Import page CSS from the entry. Use three.js unless the user asked otherwise. Reusesrc/shared/*:debug-panel.ts(left tool menu),url-state.ts(bookmarkable state),stats-overlay.ts(F3)rng.ts/noise.tsfor anything procedural (seeded!)
- Portal screenshot: in the play page call
installThumbnailCapture('<id>', () => { renderer.render(scene, camera); return renderer.domElement; }), runnpm run dev, open the game, press F9 -> writessrc/games/<id>/thumbnail.jpg.- Audio: the game's audio class (WebAudio /
Audio) must start silent whenisMutedByUrl()(src/shared/mute-param.ts,?mute=1) is true. When you open the game in the browser pane to test, always addmute=1to the URL - no sound while creating / editing, unless the task is a sound fix (then no param, low volume).
- Audio: the game's audio class (WebAudio /
- Add the game to
GAME_LISTinsrc/portal/release.ts(card order). Work in progress? Give ithideInProd: truethere: the release build gives the game no portal card but still builds it, so people with the link (games/<id>/) can play it. The dev server andbuild:testlist it. Remove the flag to release. - Restart
npm run devafter adding a new game folder / page (entries are read at startup). - Tests in
tests/<id>/, atest:<id>npm script and a CI job (testingskill); keep game logic DOM-free so it is testable in node. - Write the docs short (
AGENTS.md"Write short"). Addsrc/games/<id>/README.md(high level: what it is, screenshot, how to play, feature list, links) andsrc/games/<id>/DETAILS.md(pages, keys, rules, architecture), and a row in the rootREADME.mdtable. - Verify:
npx tsc --noEmit -p tsconfig.json,npm run lint,npm run test,npm run build.
Patterns worth copying from rally
- Fixed-step simulation + render interpolation (
src/games/rally/game/rally-game.ts). benchmark(n)method exposed onwindow.__<game>to measure ms/frame even when the tab is hidden.- Tool pages built on a shared shell (
src/games/rally/debug/viewer-shell.ts).