# Game router

> How the game-router skill turns a player's idea into a genre and a route (a module config, an example to copy, or from scratch), with its classify and route tables.

The **game-router** skill (`templates/crescent-game/.claude/skills/game-router/SKILL.md`) is the first thing an agent reads for any request that makes a game or changes what kind of game it is. It does two things before any code is written: it classifies the request into a genre, and it picks the route that builds that genre best. Then the agent reads only the skill and doc that route names.

A route is one of four kinds:

| Route kind | Means |
| --- | --- |
| `module:<name>` | The genre is a config object for a genre module: `defineRaceGame`, `defineShooterGame`, `definePuzzleGame` or `defineKingdomGame`. See [Game modules](https://docs.moonspell.app/modules/overview.md). |
| `foundation:character` | A game on the engine's character controller, cameras, bots and combat kit, copied from an example and changed. |
| `kit:turns` | A turn-based board game on the turns kit (`TurnRules`, `useOpponent`, `useTurnRoom`). See [Turns](https://docs.moonspell.app/engine/turns.md). |
| `scratch` | Written in `src/game/` with engine primitives: the 2D layer for flat games, `EngineCanvas` for 3D. |

Before routing, the skill reads `GDD.md`. If the game has one, its genre and rules hold. A change request keeps the current genre unless it names another one: "turn my race into a space game" is still racing; "add enemies that chase me" to a parkour game is parkour plus bots on the same route; "make it a card game" is a new route with the same title.

## 1. Classify

Players often name a well-known game as shorthand; the skill matches those names too. This table describes them by genre instead.

| Player says (any of) | Genre |
| --- | --- |
| race, racing, kart, drift, lap, circuit, car, F1, rally, highway, drive | racing |
| parkour, obby, platformer, jump over gaps, rooftops, lava floor, checkpoints, speedrun course, climb | parkour |
| arena, brawl, fight in a ring, knock off, sumo, dodgeball, sword duel (no guns) | arena |
| tag, chase, hide and seek, cops and robbers, catch me, zombies that chase, infected | tag |
| stealth, sneak, guards, steal, heist, escape the asylum, backrooms, monster hunts you, horror, flashlight | stealth |
| top-down, dungeon, survivors, horde, waves of enemies, twin-stick, bullet heaven | top-down |
| side-scroller, 2D-ish, a classic run-and-jump platformer, run right and jump, endless runner | side-view |
| shooter, gun, FPS, deathmatch, team deathmatch, capture the flag, king of the hill (with guns), gun game, paintball, snipers, aim, shoot the bots | shooter |
| block puzzle, drag blocks to clear lines, wood blocks on a grid, number merge, merge, match three, gems, pop the groups, tap to blast groups, water sort, ball sort, colour tubes, potions, pour | puzzle |
| chess, checkers, draughts, connect four, reversi, othello, tic-tac-toe, gomoku, dots and boxes, mancala, "play against the computer", a turn-based duel on a board | board |
| cards, deck, dice, poker, memory, trivia, quiz | cards / menu |
| kingdom, village I build and defend, a town builder with hero sorties, a survival city in the snow, a farm game with crops and orders, farm with a story, crops and raids, castle and army, samurai province, colony | kingdom |
| builder, city, idle, tycoon, tower defense, resources, upgrade timers (no story, no sorties) | strategy |
| darts, bowling, golf, basketball, flick, swipe to throw | sports minigame |

## 2. Route

What each genre maps to, where the agent starts, and what it reads next.

| Genre | Route | Start from | Then read |
| --- | --- | --- | --- |
| racing | `module:racing`: a `defineRaceGame` config | `src/game/index.tsx` as shipped (Sunset Sprint) | `car-race-game`, `docs/racing-module.md` |
| parkour | `foundation:character` | copy `examples/rooftop-hop/index.tsx` | `character-game`, `docs/character-controller.md` |
| arena | `foundation:character` | copy `examples/lantern-tag/index.tsx`, replace lanterns with the arena rule (knock-off, king of the hill) | `character-game`, `docs/bots.md` (health, knockback, swords: `docs/combat.md`) |
| tag | `foundation:character` | copy `examples/lantern-tag/index.tsx` | `character-game`, `docs/bots.md` |
| stealth / horror | `foundation:character` | copy `examples/lantern-tag/index.tsx`, `corridors` preset, dark theme, fog | `character-game`, `docs/bots.md` |
| top-down | `foundation:character` | copy `examples/lantern-tag/index.tsx` with the `top` camera and `look: false` | `character-game` |
| side-view | 2D platformer (flat, the default) or `foundation:character` (3D seen from the side) | copy `examples/sky-runner/index.tsx` (the `platformer-2d` skill), or `examples/rooftop-hop/index.tsx` with the `side` camera | `character-game` |
| shooter | `module:shooter`: a `defineShooterGame` config (modes `ffa`, `tdm`, `gun-game`, `ctf`, `koth`) | copy `examples/flag-rush/index.tsx`; for a rule no mode covers (waves, bomb defusal) copy `examples/pulse-arena/index.tsx` and use the combat kit directly | `shooter-game`, `docs/shooter-module.md` (weapons: `docs/combat.md`) |
| puzzle | `module:puzzle`: a `definePuzzleGame` config (2D, no 3D) | copy `examples/moon-blocks/index.tsx` (block) or `examples/moonrise/index.tsx` (merge, drawn tiles) | `puzzle-game`, `docs/puzzle-module.md` |
| board | `kit:turns`: the game as `TurnRules`, `useOpponent`, `useTurnRoom`, drawn on the 2D layer | chess: copy `examples/moon-chess/index.tsx` and change its look, rivals and names; another board game: keep that file's screens and flow, swap in your own `TurnRules` | `crescent-game-change`, `docs/turns.md`, `docs/2d-layer.md` |
| cards / menu | `scratch` on the 2D layer (`GameCanvas2D`), no 3D | a new `src/game/index.tsx` (`examples/tap-pop/` shows the layer) | `crescent-game-change`, `docs/2d-layer.md` |
| kingdom | `module:kingdom`: a `defineKingdomGame` config | copy `examples/oath-of-ashvale/index.tsx` | `kingdom-game`, `docs/kingdom-module.md`, `cutout-art` for portraits and icons |
| strategy | `scratch`: tap UI, `top` camera if 3D | a new `src/game/index.tsx` | `crescent-game-change` and `saves` (buildings as upgrades, currencies, offline earnings) |
| sports minigame | `scratch` with `EngineCanvas` (3D), or the 2D layer for a flat game | a new `src/game/index.tsx` | `crescent-game-change`, `touch-controls`, `docs/2d-layer.md` |

The `docs/...` paths are the template's own agent docs, inside the game repo.

## Camera, controls and bots per genre

The same table in the skill also fixes the defaults, so every game of a genre starts from choices that work on a phone.

| Genre | Camera | Controls | Bots |
| --- | --- | --- | --- |
| racing | the module's chase cam | steer + gas + brake (`controls.layout`) | AI cars (`opponents`) |
| parkour | `third` (offer `side` for a 2D feel) | stick + look + Jump | none, or 1 to 2 `coward`s as moving hazards |
| arena | `third` | stick + look + Jump (+ `a` for the action) | `guard` / `hunter`, 2 to 5, roster fill |
| tag | `third` | stick + look + Jump | `hunter` 2 to 4 (or the player hunts `coward`s) |
| stealth / horror | `first` (horror) or `third` | stick + look (+ Jump) | `guard` 2 to 3 on patrol routes or one `hunter`, `hard` hearing |
| top-down | `top` | stick + buttons (`a`, `b`) | `hunter`s in waves (`roster` to spawn) |
| side-view | 2D camera follow / `side` | stick (x only) + Jump, `look: false` | walkers on the 2D bodies, or `coward` / `guard` |
| shooter | `third` (the module's over-the-shoulder rig) or `first` | the module's: stick + look + FIRE + Jump (+ SWAP) | the module's (`seats`, `bots.difficulty`) |
| puzzle | none (2D board) | one thumb: drag (block), swipe (merge), tap (match, sort) | none (the dev AUTO plays the solver) |
| board | none (2D board) | tap a piece then a square, or drag it | three rivals (easy, medium, hard) with personalities that talk through the backend's model; the search plays alone offline |
| cards / menu | none | taps and drags (`onPointer`) | `TurnRules` + `useOpponent` for a turn-based duel; otherwise an AI you write |
| kingdom | the module's 3/4 town camera (drag to pan) and top-down sortie rig | tap lots and buttons in the village; the floating `<Joystick>` on sorties | the module's squads and camps |
| strategy | `top` or none | taps | none |
| sports minigame | fixed camera | swipe / flick (`GameCanvas2D` swipes, or one manual gesture) | optional |

## Online and saved by default

Every route gets the online default from the **realtime-backend** skill when a backend is available: a leaderboard (time or score), presence ("N playing now"), and rooms where the genre is versus or co-op (racing ghosts; tag, arena and shooter rooms with bot fill; the shooter module runs its own rooms; board games get a friend's room from a link through `useTurnRoom` and the talking opponent on `/ai/turn`). Single-player genres (parkour, puzzle, cards) get a leaderboard and presence only.

Every route that keeps anything between sessions keeps it with the **saves** skill: runners a shop and upgrades, survivors permanent upgrades, idle games offline earnings, RPGs XP and gear. See [Backend](https://docs.moonspell.app/backend/overview.md).

## What the router says is not possible yet

The skill's section 3 lists the limits and the closest thing to build instead. In short:

- **Shooters**: bomb defusal, escort and waves are not modes; build them on a copy of `ShooterGame.tsx` or on `pulse-arena` and call it a first version. Multiplayer is up to 8 per room, hosted by the oldest player's phone.
- **Hordes**: waves of 8 bots or fewer.
- **Puzzles**: mechanics beyond block, merge, match and sort (screws, parking jam, hexa boards, word puzzles) get the closest pack.
- **Board games**: chess is built in (`chessRules`); others need their `TurnRules` written. No turns across days and no notifications; a room keeps its game for 30 minutes after both players leave.
- **Kingdoms**: no alliances, gacha heroes, PvP, shared world map or tower placement.
- **Shared worlds**: a Durable Object in the game's backend; without one, the world stays on the device.

## Worked examples

These rows are from the skill.

| Request | Route | Start | Next read |
| --- | --- | --- | --- |
| "A night race through a neon city with 5 rivals" | `module:racing` | Sunset Sprint config | `car-race-game` |
| "A parkour game on rooftops, make the jumps hard" | `foundation:character` / parkour | rooftop-hop (course preset, `jumpHeight` 1.4, gaps near 3 m) | `character-game` |
| "Sneak through a dark maze, guards hear your steps, first person" | `foundation:character` / stealth | lantern-tag (corridors preset, `first` camera, 3 `guard`s `hard`, loud footsteps) | `character-game` + `docs/bots.md` |
| "Capture the flag with my friends, neon, bots when nobody's on" | `module:shooter` | flag-rush (`mode: 'ctf'`, neon theme, `seats: 6`) | `shooter-game` |
| "Tag with my friends, bots fill in when nobody's online" | `foundation:character` / tag | lantern-tag (rooms + `createRoster` fill, `hunter`s) | `character-game` + `realtime-backend` |
| "A memory card game with a leaderboard" | `scratch` (Skia) | a new `src/game/index.tsx` | `crescent-game-change` + `realtime-backend` |
| "Chess I can play with my friends or against a cheeky computer" | `kit:turns` / board | moon-chess (rename the rivals, cheeky personas) | `docs/turns.md` + `realtime-backend` |
| "Connect four against the computer" | `kit:turns` / board | moon-chess's screens and flow, a 7 by 6 board, connect-four `TurnRules` | `docs/turns.md` |

## Writing the route down

The agent puts the route on its own line in the commit message (`Route: foundation:character / stealth`) and keeps anything it tells the player about the game, not the route.

## Adding a route

A new module or foundation is one row in the Route table (genre, route, what to copy, camera, controls, bots, docs), its trigger words in Classify, and its limit removed from section 3. See [Writing a module](https://docs.moonspell.app/modules/writing-a-module.md).
