Quickstart
This page gets the starter game, Sunset Sprint, running in your browser. It takes one clone, one install and one command. Then it shows how to make a static web build, swap in an example game, and where to go for phones.
Everything here is plain Expo. You do not need the Moonspell app or any Moonspell account. This is the "engine only" path; the other one is below (What is open and what is private).
Prerequisites
- Node 24 and npm. Tested with Node 24.18.0 and npm 11.16.0.
- Git.
- A browser with WebGPU. Tested in Google Chrome 157 on macOS. The engine asks players for "the latest Chrome, Edge or Safari"; those other browsers, Firefox, Windows and Linux have not been tested yet. There is no WebGL fallback: a browser without WebGPU shows a notice instead of the game.
1. Get the code
git clone https://github.com/<org>/moonspell.git
cd moonspell/templates/crescent-game
templates/crescent-game/ is the game template: the engine in src/engine/, the starter game in src/game/, eleven example games in examples/. Every command on this page runs from that folder.
2. Install
npm install
Use a real install in this folder. A node_modules that is symlinked from somewhere else, or left over from an older checkout, breaks the web bundle (see Troubleshooting).
3. Run it in the browser
npm run web
This copies Skia's WebAssembly file (canvaskit.wasm) into public/, then starts Expo's dev server on http://localhost:8081. Open that URL. The first bundle takes about 15 seconds; later reloads are faster. To use another port, pass it through: npm run web -- --port 8790.
You should see Sunset Sprint: a coastal circuit at sunset, your car and three rivals, and a HUD with the lap, your position, your best lap and your speed.
Drive it:
| Input | Gas | Brake | Steer |
|---|---|---|---|
| Mouse or touch | hold GAS | hold BRAKE | hold the left and right arrow buttons |
| Keyboard | W or Up | S, Down or Space | A and D, or Left and Right |
The keyboard works because the engine's touch controls also listen to keys in a browser (Input). The dev server also shows development extras that production builds hide: an AUTO button that drives the car for you, and a telemetry line.
Edit src/game/index.tsx and save: the page reloads with your change. Try laps: 1, or timeOfDay: 'night' in theme.
4. Make a static web build
npm run export:web
This writes a static site to dist/: index.html, one JavaScript bundle under _expo/static/js/web/, the game's assets, and canvaskit.wasm. Serve it with any static server and open it:
cd dist
python3 -m http.server 8765 --bind 127.0.0.1
# open http://127.0.0.1:8765/
The production build has no AUTO button and no telemetry line. Two things to know before you host it:
- The build uses absolute URLs (
/_expo/...,/assets/...), so servedist/at the root of a domain or subdomain. - Serve
.wasmfiles asapplication/wasm.
Hosting, sub-paths and browser support are on Web.
5. Run it on a phone
npm run ios # expo run:ios
npm run android # expo run:android
Each makes a normal Expo development build of the game: Expo generates the native project, builds it and starts Metro. You need Xcode and CocoaPods for iOS, Android Studio and a JDK for Android, and you should first change the app ids in app.json (expo.ios.bundleIdentifier and expo.android.package) to your own.
These two commands are configured and the generated native projects were checked, but a build on a simulator or a device has not been run by the maintainers yet. The full steps and what is known are on iOS and Android.
6. Run an example
An example runs by becoming the game. Copy its file over src/game/index.tsx:
cp examples/tap-pop/index.tsx src/game/index.tsx
npm run web
Tap Pop is a 2D game: tap the drifting dots before the 30 seconds run out. To put Sunset Sprint back:
git checkout src/game/index.tsx
Tap Pop has been run in a browser; the other ten examples type-check with the template (npm run typecheck includes examples/) but have not all been run on the web. The list of examples and what each one shows is on Examples.
Check your work
npm run typecheck # tsc --noEmit
npm test # the engine's unit tests (Node's test runner)
npm run export:web # the build players get; a Metro error here means the game will not open
Metro does not stop on type errors, so run npm run typecheck as well as the build.
Troubleshooting
| What you see | Why | Fix |
|---|---|---|
| "This browser can't run this game yet. Try the latest Chrome, Edge or Safari." | The browser has no navigator.gpu, or it returns no adapter | Use a current Chrome. Headless Chrome needs --enable-unsafe-webgpu |
Unable to resolve "react-native-web/dist/exports/..." | node_modules is stale or symlinked from another folder | Delete node_modules and run npm install in this folder |
setup-skia-web prints "Metro bundler not detected. Assuming the project is using Webpack." | Skia's setup script guesses the bundler | Nothing: the scripts pass the folder (public) explicitly |
npx expo-doctor reports packages that do not match the SDK | The template pins exact versions on purpose (The game contract) | Leave them as they are |
A 404 for /favicon.ico | There is no favicon | Nothing, or add one to public/ |
Want your game in the Moonspell app?
The steps above never touch Moonspell. To publish a game to the Moonspell app on iPhone and Android and to the web, start from the starter instead: npx https://moonspell.app/api/dev/cli/latest.tgz init my-game downloads it with the built crescent CLI, and you sign in with a Moonspell account. See Publish to the Moonspell app.
Next steps
- Your first game: build a small 3D game from scratch in
src/game/. - How it works: what happens between
npm run weband a frame on screen. - Project structure: what is in the folder you ran, and what to rename before you make it your own.
- Build with AI: open the folder in Claude Code and ask for a game.