#Local development

The day-to-day loop: the commands you'll run, how to work on one game without rebuilding the rest, how to test multiplayer with two players on one machine, and what to do when the build complains.

#Prerequisites

  • Node 24+ (.nvmrc says 24).
  • Windows, macOS, and Linux all supported; all repo tooling is plain Node, no bash/make.

#Commands (repo root)

Run npm install once at the root, because the landing and docs generator needs it.

Command What it does
npm run build Full build: SDK + every game + landing page + docs → dist/
npm run build -- --only <slug> Rebuild one game (keeps the rest of dist/), then the landing page
npm run preview Serve dist/ at http://localhost:8002 (auto-increments if busy) with production-style routing + the local /api
npm run validate Everything CI checks without building: manifests, achievement catalogs, and generated-file drift
npm test Manifest + achievement validation tests
npm run gen-achievements Regenerate the API's baked-in achievement catalog (run after editing any achievements.json)
npm run new-game -- <slug> --template vite|vanilla Scaffold a new game
node scripts/dev-api.mjs Standalone API on :8002 for game-only dev loops (player data in .data/, dev-editable JSON)

Handy pages once it's up: / (the arcade), /docs/ (this documentation, rendered from these files), /profile (your GS and unlocks), /achievements/<slug>/ and /leaderboard/<slug>/ (one game's cabinet and boards), and /debug/ (a style harness for the achievement popup and everything around it; see achievements). /assets/ is the brand marks and the searchable icon pack.

The top bar is landing/static/nav.js plus landing/static/nav.css, rendered into <div id="arcade-nav">. A new host page gets the bar, sign-in and the GS chip by adding that div, <script src="/nav.js" defer>, and /styles.css (which @imports nav.css) — or /nav.css alone if it doesn't want the rest of the arcade's styles.

Games get all of that injected at build time (scripts/build-all.mjs), so a game never adds it by hand. On a game page the bar also flags <html> with arcade-over-game, which reserves its height as padding-top on <body> — so a game's own top row needs no clearance of its own. See the arcade nav bar.

#Genres

genre in game.json is free text. The rail on the landing page is built from whatever genres the live games declare, with counts. Pick an existing one to group with it, or a new one to start a group. Games without a genre collect under "Other".

Featured is the three freshest games, ranked on the later of addedAt and updatedAt — so shipping an update puts a game back in front, not just adding a new one. The badge tells them apart (NEW vs UPDATED) and expires after 30 days, because a badge is a claim about right now.

#Gameplay guides

A game's own player-facing docs live in games/<slug>/docs/, so they ship in the same pull request as the game change that motivated them. index.md becomes /docs/games/<slug>/ and any sibling becomes a sub-page. See shipping an engine export for the rest of a game's build contract.

#Brand assets

File Served at Rules
landing/static/brand/gamesight-monogram.svg /brand/… Blurple as authored. Only blurple #5555f2, white or black; use .brand-blurple / .brand-white / .brand-black. Has no intrinsic size, so use <img>, not a CSS background or mask.
landing/static/brand/gamesight-wordmark.svg /brand/… White as authored. Same three-colour rule as the monogram; usable anywhere.
landing/static/landing/hero-bg.webp /landing/… Hero backdrop (.hero::before).
landing/static/landing/foot-bg.webp /landing/… Build-a-game banner backdrop (.build-cta).

#Working on a single game

Use the game's own dev loop for fast iteration:

cd games/my-game
npm install
npm run dev        # Vite dev server with HMR

Caveats in a game's own dev server:

  • The SDK script (/sdk/v1/arcade.js) 404s, so the Arcade global is undefined. The templates guard for this; keep the guard in your code.
  • To exercise /api (auth, leaderboards, your game's DB) in a game's own dev server, run node scripts/dev-api.mjs (defaults to :8002) and proxy /api to it in your vite.config.ts (sightlines does this). Same-origin proxying keeps the session cookie working. Set ARCADE_API if the dev API lands on another port.
  • Realtime/multiplayer also flows through the dev API, so proxy /ws too (with ws: true) and Arcade.realtime works in a game's own Vite loop.

#Previewing the full arcade

npm run build && npm run preview

The preview server mimics CloudFront's URL handling (/games/x/index.html, extensionless paths resolve the same way), so if it works in preview it works deployed.

#Testing realtime / multiplayer locally

npm run preview (and node scripts/dev-api.mjs) also serve a WebSocket relay at ws://localhost:8002/ws, entirely in-process, with no AWS and no API Gateway. The SDK auto-discovers it via same-origin GET /api/realtime/info, so any game using Arcade.realtime just works.

To play both sides on one machine, open the arcade in two windows (or one normal + one incognito) and append a dev-impersonation query to each: ?dev_as=alice@gamesight.io and ?dev_as=bob@gamesight.io. Each opens the realtime socket as a distinct player. This is dev-only (gated on dev-auth mode) and ignored in production, where the socket is authed by the signed-in session's token.

#dist/ layout (mirrors the production bucket)

dist/
├── index.html            landing page
├── 404.html
├── styles.css, nav.css, favicon.svg, *.js   shared styles + page scripts
│                          (nav.css is what games get injected)
├── docs/**               this documentation, rendered from docs/*.md
├── achievements/         the dashboard, plus <slug>/ per game
├── leaderboard/          the dashboard, plus <slug>/ per game
├── assets/index.html     brand marks + the icon pack browser
├── icons/                999 icons, icons.json is the search index
├── profile/, debug/
├── games.json, achievements.json   the catalogs the pages read
├── assets/<slug>.<ext>   game thumbnails
├── assets/achievements/<slug>/…    achievement icon images
├── brand/, landing/      brand marks and page artwork
├── sdk/v1/arcade.js      shared SDK (+ arcade.esm.js, sourcemaps)
└── games/<slug>/...      each game, verbatim from its build.outputDir

#Troubleshooting

Build errors, 404s, platform-mismatched node_modules, and the SDK behaving oddly in a game's own dev server are all covered in troubleshooting.

View docs/technical/local-development.md on GitHub ↗