Project structure
Every Moonspell game is its own repo, and every repo starts as a copy of the game template, templates/crescent-game/. This page is a map of that folder: what each file is for, what you change to make a game, and what you rename when you copy it.
The tree
templates/crescent-game/
package.json scripts and the pinned dependency set; "main": "./index"
package-lock.json
app.json Expo config: name, slug, orientation, iOS and Android ids, plugins
index.ts phone entry: registerRootComponent(App)
index.web.ts browser entry: bootWeb, then the same App
metro.config.js three -> its WebGPU build, R3F -> its web build, .glb as assets
babel.config.js babel-preset-expo + the TypeGPU plugin ('use gpu' -> WGSL)
tsconfig.json strict; @/* -> src/*; includes examples/
.gitignore node_modules, dist, .expo, public/canvaskit.wasm, ...
playtest.json a scripted run of the game with real touches
CLAUDE.md standing instructions for Claude Code
AGENTS.md the same for other coding agents (an exact copy)
.claude/
settings.json Claude Code permissions for the repo's checks
skills/ 24 skills, one folder each with a SKILL.md
realtime-backend/
scaffold/backend/ the game's Cloudflare Worker, copied to backend/ when it goes online
docs/ agent-facing references: engine-api.md, games-contract.md,
one file per module and engine piece, cloudflare.md
src/
App.tsx mounts the game, wires exit and close, shows "Play" after an exit
theme.ts colours, radii and spacing used by the HUD chrome
game/
index.tsx THE GAME: default-exports a GameDefinition (starter: Sunset Sprint)
engine/ the engine, this game's own copy
index.ts everything @/engine exports
render/ core/ input/ hud/ ui/ audio/ physics/ models/ gpu/ feel/
2d/ character/ level/ combat/ bots/ save/ net/ story/ turns/
paths/ zones/ debug/ web/
modules/ racing/ puzzle/ shooter/ kingdom/
shell.ts shellModule.ts shellModule.web.ts math.ts
examples/ eleven complete games, one index.tsx each, plus README.md
assets/
icon.png
audio/ Sunset Sprint's engine loops and music, Ashvale's sounds
sfx/ short effects (pop.wav, the puzzle module's sounds)
models/ .glb models (KayKit's CC0 adventurers, Ashvale's buildings)
sprites/ sprite sheets for the examples
Folders that appear when you run things, all ignored by git: node_modules/, dist/ (from npm run export:web), public/canvaskit.wasm (copied by npm run web and npm run export:web), .expo/, and ios/ and android/ if you run npm run ios or npm run android.
Every folder of src/engine/ has its own page; the map with one line per folder is the engine overview.
What you edit
| File | What you change |
|---|---|
src/game/index.tsx | The game. Start from the starter, an example, or an empty file (Your first game). Split it into more files in src/game/ when it grows |
assets/ | Sounds, models, images and fonts, loaded with require(). Keep the total small: players download every version |
app.json | expo.name (the window and app name, "Sunset Sprint" in the template), expo.slug, expo.ios.bundleIdentifier and expo.android.package |
src/theme.ts | The colours the HUD chrome (TopBar, FinishCard, RoundButton) uses |
src/engine/net/config.ts | GAME_BACKEND_URL once you deploy a backend (Backend), and GAME_ID (below) |
GDD.md | The game's design document. It is not in the template: you or your agent create it with the first change (GDD.md and playtest.json) |
playtest.json | The scripted test. The template's drives Sunset Sprint, so rewrite it when your controls or labels differ |
The examples can go once you have picked a starting point; nothing imports them. Deleting examples/ also removes them from npm run typecheck.
What you normally leave alone
| File | Why |
|---|---|
src/engine/ | It is a library that happens to live in your repo. Change it when your game needs something the engine lacks, or to fix a bug, and keep its exports working. A platform difference goes here, never in the game (Platform layer) |
index.ts, index.web.ts, src/App.tsx | The entry chain. index.web.ts must keep loading CanvasKit before the game; keep "main": "./index" in package.json |
metro.config.js, babel.config.js | They make three the WebGPU build and compile TypeGPU shaders. Without them the engine does not build |
Dependency versions in package.json | They are pinned to what the Moonspell app compiles in. See The game contract |
"crescent" block in package.json | Metadata for the Moonspell pipeline. Harmless outside it |
Each game owns its copy of the engine. Inside the Moonspell app, engine files a game never changed are refreshed from the template on the game's next build. In your own repo nothing refreshes them: copy engine changes from a newer template yourself.
Placeholders to replace
The Moonspell pipeline fills these in when it creates a game repo. When you copy the template yourself, replace them:
| Placeholder | Where | Replace with |
|---|---|---|
__GAME_SLUG__ | name in package.json, twice in package-lock.json, GAME_ID in src/engine/net/config.ts | Your game's slug, such as orb-hunt |
__GAME_TITLE__ | The first line of CLAUDE.md and AGENTS.md | Your game's title |
__PREFIX__, __D1_DATABASE_ID__ | .claude/skills/realtime-backend/scaffold/backend/wrangler.jsonc | Nothing yet. The realtime-backend skill fills them in its copy in backend/ when you set up the backend |
GAME_ID matters more than it looks: it names the game's on-device storage (saves, settings, local leaderboards). Two games with the same id on one phone, or on one web origin, share their saves (Saves).
Also change the ids in app.json (com.rofi.crescentmoon.game in the template) to your own before you build for a phone.
A quick way to start a new game from a clone of the repository:
cp -R moonspell/templates/crescent-game orb-hunt
cd orb-hunt
git init
npm install
Then replace the placeholders above and commit once, so every later change is a diff you can read.
Docs and skills for agents
A game repo carries its own instructions, so a coding agent opened in it knows the engine without these docs:
| Where | What |
|---|---|
CLAUDE.md | What can be built and with which engine piece, the rules every game keeps, how to finish a task. Its section "Outside the Moonspell app" says which commands to use without the Moonspell CLI |
AGENTS.md | The same instructions for agents that look for that name. It is an exact copy of CLAUDE.md |
.claude/skills/ | 24 skills: game-router first, then one per genre, engine piece, art, audio, backend and the playtest |
docs/ | Short references the skills point to for every field and range |
examples/README.md | What each example shows and which route uses it |
Some skills and some lines in CLAUDE.md assume the Moonspell pipeline (the crescent CLI, generated art, live push to the app). Build with AI and Skills say which, and what to do instead.