Overview
- What: a full-stack PvP web game where players build squads of original shape-shifting combat units under a hard power budget and battle other players turn by turn.
- Hardest problem: keeping battles deterministic and race-free, so the server is the only authority and a replay always matches the live match.
- How it is validated: 2,096 tests across 104 files, with engine golden-log snapshots; each server test boots its own PGlite database.
The engine is pure and deterministic and is shared by the API and the SPA. The server snapshots both squads onto the match row and stamps the seed and engine version, so old replays survive later rebalances. The repository is private; the live demo is the public artifact.
Architecture
Highlights
- Pure, deterministic, dependency-free TypeScript engine (v3.2.0) in
shared/engine, imported by both the API (authoritative resolver) and the SPA (playback). - The client's entire vocabulary is two squad ids to start a match and
{expectedVersion, action}per turn; the server snapshots both squads onto the match row and stamps seed + engine version, so replays survive future rebalances. - Optimistic concurrency: a conditional
UPDATE ... WHERE version = ?serializes racers and the loser gets a 409. - The battle UI is a fold over the persisted event log: one cursor mechanism drives both live animation and the replay viewer, and no battle math runs in React.
- Exactly one Vercel serverless function (
api/index.ts, a Hono router); Vercel Hobby caps non-framework projects at 12 functions, so the cap is made structurally unhittable. - 54-sector world, frozen 3,063-species catalogue, bots, rogue operators, live and asynchronous PvP, matchmaking, spectating, trading, auctions, clans, seasons and achievements.
- GitHub Actions CI (structural guards, typecheck, lint, tests, build, then migrations); PWA with service worker and offline page; free-tier infrastructure that pauses rather than bills.
Screenshots
What I learned
Putting all battle math in one pure module, with the UI as a fold over the event log, meant live play and replay became the same code path instead of two features to keep in sync. Treating the 12-function Vercel Hobby cap as a design constraint, and enforcing a single /api function with a structural guard script, was better than hoping not to hit it.
Limits
Phase 3 is complete (milestones M19 to M25). The source repository is private, so only the live demo is linked.