#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+ (
.nvmrcsays 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 theArcadeglobal 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, runnode scripts/dev-api.mjs(defaults to :8002) and proxy/apito it in yourvite.config.ts(sightlines does this). Same-origin proxying keeps the session cookie working. SetARCADE_APIif the dev API lands on another port. - Realtime/multiplayer also flows through the dev API, so proxy
/wstoo (withws: true) andArcade.realtimeworks 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.