Overview
Crucible is a competitive 1v1 arena combat game: players own Gladiators and gear as ERC-721 NFTs (Polygon Mumbai) and fight in skill-influenced real-time battles. The stack is a TypeScript monorepo — Next.js 14 frontend (Canvas 2D + prediction), a standalone Node/Express game server for authoritative simulation, shared physics/combat in packages/shared, Prisma + Supabase persistence, and Solidity contracts via Hardhat.
As of the last workflow-core sync, Sprints 0–6 are complete (auth, NFT mint, CPU + PvP combat, progression/loot, multiplayer infrastructure). Sprint 7 (E2E tests, production deploy, demo polish) is the next milestone.
Problem & Context
Many Web3 games optimize for tokens over gameplay. Crucible prioritizes combat feel first, then layers NFT ownership without forcing crypto on every player (social auth + optional wallet link). The engineering challenge is low-latency, cheat-resistant multiplayer in the browser while keeping on-chain scope minimal.
Constraints
- Web-first: Canvas 2D at ~60 FPS; no heavy native client.
- Testnet-only demo (Mumbai); mainnet documented but not deployed.
- Server-authoritative: clients may predict; never decide outcomes.
- Small team / sprint-bounded scope across seven planned sprints.
Approach & Design Decisions
- Isomorphic shared library —
packages/sharedruns the same deterministic physics in the game server and browser for prediction + reconciliation. - Separate game server — Long-lived Socket.io process (Railway-style hosting) avoids serverless cold starts and holds match state in memory.
- 60 Hz sim / 20 Hz broadcast — Continuous ticks with compressed snapshots; client interpolates and reconciles.
- Template/instance content — Equipment authored in DB, published to JSON bundles in storage, loaded once at startup (no DB mid-match).
- Redis Socket.io adapter — Enables horizontal scaling groundwork for rooms and matchmaking.
- Anti-cheat layering — Stamina/cooldown validation, sliding-window rate limits, server-only truth.
Implementation Highlights
- apps/web — NextAuth (Google/Twitter), wagmi/viem mint UI, arena Canvas +
useClientPrediction/useRealTimeMatch, progression UI, admin bundle authoring, quick match + friends flows. - apps/game-server —
MatchInstance60 Hz loop,CombatEngine, CPU AI (three HP-tier strategies), matchmaking, disconnect/reconnect window,BlockchainListener(ethers v6). - packages/shared — Vectors, movement, melee arcs, four weapons, projectiles, skills, crafting, loot pools.
- contracts —
GladiatorNFT.sol(OpenZeppelin ERC-721, eight rolled stats at mint).
Results & Evaluation
- Core demo criteria met: mint, equip, fight CPU, quick-match PvP, friends/challenges, authoritative outcomes, leveling + skill trees, rarity-tiered loot, crafting — per sprint documentation through v2.1.0 (multiplayer).
- Sprint 7 still open: E2E coverage, Vercel + Railway production story, asset polish, demo video.
- No fabricated latency or CCU metrics — extended production baselines still TODO.
Tradeoffs & Limitations
- Scaling to very high concurrency still needs load balancer + sticky sessions beyond Redis adapter alone.
- Placeholder art; frontend test coverage called out as Sprint 7 scope.
- Testnet / programmer-art demo — not positioned as a production game SKU.
- Friends surface: some GET routes noted as deferred in sprint summaries.
What I Learned
Shipping shared deterministic physics for both authority and prediction, plus WebSocket room hygiene and content bundles that remove DB reads from the hot path, is the difference between a playable arena and a fragile prototype — and keeping NFT scope narrow (ownership + mint) avoids derailing gameplay work.