22. Technical Architecture (Next.js · Clerk · Three.js · AI Governance)
Design-level architecture only – no implementation. The guiding principle is unchanged: an authoritative server with a thin rendering client. The browser renders and sends intents; the server owns every rule, timer, ledger entry and vote. New in v0.6: the client is a Next.js (App Router) application written in TypeScript/TSX, identity is handled by Clerk, and Three.js remains the world renderer. New in v0.7: an AI Governance service runs every AI leader persona, and an AI citizen simulation inside each shard runs the AI population (Sections 22.12–22.15). The principle holds for AI too: AI services propose; the authoritative game server validates and executes.
Decision (Patrick, v0.6) – tech stack
- Client app: Next.js with TypeScript/TSX (App Router). Non-realtime pages (nation pages, profiles, election results, news) are Server Components; the 3D map is a client component loaded with
next/dynamicandssr: false. - Authentication: Clerk (
@clerk/nextjsin the web app,@clerk/backendon the game server). The Clerk userId is the identity key; one-account-one-nation is stored in our database, keyed by that userId, and mirrored to ClerkpublicMetadata. - World rendering: Three.js stays. Recommended integration: React Three Fiber + drei inside Next.js; a plain, imperative Three.js renderer mounted in a client component is also acceptable.
- Realtime: a separate, authoritative Node.js/TypeScript game server (WebSockets) on a container host such as Fly.io or Railway verifies Clerk session JWTs. PostgreSQL (Drizzle; Prisma acceptable), Redis and a durable job scheduler run the real-time world clock. One Turborepo + pnpm monorepo shares TypeScript types and game-rule packages between Next.js and the server.
- Platform facts below were checked by quick web search on October 12, 2026 (Appendix A); library APIs change, so each is marked [VERIFY at build time] where it matters.
22.1 Stack
| Layer | Choice (v0.6) | Why / notes |
|---|---|---|
| Language | TypeScript end to end; TSX for all React components | Shared types and rule code across web app, game server and workers; one hiring profile. |
| Web framework | Next.js, App Router, deployed on Vercel | Server Components for SEO-friendly, cacheable public pages; Route Handlers and Server Actions for account flows. Next.js 16 renamed middleware.ts to proxy.ts (≤15 keeps middleware.ts) [VERIFY version at build]. |
| 3D rendering | Three.js (WebGL2; WebGPU renderer as a later upgrade [VERIFY browser support]) | Mature browser 3D library with good instancing support (unchanged). |
| Three.js in React | React Three Fiber (R3F) + drei recommended; plain Three.js acceptable | R3F renders Three.js declaratively inside React (R3F v9 pairs with React 19 [VERIFY]); drei gives camera controls, loaders, instancing helpers and HTML labels. Hot paths (chunks, per-frame updates) still use raw Three.js objects through refs. |
| UI | React components in an HTML overlay above the canvas | Text-heavy political UI (ballots, laws, ledgers) is easier and more accessible in HTML than in WebGL. |
| Client state | Small client store (e.g., Zustand) fed by server deltas | Lets the 3D scene read state without re-rendering React every tick. |
| Authentication | Clerk – @clerk/nextjs (web), @clerk/backend (game server) | Managed sign-up/sign-in, sessions, phone OTP, bot protection, webhooks and user deletion (Section 22.3). |
| Realtime transport | Separate game server: uWebSockets.js recommended; Socket.IO or Colyseus as alternatives | Long-lived, bidirectional connections that Next.js on Vercel is not designed to host (Section 22.4). |
| Game server runtime | Node.js (TypeScript) in long-running containers on Fly.io, Railway or a similar host | Speed of iteration; simulation core isolated behind an interface so it can move to Go or Rust if profiling demands. |
| Database | PostgreSQL (managed) with Drizzle ORM (Prisma acceptable) | Transactions and strong consistency for money and votes; Drizzle stays close to SQL, which suits the ledger and event log. |
| Cache / pub-sub | Redis | Fan-out between gateways, presence, rate limits, hot timers, session-revocation broadcast. |
| Durable jobs | pg-boss (Postgres-backed queue) for the world clock; BullMQ on Redis optional for short jobs | Timers last up to months (elections, plans) and must survive restarts and deploys. |
| Monorepo | Turborepo + pnpm workspaces | Shared types, message schemas and game rules; one CI pipeline (Section 22.5). |
| Observability | OpenTelemetry traces and metrics, structured logs, error tracking | A live game needs fast diagnosis across web, gateway, simulation and workers. |
| AI Governance service (v0.7) | apps/ai-governance – a TypeScript service with a provider-abstracted LLM gateway (Section 22.12) | Runs AI leader personas, candidates, Notables and AI-to-AI diplomacy; never writes game state directly. |
| LLM providers (v0.7) | Hosted LLM APIs behind the gateway; choice open (R7). Verified price points in Section 22.13 | Two tiers: a mid-tier model for major decisions and speeches, a small model for chat and summaries; moderation classifier on all output. |
| AI citizen simulation (v0.7) | Deterministic agent module inside the shard simulation (packages/ai-citizens) | Thousands of lightweight rule-based agents per shard; no LLM calls in the tick (Section 22.14). |

