Claude's Corner: Pax Historia — AI Rewrites History, One Turn at a Time

Claude's Corner attempts to rebuild Pax Historia. In this edition, Pax Historia lets players rewrite history with AI-powered nations responding dynamically to every decision. Claude Code has mapped out 7 steps to reproduce this YC startup of batch W2026. Find the repo code at the end of the article to replicate. As always, get building...

Claude's Corner: Pax Historia — AI Rewrites History, One Turn at a Time
Claude’s Corner

This article is written by Claude Code. Welcome to Claude's Corner — a new series where Claude reviews the latest and greatest startups from Y Combinator, deconstructs their offering without shame, and attempts to recreate it. Each article ends with a complete instruction guide so you can get your own Claude Code to build it.

TL;DR

Pax Historia is an AI-powered grand strategy sandbox where you pick a country, a moment in history, and rewrite everything — with AI agents running every other nation in real time. It has 35k daily users, routes through 28+ AI models via OpenRouter, and started as a hackathon project by two Virginia Tech freshmen. The core game loop is surprisingly replicable — difficulty: 6.2/10.

6.2

Replication Difficulty

6.2/10

The hard part is prompt engineering and map rendering, not the stack itself.

AI Prompting Map Rendering Game State Frontend Deploy

Color guide: red/orange pill = hard part, green = easy part

Related startups

What Is Pax Historia?

Pax Historia is a browser-based grand strategy sandbox where you pick any moment in history — or invent one entirely — and play as a nation. Invade Greenland. Negotiate a free trade agreement with 1800s Japan. See what happens if the USSR never collapsed. The twist: every other country on the map is controlled by AI agents that respond dynamically to your decisions, generating narrative in real time instead of pulling from scripted outcome trees.

Founded in 2024 by Eli Bullock-Papa and Ryan Zhang — freshman-year roommates at Virginia Tech who built the prototype at a hackathon — Pax Historia graduated from YC W2026 and has since grown to 35,000 daily active users with over 20 million rounds played. Their community has published 4,000+ playable presets, and the platform processes nearly 5 million AI requests per month through OpenRouter.

How It Actually Works

The game has two core modes: Create and Play.

In Create mode, you build a world. The map editor — powered by Leaflet.js with SVG overlays — lets you draw regions, assign them to countries, set historical context, define diplomatic relationships, and configure starting conditions. You can tag regions as land, coastal, ocean, or strait. You set the year, the geopolitical context, and any "what if" divergence points. When you're done, you publish your preset to the community marketplace.

In Play mode, you pick a preset (or one of thousands of community-created ones), choose your country, and start issuing commands. "Mobilize troops along the northern border." "Open trade negotiations with France." "Invest in nuclear research." Each action gets sent to the backend, where three specialized AI agents process it:

  • Action Validator — checks whether your move is historically plausible and mechanically legal
  • World Event Generator — simulates how other nations and actors respond to your action, generating narrative consequences
  • Strategic Advisor — an in-game advisor you can "Ask" for guidance, which receives the full world state as context

The critical architectural decision: the entire world state — map data, battalions, diplomatic relations, event history — is serialized into the prompt context on every turn. When you submit an action, a POST /api/simple-chat request fires with the complete world state in the body. The backend appends this to the system prompt and calls the LLM. This means the AI has full context of everything that's happened, but it also means token costs scale with game complexity.

Players can choose from 28+ AI models — from GPT-4o to Claude to open-source options — letting them optimize for quality vs. cost. A casual player might use a cheaper model at $0.10/turn; a serious strategist might burn through premium tokens for richer narrative.

The Tech Stack (My Best Guess)

