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,
};
  • id must match the id in games.config.json
  • register() / unregister() wire up game message handlers, analytics providers, etc.
  • GameLayer is 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:

  1. Set "enabled": true on the game entry in games.config.json.
  2. Add a GAME_TEST_OVERLAYS entry in e2e/src/games/game.registry.ts so @critical specs (notably critical-path.spec.ts) can drive the game.
  3. 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-failure and trashcat-<env>-external-<gameId>-canary-tests-failure.
  4. 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 via frontend/src/game-selection/ui/game-selection-menu.tsx).
  • Exposes the shared speedrun-button HUD testid (automatic if the game renders the standard learning HUD).
  • Transitions to GameStateType.Game when the speedrun starts.