Adding New Games to the Monorepo
This monorepo supports multiple games. Each game has a frontend plugin, an entry in games.config.json, and a feature folder under frontend/src/. Use frontend/src/example-web-game/ as the reference template.
Checklist
1. Add Entry to games.config.json
Add a new object to the games array at root games.config.json:
{
"id": "my-game",
"type": "web",
"enabled": true,
"label": "MY GAME",
"pageTitle": "My Game",
"bridgePath": "./frontend/src/my-game/bridge",
"iconSrc": "/my-game/images/game-icon.png",
"faviconHref": "/my-game/images/fav-icon.png"
}
| Field | Required | Notes |
|---|---|---|
id |
Yes | Must match the frontend feature folder name (frontend/src/<id>/) |
type |
Yes | "web" |
enabled |
Yes | false hides from selection and excludes from tooling |
label |
Yes | Display name in game selection menu (uppercase) |
pageTitle |
Yes | Browser tab title |
bridgePath |
No | Relative path to game-specific bridge TS schemas |
iconSrc |
No | Path to icon served from frontend/public/ |
faviconHref |
No | Path to favicon served from frontend/public/ |
2. Create Frontend Feature Directory
Create a feature folder at frontend/src/<id>/ following Feature Sliced Design. Mirror frontend/src/example-web-game/:
frontend/src/<id>/
├── <id>.plugin.ts
└── ui/
└── <id>-game-layer.tsx
3. Create the Game Plugin
The plugin implements GamePlugin from frontend/src/game-selection/models/game-plugin.model.ts:
import type { GamePlugin } from '../game-selection/models/game-plugin.model';
import { MyGameLayer } from './ui/my-game-layer';
export const myGamePlugin: GamePlugin = {
id: 'my-game',
register() {},
unregister() {},
GameLayer: MyGameLayer,
};
idmust match theidingames.config.jsonregister()/unregister()wire up game message handlers, analytics providers, etc.GameLayeris the root React component rendered when this game is active. Mount<GamesSharedLayer />and<LearningLayer />alongside game-specific overlays.
4. Register the Plugin
Add the plugin to frontend/src/game-selection/services/game-plugin.registry.ts:
import { myGamePlugin } from '../../my-game/my-game.plugin';
const GAME_PLUGINS: Record<string, GamePlugin> = {
// ... existing plugins
[myGamePlugin.id]: myGamePlugin,
};
In DEV mode, the registry warns if an enabled game has no matching plugin entry.
5. Add Game Icons
For the game selection menu, place icon assets in frontend/public/<id>/images/ (or another public path). Those paths must match the iconSrc / faviconHref values in games.config.json.
Game-Specific Bridge Models
For game-specific TypeScript models that cross the React ↔ Game bridge, colocate Zod schemas under frontend/src/<id>/bridge/ and set bridgePath in games.config.json. Follow the same patterns as shared/ts/bridge/.
Systems That Auto-Discover Games from games.config.json
These systems read games.config.json and operate on all enabled games automatically:
| System | What it does | Filter |
|---|---|---|
game-plugin.registry.ts |
DEV warning for missing plugins | All enabled games |
loadGamesConfig() (@shared/games-config) |
Single source of truth for parsing games.config.json |
All games — consumers filter as needed |
loadCanaryTestsGames() (infra/utils/canary-tests-games.util.ts) |
Filters config for canary-tests alarms and metrics | enabled === true |
e2e/src/games/game.registry.ts (GAME_IDS, GAME_TEST_OVERLAYS) |
Drives @critical Playwright specs (including canary tests) |
enabled === true and a GAME_TEST_OVERLAYS entry |
Canary tests coverage
The Canary Tests workflow (.github/workflows/canary-tests.yml) runs Chromium @critical Playwright checks every 10 minutes in integration and production. Every game with "enabled": true in games.config.json is included automatically — there is no separate opt-in flag.
To add a new game to canary tests:
- Set
"enabled": trueon the game entry ingames.config.json. - Add a
GAME_TEST_OVERLAYSentry ine2e/src/games/game.registry.tsso@criticalspecs (notablycritical-path.spec.ts) can drive the game. - Redeploy monitored integration or production through the corresponding Deploy Integration or Deploy Production GitHub Actions workflow. Infrastructure keeps the EventBridge schedule enabled and adds the per-game CloudWatch alarms
trashcat-<env>-<gameId>-canary-tests-failureandtrashcat-<env>-external-<gameId>-canary-tests-failure. - Verify one scheduled run in GitHub Actions (workflow Canary Tests) or run the critical gate locally:
pnpm --filter trashcat-e2e exec playwright test --project=chromium --grep @critical
Prerequisites for a game to pass the canary critical path:
- Exposes the shared
game-selection-<gameId>testid in the game-selection menu (automatic viafrontend/src/game-selection/ui/game-selection-menu.tsx). - Exposes the shared
speedrun-buttonHUD testid (automatic if the game renders the standard learning HUD). - Transitions to
GameStateType.Gamewhen the speedrun starts.