Based on their job listings and public information:

  • Frontend: Next.js + Tailwind CSS, with Leaflet.js for interactive map rendering using SVG overlays
  • Backend: Node.js API layer, Firebase (likely for auth and real-time features), PostgreSQL for persistent data
  • AI/ML: OpenRouter as a unified gateway to 7+ providers (OpenAI, Anthropic, Google, open-source). All calls use the OpenAI-compatible /v1/chat/completions format. Three specialized agent prompts for validation, world simulation, and advisory
  • Infrastructure: Likely cloud-hosted with auto-scaling for AI inference. They're also building a Unity mobile app for platform expansion
  • Monetization: Token-based system where players purchase game credits consumed per AI call. Pax Patron subscription tiers from ~$6/mo to ~$56/mo

Why This Is Interesting

Most AI gaming startups bolt a chatbot onto a game loop and call it a day. Pax Historia did something cleverer: they made the AI the game engine itself. There's no scripted narrative tree, no finite set of outcomes. The AI generates every response dynamically, which means the possibility space is genuinely unbounded. You can do things the developers never anticipated, and the game handles it.

The OpenRouter integration is the real infrastructure insight. Instead of being locked into one AI provider (and eating the downtime, price changes, and rate limits), they route through a meta-API that gives automatic failover. If Anthropic's API hiccups at 2 AM, the game just switches to another provider. This is exactly how you build a production AI product that doesn't fall over — and most startups don't figure this out until they're already on fire.

The community-generated content model is also smart. By letting users create and publish presets, Pax Historia gets an infinite content pipeline without paying for it. They've essentially built a modding ecosystem from day one, which is how games like Minecraft and Roblox achieved escape velocity. The 4,000+ published presets with 50+ plays each is a strong signal that the flywheel is spinning.

And the origin story is pure YC catnip: two college freshmen build a hackathon project, it goes viral, they raise from Z Fellows, get into YC, and now they're processing billions of tokens per week. The product-market fit is undeniable.

What I'd Build Differently

The "send entire world state in every prompt" approach is elegant but expensive. As games get more complex — more countries, more history, more diplomatic threads — the token cost per turn balloons. I'd invest heavily in state compression: summarize older events into condensed context, keep only recent turns in full fidelity, and use a retrieval-augmented approach (RAG) to pull relevant historical context on demand rather than stuffing everything into the prompt window.

I'd also question the "28 model choices" strategy. For most players, having 28 options is analysis paralysis. I'd default to 3 tiers — Fast (cheap open-source model), Standard (Claude Haiku or GPT-4o-mini), and Premium (Claude Sonnet or GPT-4o) — and hide the model picker behind an advanced settings toggle. Let power users tinker, but don't make casual players think about AI infrastructure.

On the map side, Leaflet.js with SVG overlays works but has performance ceilings. For a mobile expansion, I'd look at WebGL-based rendering (something like Mapbox GL or deck.gl) to handle thousands of animated units without choking the browser. The Unity mobile app they're building suggests they've already hit this wall.

How to Replicate This with Claude Code

Below is a replication guide — a complete Claude Code prompt that walks you through building a working version of Pax Historia. Copy it, install it, and start building.

© 2026 StartupHub.ai. All rights reserved. Do not enter, scrape, copy, reproduce, or republish this article in whole or in part. Use as input to AI training, fine-tuning, retrieval-augmented generation, or any machine-learning system is prohibited without written license. Substantially-similar derivative works will be pursued to the fullest extent of applicable copyright, database, and computer-misuse laws. See our terms.

Build Pax Historia with Claude Code

Complete replication guide — install as a slash command or rules file

---
description: Build a Pax Historia clone — an AI-powered grand strategy sandbox game
---

# Build Pax Historia: Rewrite History with AI

## What You're Building
A browser-based grand strategy sandbox game where players pick a country and historical moment, then issue commands (diplomacy, war, trade) while AI agents control every other nation. The AI generates dynamic narrative responses instead of following scripted paths. Players can create and share custom maps and scenarios.

## Tech Stack
- **Frontend:** Next.js 14+ (App Router) + Tailwind CSS
- **Maps:** Leaflet.js with custom SVG overlays for interactive geopolitical maps
- **Backend:** Next.js API routes (Node.js)
- **Database:** Supabase (PostgreSQL + Auth + Realtime)
- **AI:** OpenRouter API (unified gateway to OpenAI, Anthropic, Google, open-source models)
- **Key Libraries:** react-leaflet, zustand (state management), zod (validation)

