Skip to main content

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:

InputGasBrakeSteer
Mouse or touchhold GAShold BRAKEhold the left and right arrow buttons
KeyboardW or UpS, Down or SpaceA 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 serve dist/ at the root of a domain or subdomain.
  • Serve .wasm files as application/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 seeWhyFix
"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 adapterUse 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 folderDelete 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 bundlerNothing: the scripts pass the folder (public) explicitly
npx expo-doctor reports packages that do not match the SDKThe template pins exact versions on purpose (The game contract)Leave them as they are
A 404 for /favicon.icoThere is no faviconNothing, 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 web and 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.