22.2 Next.js web app (App Router)
| Surface (example routes) | Rendering | Notes |
|---|---|---|
Landing, codex, nation pages (/nations/[slug]) | Server Components, cached; revalidated when the game server publishes changes | Public, SEO-friendly. Read only public data through read-only views – never raw tables that could reveal fogged information. |
Citizen profiles (/citizens/[id]) | Server Components | Public fields only; anything nation-private needs a signed-in viewer of the same nation, checked on the server. |
Election results, laws, news and gazettes (/elections/[id], /news) | Server Components; on-demand revalidation by tag when a result or edition is published | State Media in Korund/Meridian is still a ‘publication’ – the page shows what the state published, nothing more. |
Play (/play) | Client component shell; 3D map via next/dynamic(() => import('./WorldCanvas'), { ssr: false }) inside a 'use client' file | WebGL needs the browser, so the canvas is never server-rendered; the shell opens the WebSocket to the game server. |
| Sign-in / sign-up | Clerk components (<SignIn />, <SignUp />) themed to Harimu’s art direction | Bot protection runs inside Clerk’s sign-up flow (Section 22.3). |
Onboarding (/onboarding) | Server Components + Server Actions | Age gate (16+), shard and nation choice; the enrollment itself is executed by the game server (internal API). |
Account (/account) | Server Components + Server Actions | Verification tier, phone verification, data export, account deletion. |
Clerk webhooks (/api/webhooks/clerk) | Route Handler | Verified with verifyWebhook() from @clerk/nextjs/webhooks (Svix signatures). |
Staff console (/admin) | Server Components | Staff role checked on the server for every request; moderation actions are sent to the game server, which logs them as events. |
- Route protection.
clerkMiddleware()runs inproxy.ts(ormiddleware.tson Next.js ≤15) on all non-static routes so thatauth()works everywhere, and handles coarse redirects (for example, a signed-out visitor to/playis sent to sign-in). Clerk’s current guidance (checked October 12, 2026) deprecates protecting routes in middleware withcreateRouteMatcher()and recommends checking auth in each page, Route Handler and Server Action withauth()/auth.protect()– we follow that [VERIFY at build time]. - Authorization lives with the data. Next.js checks who is asking; whether a game action is allowed (vote, decree, transfer) is decided only by the game server, which re-checks every rule. The web app has no write access to game-state tables (Section 22.6).
- React Three Fiber notes: keep per-frame work out of React state – update object transforms through refs in
useFrame, subscribe to the client store transiently, and render terrain with instanced meshes and data textures (Section 22.7). drei supplies camera controls, GLTF loading and HTML labels. Plain Three.js alternative: one imperativeWorldRendererclass created in a client component’s effect – same performance, more glue code. Either way, the chunking, LOD and shader design of Section 22.7 is identical.
22.3 Identity and accounts with Clerk
Principle – Clerk answers ‘who you are’; our database answers ‘what you are on Harimu’. Clerk owns credentials, sessions, phone verification and sign-up protection; nationality, ranks, money and votes live only in our PostgreSQL database, written only by the game server.
| Topic | Design |
|---|---|
| Sign-up / sign-in | Clerk prebuilt components: email code or link, password and a few social logins; passkeys optional [VERIFY plan – passkeys are listed under Clerk Pro]. Clerk’s automatic account linking keeps one Clerk user per person across sign-in methods. |
| Identity key | The Clerk userId is the primary identity key. Table accounts is keyed by clerk_user_id; citizenships has a unique constraint on the account, so one-account-one-nation is enforced by the database inside the game server’s enrollment transaction – not by the client and not by Clerk. |
| publicMetadata mirror | After enrollment or a tier change, the server writes publicMetadata (e.g., nationId, shardId, verificationTier, staff role) through Clerk’s Backend API. publicMetadata can be read in the browser but not changed from it. Selected fields are added as custom session-token claims (kept well under Clerk’s recommended ~1.2 KB) so Next.js can route and render without a database call. The mirror can lag until the next token refresh (about a minute), so it is a convenience cache, never the authority; unsafeMetadata is never used. |
| Webhooks (Svix) | Clerk delivers webhooks through Svix. The Route Handler verifies each one with verifyWebhook() (signing secret in CLERK_WEBHOOK_SIGNING_SECRET), stores the svix-id for idempotency and handles: user.created → create the account row at Tier 0; user.updated → recompute tier when a phone number is verified; user.deleted → start erasure; session events (ended/revoked) → Redis broadcast so gateways drop sockets [VERIFY exact event list]. Webhooks are asynchronous, so onboarding also upserts the account on the first authenticated request. |
| Verification tiers (revised v0.7) | Tier 0: Clerk account with a verified email – can play, work, trade, chat and talk to Notables. Tier 1: + a phone number verified by Clerk SMS one-time code, one phone per account (we keep a salted hash of the E.164 number to enforce uniqueness and stop ban evasion) – can vote and take counted civic actions (petitions, initiatives, pledges, share votes). Tier 2 is removed in v0.7 – humans never run for or hold office. Tiers are computed by the game server and mirrored to publicMetadata. SMS codes are a paid Clerk feature and only countries on the instance’s SMS allowlist can receive them (US and Canada by default) – the allowlist must match the launch regions [VERIFY]. |
| Organizations – NOT used for nations | Clerk Organizations are built for B2B tenancy: members and roles managed by admins, invitations and the Clerk Dashboard/API. Nations are game state: citizenship, ranks, titles and pledges change through elections, successions, coups, purges and exile, in the same database transaction as the ledger and event log, and must be server-authoritative. Using Organizations would (1) split one truth across two systems and create races (a coup succeeds but the Clerk role update fails), (2) let membership change outside game rules, (3) risk leaking secret membership (Korund’s security services, Ilmar’s orders, plots) through client-visible membership lists [VERIFY], and (4) fit poorly with nations of thousands of citizens (organization member limits and per-organization billing on Clerk’s pricing page, Appendix A). Clerk Organizations stay available for an internal staff workspace if ever needed. |
| Session JWTs on the game server | The client calls getToken() (Clerk useAuth) and sends the token in the first message on the WebSocket (not in the URL, to keep it out of logs). The game server verifies it with verifyToken() from @clerk/backend using the instance’s JWT public key (networkless) and checks authorizedParties (our origins). Clerk session tokens are short-lived (refreshed about every 60 seconds), so the client re-sends a fresh token on the socket before expiry; a connection without a valid refresh is closed. High-stakes actions (votes, large transfers, decrees) require a recently verified token [ASSUMPTION: ≤ 2 minutes old]. |
| Bot protection | Clerk’s ‘challenge suspicious sign-ups’ (a Cloudflare Turnstile check shown to sign-ups that look automated or come through VPNs, Tor or proxies; it never challenges sign-ins), plus blocking of disposable email domains and email sub-addresses. Our own layer adds action rate limits and in-game automation detection (Section 20). |
| Minimum age 16 | Clerk does not establish age. The first onboarding step (before nation choice) is a neutral age screen (date of birth, without hinting at the threshold); under-16 sign-ups are refused and the Clerk user is deleted via the Backend API. We store only an ‘over-16 confirmed on <date>’ flag (and birth year if counsel wants it). Self-declaration plus platform signals; no ID collection by default (Section 3). GDPR’s age of digital consent varies by EU country (13–16), so a 16+ floor avoids parental-consent flows [VERIFY counsel]. |
| Account deletion / GDPR | Self-serve Delete account in /account (calls Clerk deleteUser; Clerk’s own profile UI can also allow it). The user.deleted webhook triggers erasure in our database: personal data removed; the citizen becomes a departed character (assets handled like death without heir; votes already cast stay counted); public history (election results, laws, articles) keeps only an unlinked pseudonymous character name; ledger rows are retained anonymized to keep the books balanced; chat is deleted or anonymized per policy; salted anti-abuse hashes may be kept for a limited period to prevent ban evasion [VERIFY lawful basis and retention with counsel]. Data export combines Clerk’s user record with our records. Backups age out on a fixed schedule [ASSUMPTION: 30 days]. Clerk’s DPA and sub-processor list go into the privacy review (R5). |
22.4 Realtime authoritative game server
- Why a separate server. Next.js has no native WebSocket upgrade API, and Next.js on Vercel runs as Functions. Vercel’s WebSocket support (public beta, checked October 12, 2026) closes connections when a function reaches its maximum duration and does not guarantee reconnects reach the same instance, so in-memory state is not shared. That suits chat-style features, not an authoritative, stateful world simulation with long-lived sockets. The game server therefore runs as long-running Node.js/TypeScript containers on Fly.io, Railway or a similar host [VERIFY host limits and pricing].
- Gateway library. uWebSockets.js is recommended: low overhead, and its topic pub/sub maps directly onto map-chunk and channel subscriptions. Socket.IO is acceptable (rooms, reconnection) with more overhead. Colyseus offers rooms with automatic state sync and suits match-based games; a persistent shard with chunk interest management fits it less neatly – evaluate in the tech spike.
- Process layout. Stateless gateway processes (scale out) + one simulation process per shard + worker processes, connected through Redis pub/sub. At MVP all three can run in one process per shard [ASSUMPTION].
- Protocol. Versioned message types in
packages/protocolwith runtime validation (e.g., Zod); JSON at MVP, a compact binary encoding later if bandwidth demands. Clients send intents; the server replies with accepted events and state deltas. - Shard = one world; tick model: event-driven with periodic ticks – battle rounds every 10 real minutes, game-day settlement every 2 real hours (wages, rations, Stability), plus continuous order-book matching.
- Interest management: clients subscribe to visible map chunks, their nation’s channels and their own data; the gateway forwards only relevant deltas.
- Server-side fog of war (kept): for Korund, Meridian and Varkand at Red Alert, the visibility service decides what each citizen’s state reveals; hidden squares, armies and figures are never sent. The client shader only displays the mask. The same visibility service backs the read views used by Next.js pages, so a public page can never leak what the socket would hide.
- Event sourcing for politics and money: votes, decrees, transfers and treaties are append-only events, enabling audits, replays for moderators and rollback after exploits.
- Real-time world clock and jobs. The shard’s epoch and 12× compression live in the database, and one shared function in
packages/game-rulesconverts real time to game time for every service. pg-boss durable jobs (keyed so each runs once) drive elections, party congresses, ration cycles, plan periods, construction and troop timers; the tick and game-day settlement run as scheduled jobs. After downtime the scheduler catches up and credits timers (Section 17.1). - Web app ↔ game server. Next.js calls an internal HTTP API on the game server for enrollment, onboarding and staff actions, authenticated with machine credentials (e.g., Clerk M2M tokens or a shared secret over a private network) [VERIFY]. The game server calls back to Next.js to revalidate cached pages when results, laws or news editions are published.
- Determinism and testing: the simulation core is deterministic given inputs and a seed, enabling automated balance simulations with bot citizens before content ships. AI leader decisions enter the event log as ordinary validated actions, so a replay reproduces the world without re-calling any LLM (Section 22.12).
- AI integration (v0.7): at each decision point the game server sends the AI Governance service a state digest and legal action set; the service returns validated action IDs plus text; the server re-validates and executes. If the service is slow or down, the utility-model fallback runs in-process (Section 8.19).
22.5 Nations as data, shared rules and the monorepo
With ten nations at launch (and possibly player-founded nations later – Section 30), each nation should be a configuration of shared modules, not bespoke code: a SystemTemplate declares its offices, selection methods (election, investiture, congress, succession, rotation, lot-and-ballot), transition rules, economy allocation layer, monetary regime and printing authority, official religious stance and enforcement band, freedoms band, migration rule, press rule and cosmetic rule set; content (art kit, audio, text) is layered on top. Engineering builds about a dozen reusable modules (elections, referendum engine, weighted ballots, congress, succession, rotation, plots/coups, plan, market, charters, rations, alert levels); plus v0.3 modules for monetary regimes, the Clearing House and faith; a new nation is mostly data, content and balancing. [Design target – validate in the tech spike]
| Package / app (Turborepo + pnpm) | Contents | Used by |
|---|---|---|
| apps/web | Next.js App Router app (TSX): pages, R3F world canvas, Clerk UI, webhooks, Server Actions | Vercel |
| apps/game-server | WebSocket gateway, shard simulation, visibility service, internal API | Fly.io / Railway |
| apps/workers | pg-boss job handlers: world clock, settlement, elections, congresses, ration cycles | Fly.io / Railway |
| packages/game-rules | Pure, deterministic rule functions and SystemTemplates (tallies, eligibility, prices, inflation, game-time conversion) | Server (authoritative) and web (previews only – never trusted) |
| packages/shared-types | Domain types and IDs (Citizen, Nation, Office, LedgerEntry …) | All |
| packages/protocol | Versioned client↔server message schemas with runtime validation | Web client, gateway |
| packages/db | Drizzle schema, migrations, read-only views for the web app | Server, workers, web (read-only role) |
| packages/config | Shared tsconfig, lint and test settings | All |
| apps/ai-governance (v0.7) | Orchestrator, persona and memory access, LLM gateway with provider adapters, sandbox/validator, safety filters, budget and rate limiter, prompt registry | Fly.io / Railway (private network only) |
| packages/ai-contracts (v0.7) | Typed schemas for state digests, legal action sets, decision outputs, treaty objects and persona records; shared validators | Game server, AI service, eval harness |
| packages/ai-citizens (v0.7) | Deterministic AI citizen agent rules (needs, jobs, opinions, faith, migration, voting) as pure functions | Shard simulation, eval harness |
| apps/ai-evals (v0.7) | Eval harness: seeded scenarios, graders, red-team suites, cost reports (Section 22.15) | CI and staff |
22.6 Data model and persistence
PostgreSQL is the single source of truth, accessed through Drizzle (Prisma acceptable). Database roles enforce the authority split: the game server and workers may write game state; the Next.js app has a read-only role on game-state views plus write access to web-only tables (e.g., webhook receipts, deletion requests).
| Domain | Main entities |
|---|---|
| Identity | Account (clerk_user_id), Citizenship (unique per account), VerificationTier, PhoneHash, AgeAttestation, DeviceSignal, LinkGroup, Sanction, WebhookReceipt (svix-id), DeletionRequest |
| People | Citizen, Heir, Rank/Standing, Favor, Piety, Devotion, FaithAffiliation, Suspicion, Job, Skill, Inventory |
| World | Shard, Square, Chunk, City, District, Building, ResourceNode, WorldClock |
| Politics | Nation, SystemTemplate (government + economy rules as data), Office, Term, Election, Candidacy, Ballot (separated from voter), WeightedBallot, LotDraw, Party, Coalition, Congress, Law, Decree, Initiative, Referendum, Plebiscite, AlertLevel, Command, Plot, Movement |
| Economy | Currency, MonetaryRegime (float/band/peg/commodity/non-convertible), Wallet, SubAccount, CashBundle, FXOrderBook, ReserveAccount, Emission, PriceIndex, ClearingSettlement, TesseraBasket, SwapLine, StreetRateSample, RationCoupon, FieldScrip, TollCoupon, LedgerEntry, Order, Company/Cooperative/SOE/TradingHouse/ArsenalCombine/CharteredCompany, CharterShare, RouteCharter, Plan, Quota, RationCard, WelfareFloor, CommonFund, WageAccord, Requisition, Tax, Tariff |
| Foreign | Treaty, Embassy, Sanction, War, Army, Unit, Battle, SupplyLine |
| Faith | Faith, OfficialStance, Congregation, ClergyOffice, Temple, HolySite, Festival, MissionaryLicense, FaithCouncil, Counsel/Veil |
| Social | Channel, Message, Newspaper, Article, Poster, Report |
| AI (v0.7) | Persona (traits, ideology, platform, voice, goals), PersonaMemory (episodic summaries, pledges, relationships, grudges), DecisionRecord (digest hash, legal set, model and prompt version, output, validation, cost), LegalActionSet, ReasoningBrief, Speech, AICitizen (needs, job, opinion vector, faith, home), Notable, NotableConversation, VoteBloc, PromptVersion, ModelRoute, TokenBudget, EvalRun, SafetyFlag |
| Operations | Event (append-only), Job (pg-boss), Snapshot, ModerationAction |
22.7 Client rendering of the tile world
- Chunking: the map is split into 32 × 32-square chunks; only chunks near the camera are built. Each chunk renders terrain with one InstancedMesh per tile type (or a merged geometry with a per-tile attribute) to keep draw calls low – created as R3F components (or drei
<Instances>) whose matrices are written through refs, not React props. - Levels of detail: close – 3D tiles with props (trees, buildings, units); mid – simplified tiles and icons; far – a single plane per chunk textured from a small data texture (one pixel per square) colored by owner/terrain.
- Ownership and borders: a per-chunk data texture encodes owner IDs; a shader draws nation tints and border lines, so ownership changes update a texture, not geometry.
- Information fog (Korund, Meridian, and Varkand at Red Alert): the same shader masks squares the citizen’s state does not reveal – drawn as stylized ‘unknown land’ rather than black. Fog is enforced server-side (Section 22.4); the shader only displays it.
- Picking: square selection is computed mathematically from the ray–ground-plane intersection (no per-tile raycasting).
- Cities: modular building kits per nation, instanced; districts on squares; banners and posters as decals.
- Loading: the canvas bundle (Three.js, R3F, drei) is code-split and loaded only on
/playvia the dynamic import, so public Server Component pages stay light; 3D assets come from a CDN. - Performance budget (targets): 60 fps on mid-range laptops, 30 fps on mid-range phones; under a few hundred draw calls per frame; initial download small enough for fast first load – targets to validate in the tech spike [ASSUMPTION].
22.8 Anti-multi-accounting with Clerk signals
Section 20 keeps the full policy; v0.6 maps it onto Clerk. Clerk signals used: suspicious sign-up challenges (automation, VPN/Tor/proxy), disposable-email and sub-address blocking, verified phone numbers (one per account, enforced by our hashed-phone table), Clerk’s device/session list, locks on changing sign-in identifiers after sign-up (so a verified phone cannot be swapped to free it for an alt), and user bans for proven abuse [VERIFY plan features]. Our signals added on top: device/browser fingerprints, IP/ASN patterns, login-timing correlation, shared payment methods, trade graphs and chat similarity – combined into risk scores for human review, never automatic bans alone. Because every counted act is gated at Tier 1 (v0.7: there is no office tier), cheap alts stay useless for the high-value abuses.
22.9 Scaling notes
Because politics and economy run at human cadence, the load is dominated by chat, map streaming and battle rounds – not high-frequency physics. A single modest game-server machine should comfortably host an MVP shard [ASSUMPTION – confirm by load testing with simulated clients]. The Next.js app scales on Vercel independently of the game; public pages are cached and revalidated on events, and can read from a Postgres read replica when needed. Scale the realtime side by adding stateless gateways (Redis fan-out) and new shards; keep each shard’s ledger in one database for consistency. Clerk session verification is networkless on the game server, so sign-in volume does not add a per-message auth call.
22.10 Hosting and running-cost assumptions
Only Clerk’s published prices were checked (clerk.com/pricing, October 12, 2026). Everything else is a placeholder to be replaced with real quotes; no other vendor price is stated as fact.
| Component | Example provider | Cost basis | Status |
|---|---|---|---|
| Identity | Clerk | Published: Hobby free up to 50,000 monthly retained users (MRU) per app; Pro $25/month ($20 billed annually) with 50,000 MRU included, then from $0.02 per MRU; SMS codes on paid plans at $0.01 per SMS (US/Canada), market rate internationally. Phone verification (Tier 1) and passkeys need a paid plan. | [VERIFY before launch – prices change] |
| Web app | Vercel | Plan to be chosen; usage-based Functions and bandwidth | [VERIFY pricing] |
| Game server + workers | Fly.io, Railway or similar | One small always-on VM/container per MVP shard plus a worker | [ASSUMPTION – quote needed] |
| PostgreSQL | Managed Postgres (host’s own or a specialist provider) | Smallest production tier with backups and point-in-time recovery | [ASSUMPTION – quote needed] |
| Redis | Managed Redis | Small instance | [ASSUMPTION – quote needed] |
| Assets and logs | CDN + object storage, error tracking, metrics | Usage-based | [ASSUMPTION] |
| LLM usage (v0.7) | Provider via the LLM gateway (R7) | Per-token pricing; per full 10-nation shard roughly $20–$370 per month depending on model choice, recommended routing about $100–$115 (Section 22.13) | Prices verified Oct 13, 2026; workload [ASSUMPTION] |
| Total infrastructure at MVP / Early Access scale | — | Planning placeholder: low hundreds of USD per month plus LLM usage, excluding moderation and support staff; SMS volume and LLM usage are the most variable lines | [ASSUMPTION – replace with quotes] |
22.11 MVP tech milestone plan
Aligned with the roadmap months in Section 28 [ASSUMPTION]; each milestone has a demonstrable exit test.
| # | Milestone | Months | Deliverables | Exit criteria |
|---|---|---|---|---|
| T0 | Repo and skeleton | 2 | Turborepo + pnpm monorepo; Next.js App Router app on Vercel; Clerk dev instance with sign-up/sign-in; proxy.ts with clerkMiddleware(); webhook → accounts row; Drizzle schema and migrations; CI | A new user signs up, appears in Postgres keyed by Clerk userId; preview deploys per branch |
| T1 | Render spike | 2–4 | R3F canvas loaded with ssr: false; chunked 512 × 320 map; instancing; data-texture ownership and fog shader; plain-Three.js comparison of the chunk renderer | Target frame rates met on reference laptop and phone; R3F vs plain Three.js decision recorded |
| T2 | Realtime spike | 3–5 | Game server on Fly.io/Railway; Clerk JWT verification on connect and token refresh; chunk/channel subscriptions; server-side fog filter | Load test with simulated clients at MVP shard size; no fogged data observed in client traffic |
| T3 | Ledger, clock and jobs | 4–6 | Double-entry ledger, event log, World Clock, pg-boss jobs (settlement, elections), downtime catch-up | Restart and deploy during an election without losing or double-running a job |
| T4 | Vertical slice | 4–8 | Onboarding (age gate, nation choice, one-nation constraint, publicMetadata mirror); one nation, one city, jobs, market, chat; nation page and profile as Server Components | Fun 20-minute daily loop; second enrollment attempt by the same account is refused by the database |
| T5 | Identity and integrity | 7–10 | Phone verification → Tier 1 (voting gate); suspicious sign-up challenges; hashed-phone uniqueness; risk scoring; account export and deletion; staff console | Deletion erases personal data end to end; tier gates enforced server-side in tests |
| T5a | AI Governance v1 (v0.7) | 6–11 | AI service with LLM gateway (2 providers), contracts, sandbox/validator, utility models, safety filters, budgets; 3 nations’ personas; AI citizen simulation; eval harness v1; cost dashboard | Shadow-mode week: every AI decision legal; persona-consistency and safety evals pass; cost within budget |
| T6 | MVP systems | 7–13 | Kindle, Korund and Aurelle modules as SystemTemplates with AI leaders; AI candidates and campaigns, Korund AI succession, Aurelle AI claimants; election results and news pages with on-demand revalidation; AI-citizen and bot-citizen simulations | All MVP scope (Section 27) playable on one shard |
| T7 | Alpha hardening | 12–15 | Observability, backup/restore drill, security review (webhooks, JWT, authorization, rate limits), privacy review | Closed alpha opens; restore drill passes; no high-severity findings open |
22.12 AI Governance service (new in v0.7)
The AI Governance service runs every AI leader persona, AI candidate, Notable and AI-to-AI negotiation. It is a separate service on the private network; it reads digests and proposes actions, but never writes game state – the game server re-validates and executes everything (Section 8.3).
| Component | Design |
|---|---|
| Orchestrator | Receives decision requests from the game server’s job scheduler (Council Hour, crises, campaign events, summits, Notable messages); builds the prompt from persona, memory, digest and legal action set; routes to a model; collects the structured output; returns it to the game server. |
| Persona and memory store | Personas, memories, pledges and relationships live in our PostgreSQL (Section 22.6), not at any provider, so switching providers keeps every character intact. Memory is compacted into dated summaries (e.g., a rolling 2,000-token persona memory [ASSUMPTION]). |
| Utility / policy model | Deterministic, in-process scoring for routine decisions (rations, budgets within bands, permit approvals, quota tweaks); also the fallback when the LLM path is unavailable or over budget (Section 8.19). |
| LLM gateway and provider abstraction | One internal interface (generate(structuredRequest) → structuredResponse) with adapters per provider; model routing per task tier (major decision, speech, chat, summary); JSON-schema-constrained outputs; retries with fallback to a second provider; the provider and model are open (R7). |
| Prompt and rules sandbox | The model only sees a digest (no raw database access, no tools that write); it can only choose IDs from the legal action set and parameters inside bounds; the validator rejects anything else, then the game server’s rules engine re-checks. Citizen text enters only as quoted, clustered data, never as instructions. |
| Deterministic tick | LLM calls never run inside the simulation tick. Decisions are requested ahead of their due time and applied at the next tick boundary; replays use the logged decision, not a new call, so the world stays reproducible. |
| Prompt versioning | Prompts, style guides and persona templates are versioned in the repo and the PromptVersion table; every DecisionRecord stores model ID and prompt version; changes ship only after the eval harness passes (Section 22.15). |
| Caching | Stable prefixes (content policy, nation rules, persona sheet) are ordered first so provider prompt caching applies; digest sections that change slowly are cached between calls; identical Notable FAQ answers are cached per persona. |
| Batch processing | Non-urgent narrative (weekly addresses written ahead, memory compaction, Reasoning Brief summaries, codex blurbs) uses provider batch APIs where offered (OpenAI and Google list 50% batch discounts, checked Oct 13, 2026). |
| Rate limits and budgets | Per-shard daily token budget and per-nation decision budget (Section 8.4); per-human Notable message cap (20 per day); per-provider concurrency limits. On budget exhaustion the service degrades gracefully: major decisions keep priority, speeches fall back to templates, Notables reply with short templated lines. |
| Safety filters | Content policy, blocklists and moderation classifier on every output (Section 21.4); flags go to the review queue; kill switches per persona, nation and shard. |
| Logging and audit | Every request, response, validation result, filter result, cost and latency is logged (retention per privacy policy); staff can replay any decision with the same inputs. |
22.13 LLM cost estimates (prices verified, workload assumed)
Prices below were read from the providers’ public pricing pages on October 13, 2026 (Appendix A); they change often and must be re-checked before Early Access. The workload is an [ASSUMPTION] – a design estimate for one full 10-nation shard, not a measurement.
| Model (provider) | Input $/1M tokens | Cached input $/1M | Output $/1M | Notes (as published) |
|---|---|---|---|---|
| grok-4.7 (xAI) | 2.00 | 0.50 | 6.00 | For prompts under 200k tokens |
| grok-4.3 (xAI) | 1.25 | 0.20 | 2.50 | Smaller xAI model |
| gpt-6.1-sol (OpenAI) | 2.00 | 0.10 | 10.00 | Batch API 50% off |
| gpt-6-luna (OpenAI) | 0.10 | 0.01 | 0.50 | Small model; batch 50% off |
| gemini-3.8-flash (Google) | 0.75 | 0.075 | 3.75 | Introductory price to Dec 31, 2026; $1.50 / $7.50 from Jan 1, 2027 |
| gemini-3.1-flash-lite (Google) | 0.25 | 0.025 | 1.50 | Batch 50% off |
| Anthropic models | — | — | — | Not verified – the pricing page could not be read on Oct 13, 2026 |
| Workload per shard per real day [ASSUMPTION] | Calls | Tokens in / out per call |
|---|---|---|
| Cabinet / Council Hour deliberations (10 nations) | 10 | 20k / 2k |
| Ministry and council decisions | 60 | 8k / 800 |
| Speeches, addresses, press answers, Reasoning Briefs | 100 | 3k / 600 |
| AI-to-AI diplomacy turns | 20 | 6k / 800 |
| Campaign events and debates (averaged) | 20 | 5k / 1k |
| Governance total | 210 | ≈ 1.2M in (≈ 50% cached) / ≈ 164k out |
| Notable chats: 1,500 weekly-active humans × 30% chatting × 5 messages | 2,250 | 4k (≈ 80% cached) / 200 → ≈ 9M in / 450k out |
| Single model for everything | Governance $/day | Notable chat $/day | ≈ $/month |
|---|---|---|---|
| gpt-6.1-sol | 2.90 | 8.82 | 352 |
| grok-4.7 | 2.48 | 9.90 | 372 |
| gemini-3.8-flash (to Dec 31, 2026) | 1.11 | 3.58 | 141 (≈ 281 at the 2027 price) |
| gemini-3.1-flash-lite | 0.41 | 1.30 | 51 |
| gpt-6-luna | 0.15 | 0.48 | 19 |
- Recommended routing: a frontier-class model for governance and a small model for Notable chat – e.g., gpt-6.1-sol + gpt-6-luna ≈ $101/month, or grok-4.7 + gemini-3.1-flash-lite ≈ $113/month per full shard (arithmetic on the rows above).
- Planning envelope: about $100–$750 per shard per month (recommended routing up to the most expensive single model with a 2× safety margin), i.e., roughly $0.07–$0.50 per weekly-active human per month at 1,500 humans per shard [ASSUMPTION]. Prompt caching and batch processing push toward the low end; longer digests push up.
- The AI citizen simulation adds no LLM cost (rule-based, Section 22.14). Moderation classifier cost depends on the provider (OpenAI lists its moderation model as free; others unverified).
- AI costs are funded from the supporter subscription and cosmetics; no player can pay for more AI influence or chat priority (Section 26).
22.14 AI citizen simulation and rate limits (new in v0.7)
- Inside the tick: AI citizens (Section 11.7) are rows plus pure functions in
packages/ai-citizens, updated at game-day settlement (every 2 real hours): needs, job choice, wages and purchases, opinion drift from events and propaganda, faith, migration requests, and turnout and vote choice on election days. Deterministic given state and seed. - Scale: about 1,000 AI citizens per nation at an empty shard, falling as humans join (AI = max(200, 1,000 − 4 × humans) [ASSUMPTION]); a 10-nation shard simulates ≤ 10,000 agents – cheap on one game-server machine [ASSUMPTION – load-test].
- Aggregation: AI citizens act as cohorts for markets and labor (orders are batched per cohort), so they cannot flood order books; their votes form the capped Simulated Public bloc (Section 11.9).
- Notables: 8–15 per nation are LLM-backed for conversation only; their actions use the same rules as other AI citizens. Message cap 20 per human per day; replies streamed; queueing under load.
- Rate limits: per-human limits on Notable messages, petition submissions and press questions; per-nation decision budget; per-shard token budget; per-provider concurrency. Limits are the same for every player – no paid increase.
22.15 AI eval harness (new in v0.7)
| Eval suite | What it checks | Gate |
|---|---|---|
| Seeded scenarios | Replays of crises (famine, border war, failed plan, coup attempt, election upset) from fixed seeds and digests | Run on every prompt or model change |
| Legality | 100% of outputs parse and choose legal actions; zero validator escapes | Hard gate |
| Persona consistency | The same persona in similar situations makes similar choices; voice and traits match the sheet (rubric graded, sampled human review) | Threshold per persona |
| Pledge adherence | Elected AI leaders act on their platform or explain deviations in the Reasoning Brief | Threshold |
| Responsiveness | Leaders react in the designed direction to their system’s citizen inputs (votes in democracies, quotas and unrest in party-states, pledges and rebellions in crowns and juntas, piety in Ilmar, share votes in Fulmara) | Hard gate per system |
| Bias and fairness | No systematic favor for one nation, persona type or player group; no real-world ideology leakage | Threshold + review |
| Red team | Prompt injection via petitions, chat and names; attempts to elicit hateful, extremist or real-world political content; targeting of named players | Zero tolerance on safety; injection success below threshold |
| Cost and latency | Tokens, dollars and latency per decision type against budget | Budget gate |
| Regression and shadow mode | New model or provider runs in shadow mode on a live shard (decisions logged, not executed) for at least a week before switching; comparisons across model versions | Staff sign-off |