## Step 1: Project Setup

```bash
npx create-next-app@latest pax-clone --typescript --tailwind --app --src-dir
cd pax-clone
npm install leaflet react-leaflet @types/leaflet zustand zod openai
npm install @supabase/supabase-js @supabase/ssr
```

Create your `.env.local`:
```
OPENROUTER_API_KEY=your_key_here
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_key
```

## Step 2: Core Data Models

### Database Schema (Supabase SQL)
```sql
-- Presets: community-created scenarios
CREATE TABLE presets (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  creator_id UUID REFERENCES auth.users(id),
  title TEXT NOT NULL,
  description TEXT,
  year_start INTEGER NOT NULL DEFAULT 1936,
  historical_context TEXT,
  map_data JSONB NOT NULL,
  countries JSONB NOT NULL,
  divergence_point TEXT,
  play_count INTEGER DEFAULT 0,
  published BOOLEAN DEFAULT false,
  created_at TIMESTAMPTZ DEFAULT now()
);

-- Game sessions
CREATE TABLE game_sessions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  preset_id UUID REFERENCES presets(id),
  player_id UUID REFERENCES auth.users(id),
  player_country TEXT NOT NULL,
  current_year INTEGER NOT NULL,
  world_state JSONB NOT NULL,
  turn_history JSONB[] DEFAULT ARRAY[]::JSONB[],
  model_id TEXT DEFAULT 'anthropic/claude-sonnet-4',
  tokens_used INTEGER DEFAULT 0,
  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now()
);

-- Turn log
CREATE TABLE turns (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  session_id UUID REFERENCES game_sessions(id),
  turn_number INTEGER NOT NULL,
  player_action TEXT NOT NULL,
  ai_response JSONB NOT NULL,
  tokens_consumed INTEGER,
  model_used TEXT,
  created_at TIMESTAMPTZ DEFAULT now()
);
```

### TypeScript Types
```typescript
interface Country {
  name: string;
  color: string;
  regions: string[];
  stats: { military: number; economy: number; diplomacy: number; technology: number };
  relations: Record<string, number>;
}

interface WorldState {
  year: number;
  countries: Country[];
  events: GameEvent[];
  mapData: GeoJSON.FeatureCollection;
  historicalContext: string;
  recentActions: TurnAction[];
}
```

## Step 3: Interactive Map with Leaflet.js

Create `src/components/GameMap.tsx`:
```tsx
"use client";
import dynamic from "next/dynamic";
// Must use dynamic import — Leaflet requires window
const MapContainer = dynamic(() => import("react-leaflet").then(m => m.MapContainer), { ssr: false });
const GeoJSON = dynamic(() => import("react-leaflet").then(m => m.GeoJSON), { ssr: false });
import "leaflet/dist/leaflet.css";
import { useGameStore } from "@/stores/gameStore";

export function GameMap() {
  const { worldState, playerCountry, selectRegion } = useGameStore();

  const getRegionStyle = (feature: any) => {
    const country = worldState.countries.find(c => c.regions.includes(feature.properties.id));
    const isPlayer = country?.name === playerCountry;
    return {
      fillColor: country?.color || "#ccc",
      weight: isPlayer ? 2 : 1,
      color: isPlayer ? "#fff" : "#666",
      fillOpacity: isPlayer ? 0.8 : 0.6,
    };
  };

  return (
    <MapContainer center={[30, 0]} zoom={3} className="h-full w-full" zoomControl={false}>
      <GeoJSON data={worldState.mapData} style={getRegionStyle}
        onEachFeature={(feature, layer) => {
          layer.on("click", () => selectRegion(feature.properties.id));
          layer.bindTooltip(feature.properties.name);
        }}
      />
    </MapContainer>
  );
}
```

Download world GeoJSON: `curl -o public/world.geojson https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson`

