Primordial Orbs is a turn-based, two-player strategy game about terraforming planets, evolving life, and destabilizing your opponent through catastrophic events.
This repository contains the local hotseat + solo-play MVP, built as a one-screen web game with a strong emphasis on clarity, polish, and testability.
- Local 2-player hotseat play
- Solo play vs basic AI (personality toggles)
- Terraforming, Colonization, and Impact systems
- Asymmetric planet cores with passive abilities
- Action log + full replay from start
- Undo / History system
- Turn Coach hints + guided first turn
- One-screen responsive UI (no scrolling)
- Visual polish:
- Marble-style orbs with etched symbols
- Subtle shimmer on hover/selection
- Cataclysm Arena FX
- Deterministic Demo Mode for testing
- Automated Playwright UI layout guardrails
Networking multiplayer is a future goal β architecture decisions are being made with that in mind, but no network code exists yet.
- Frontend: React + TypeScript (strict)
- Styling: CSS tokens + component primitives (no UI framework)
- State: Deterministic reducer-driven game engine
- Testing: Playwright (UI layout + visual regression)
- Tooling: ChatGPT Codex used for structured PR-sized changes
No backend, no database, no canvas.
Local profiles and match stats are stored on this device (localStorage only).
Local Supabase is optional today; it is used for future Google Auth, cloud stats, and networking foundation work.
npm run dev:all
npm run dev:db
npm run dev:db:stopSee /server/README.md for full setup.
If you see an error like permission denied while trying to connect to the Docker daemon socket..., run:
sudo groupadd docker 2>/dev/null || true
sudo usermod -aG docker $USER
newgrp docker
docker psdocker ps
sudo systemctl start dockerOn WSL, ensure Docker Desktop integration is enabled if you use Docker Desktop.
npm runConfirm script names in the root package.json (dev:db, dev:db:stop, dev:all).
npm installnpm run devOpen:
http://localhost:5173
Build a production bundle:
npm run buildPreview the production bundle locally:
npm run previewBuild and run the production Docker image:
make web-docker-build
make web-docker-runThe containerized app is served at http://localhost:8080.
- Nginx serves the built
distoutput. - SPA routes fall back to
index.html. - Hashed assets under
/assets/are sent with long-lived immutable cache headers. index.htmlis intentionally not long-cached to prevent stale deployments.- CI validates dependency install, optional lint/tests, production build, and Docker image build.
To run the game in a stable, deterministic state (used by tests and screenshots):
http://localhost:5173/?demo=1
Optional flags:
&shots=1β disables animations/transitions for stable screenshots
Share setup links:
?cfg=...pre-fills setup values (cores, mode, hand size, core size, AI options, seed, density)&autostart=1starts immediately with that setup- Share links only include setup config (no match history, hand/deck, or private state)
- Mouse: All actions clickable
- Keyboard shortcuts:
Dβ DrawEβ End PlayAβ AdvanceUβ Undo
src/
ββ engine/
ββ ui/
β ββ components
β ββ icons
β ββ theme
β ββ utils
ββ main.tsx
tests/
ββ ui.layout.spec.ts
ββ helpers/layout.ts
Run UI tests:
npm run test:uiUpdate visual baselines:
npm run test:ui:update- Theme tokens:
src/ui/theme/tokens.css - Components:
components.css - Orbs:
orbs.css - Vite orb sprites: place ornate orb assets in
/public/assets/orbsand reference at runtime as/assets/orbs/...
- Shared orb rendering now lives in
src/ui/components/OrbVisual.tsxand is reused by hand, slot, and impact preview contexts. src/ui/components/OrbToken.tsxis the interaction wrapper (click/disabled/tooltips), whileOrbVisualowns orb framing + animation classes.- Tune motion/glow in:
src/ui/theme/orbs.css(idle breathing, hover pulse, selected halo, entrance timing)src/ui/theme/layout.css(slot placement ripple)src/ui/theme/arena.css(arena/impact flash + last-impact polish)
- Keep gameplay logic in engine/reducer files; visual changes should stay in UI components/theme CSS unless a UI event flag is needed.
Some orb/core SVGs can look visually undersized when their artwork only occupies a small portion of the SVG canvas. This repo includes a deterministic normalization utility that tightens each SVG viewBox to the actual geometry bounds and adds a small safety margin, so icons fill their slot more consistently without rasterizing assets.
- Script:
scripts/normalize-svg-viewbox.mjs - Default targets:
src/assets/orbsandsrc/assets/cores - Default padding:
6%of max(art width, art height) - Output stays SVG/text-only (no PNG/WebP generation)
Check whether files need normalization:
npm run icons:checkNormalize SVGs in place:
npm run icons:normalizeNotes:
- The script preserves/ensures
viewBoxand removes fixedwidth/heightattributes when found, so scaling remains responsive in the React/Vite pipeline. --checkexits non-zero if a file would change (CI-friendly).- For safety, SVGs with transforms or unsupported constructs are skipped with warnings instead of being rewritten.
- This workflow is intentionally text-only so Codex/PR review stays clean and diffable.
AI is simple and deterministic, designed for playtesting.
Audio is driven by src/audio/audioManager.ts with path wiring in src/audio/audioManifest.ts.
- Asset locations:
- SFX:
public/sfx/*.mp3 - Ambient music:
public/music/ambient_space.mp3
- SFX:
- Logical SFX events:
click,orbPlace,impactCast,impactLand,draw,endPlay,error,unlock. - Logical music events:
ambient. - To replace a sound, keep the same filename/path in
public/or update onlysrc/audio/audioManifest.ts. - SFX playback applies subtle per-play pitch/volume variation to avoid robotic repeats.
- Ambient music is looped and does not use playback variation.
- One PR = one focused improvement
- Prefer clarity over cleverness
- No silent layout regressions
- External playtesting
- Balance tuning
- Expanded solo mode
- Networking foundation
TBD
Use this quick checklist after setup to confirm your local environment is healthy.
docker psYou should see Docker respond without daemon/permission errors.
npm run dev:dbThen verify status:
cd server && npx supabase statusnpm run devOpen http://localhost:5173 (or http://localhost:5173/?demo=1 for deterministic demo mode).
cd server && npx supabase status -o envCopy VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY into your local .env.local (see server/.env.example).
For full backend setup/details, see server/README.md.