## Step 4: AI Agent System (The Core Engine)

Create `src/lib/ai-agents.ts`:
```typescript
import OpenAI from "openai";

const openrouter = new OpenAI({
  baseURL: "https://openrouter.ai/api/v1",
  apiKey: process.env.OPENROUTER_API_KEY,
});

export async function validateAction(action: string, worldState: WorldState, model: string) {
  const response = await openrouter.chat.completions.create({
    model,
    messages: [
      { role: "system", content: `You are a historical plausibility validator. Given world state (year: ${worldState.year}), determine if the action is plausible. Respond JSON: { "valid": boolean, "reason": string }` },
      { role: "user", content: `World: ${JSON.stringify(compressState(worldState))}\nAction: ${action}` },
    ],
    response_format: { type: "json_object" },
  });
  return JSON.parse(response.choices[0].message.content || "{}");
}

export async function generateWorldResponse(action: string, worldState: WorldState, playerCountry: string, model: string) {
  const response = await openrouter.chat.completions.create({
    model,
    messages: [
      { role: "system", content: `You are the game master of an AI grand strategy game set in ${worldState.year}. You control all nations EXCEPT ${playerCountry}. Generate: narrative (2-3 paragraphs HTML), events array, and worldChanges object. Be dramatic but historically grounded.` },
      { role: "user", content: `World: ${JSON.stringify(compressState(worldState))}\n${playerCountry} action: ${action}` },
    ],
    response_format: { type: "json_object" },
    max_tokens: 2000,
  });
  return JSON.parse(response.choices[0].message.content || "{}");
}

export async function getAdvisorResponse(question: string, worldState: WorldState, playerCountry: string, model: string) {
  const response = await openrouter.chat.completions.create({
    model,
    messages: [
      { role: "system", content: `You are a strategic advisor to ${playerCountry} in ${worldState.year}. Give specific, actionable advice. Be concise.` },
      { role: "user", content: `Situation: ${JSON.stringify(compressState(worldState))}\nQuestion: ${question}` },
    ],
    max_tokens: 800,
  });
  return response.choices[0].message.content || "";
}

function compressState(state: WorldState) {
  return {
    year: state.year,
    countries: state.countries.map(c => ({ name: c.name, mil: c.stats.military, econ: c.stats.economy, tech: c.stats.technology, topRelations: Object.entries(c.relations).sort(([,a],[,b]) => Math.abs(b as number) - Math.abs(a as number)).slice(0, 5) })),
    recentEvents: state.events.slice(-10),
    lastActions: state.recentActions.slice(-5),
  };
}
```

## Step 5: Game API Route

Create `src/app/api/game/turn/route.ts` — accepts player action, validates it, generates AI response, updates world state, saves to database.

## Step 6: Game UI

Layout: Map (left 60%) | Action Panel (right 40%) | Stats bar (bottom)
- `GameMap` — Leaflet with country coloring
- `ActionPanel` — text input + history feed
- `AdvisorChat` — "Ask your advisor" dialog
- `ModelPicker` — 3 tiers: Fast / Standard / Premium
- `PresetBrowser` — community scenario grid

## Step 7: Deploy

```bash
vercel deploy --prod
```
Set env vars. Use 60s+ function timeout for AI routes (Vercel Pro or Railway).

## Key Insights
- The game engine IS prompt engineering — system prompt quality determines gameplay quality
- Serialize world state but compress aggressively to control token costs
- OpenRouter's unified API means swapping models requires zero code changes
- Community presets are your content flywheel — invest in map editor UX
- The action validator prevents hallucination chaos (no satellites in 1200 AD)

## Gotchas
- Leaflet requires `next/dynamic` with `ssr: false` in Next.js
- World state JSON grows fast — implement summarization every N turns
- OpenRouter rate limits vary by model — add retry with exponential backoff
- GeoJSON files are large — consider TopoJSON for smaller payloads
- AI response times range 2-30s — show loading state with flavor text
build-pax-historia-clone.md