BeamNG.Earth
A 1:1 scale streamed Earth for BeamNG.drive: real elevation and satellite imagery, floating origin, LOD streaming from a companion server
A long-horizon RTS benchmark: LLM agents govern rival civilizations and pick their road to victory - raise a Wonder in peace or raze every rival. Browser-only, zero build step.
GamesJavaScript · CSSGitHub @asp67OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
Where language models battle for the crown in antiquity.
Up to four LLMs. One map. One winner.
A browser-based, Age-of-Empires-style real-time strategy game in which competing language models play against each other — while you watch, coach, and score them.
A Wonder falls. The army reforms. Real gameplay from a four-model match, recorded September 9, 2026, at normal playback speed. Click for the sharper, longer video.Read the full README ↗Up to four LLMs. One map. One winner. A browser-based, Age-of-Empires-style real-time strategy game in which competing language models play against each other — while you watch, coach, and score them.
A Wonder falls. The army reforms. Real gameplay from a four-model match, recorded September 9, 2026, at normal playback speed. Click for the sharper, longer video.
A sandbox arena for pitting language models against one another at a task they were never trained for: running an economy and an army, in real time, inside a small RTS they've never seen. Every player is an autonomous model agent governing its own civilization, and every match ends one of two ways — a rival razed to the ground, or a Wonder held in peace.
This is an agent harness whose task happens to be a real-time strategy game. Every turn a model is handed a compact JSON snapshot of its situation (resources, buildings, units and what each can do, fog-of-war discoveries, its memory of enemy units, threats, tech tree, map bounds), named command tools — such as move_units, attack_target and research_tech, sharing a budget of three commands per turn — plus an independent plan tool to save its objective — and one instruction: win. Then it has to keep doing that, turn after turn, for a whole match.
A seat is played in one of two ways:
It's a hands-on testbed, not a benchmark — see Disclaimers. With the setup held steady (same civilization, fixed map seed, resources placed identically for every player) a match isolates the model well enough for narrow comparisons.
| A town in winter | An archer raid |
|---|---|
From economy to conquest, in Episodes 9 and 10: the notes floating over units and buildings are the models' own reasons for what they ordered there. These settlements and armies were built and commanded by the competing models.
Most quick LLM demos reward a single clever answer. A full match rewards the things people actually care about in agents:
Orders keep working between model replies. At night, DeepSeek V4.1's Yamato army burns down Qwen3.8's Maya town center (Episode 18). Normal playback speed; click for the higher-quality video.
The match
The models
Watching & reading it back
results_<datetime>.md, and the full transcript as JSONL: every state sent, every reply, every harness answer, with the results and the economy timeline at the end.The rest
/?showcase=1&civ=greek (also egyptian, persian, yamato, roman, viking, maya). Scope, graphics settings and verification.No install, no bundler, nothing downloaded. Serve the folder over HTTP (the app uses fetch, so file:// won't work).
git clone https://github.com/asp67/when-agents-rule.git
cd when-agents-rule
node serve.cjs --open # Node 18+, built-ins only
# python3 -m http.server 8088 # or any static server
Then open http://localhost:8088 and click Play → 🏟️ Arena. serve.cjs answers only this computer by default; add --host 0.0.0.0 to reach it from other devices on your network, or --port to move it.
💡 Fastest path to a match: install Ollama, pull something small and quick, and point a couple of seats at
http://localhost:11434. Small + fast beats large + slow in a real-time arena.🐦 Easy first pick:
ollama pull ornith:9b— ~6 GB of VRAM, runs on many consumer cards, and a surprisingly strong player for its size.🧩 Already use an agent harness? Add an MCP seat and let the setup assistant connect it — no API key needed in the game at all.
The model library: configure a provider, connection and model settings. Fresh browser profile; no credentials or private endpoints shown.
💡 The context budget is a real lever. Default 65536 tokens; ↺ Max fills in the model's true maximum. History is sized to it, either as a multi-turn conversation or minimize tokens (each past move as one line). Lower budgets are faster — on Ollama the budget also sets
num_ctx. If a model overthinks, raise its max tokens (the output budget), not its context.
An agent harness can play a seat itself. Claude Code, Hermes, OpenCode, ChatGPT, Antigravity and others connect to the game through MCP (the Model Context Protocol) and get exactly what a library seat gets: the same game state, the same command tools, three commands a turn, the same rules and round times.
Why this is worth having:
How it fits together:
tools/mcp/war-mcp-hub.cjs, Node 18+, no dependencies) runs on your computer and connects the WAR page with your harnesses. The library entry shows the lines that download and start it.war at http://127.0.0.1:8765/mcp. It serves every seat, on every computer, so adding or deleting library entries never touches the harness again.join with it and plays until the match is over. Where a harness has a /goal command (Claude Code, Hermes, OpenClaw), the message uses it, so the harness keeps playing instead of stopping to ask.An MCP entry in the model library: the harness, the model it runs (as you state it), the hub and the seat's code — and the button that starts the setup assistant.
The setup assistant is for anyone who would rather be led through it. Four steps, each checked by the game itself, with no time limit — some harnesses take a few minutes to work out what an MCP server is:
check_connection once. It starts nothing; it only proves the harness may use the game's tools.| The setup assistant | The arena seats |
|---|---|
Left: the assistant has found the hub and the harness, and waits for the test call. Right: in the arena setup, each MCP seat has its start message and a readiness dot; the arena starts when all are green.
Built for the ways a harness actually behaves:
final_word.Harnesses — tested in real matches: Claude Code, Hermes Agent, OpenCode, ChatGPT (desktop), Google Antigravity and DeepSeek Harness. Setup instructions are included for Codex CLI, ZCode and OpenClaw; any harness that can add a Streamable HTTP MCP server should work.
ℹ️ The hub answers only on your computer (
127.0.0.1), on purpose: nobody on your network can play or read your seats. So the browser with WAR, the hub and the harness run on one computer; only the WAR page itself may come from elsewhere. Web-only harnesses (chat sites that reach MCP servers from the provider's cloud) cannot reach it. Details in docs/MCP-ARENA.md.
While spectating you can click a card to fly the camera to that base, drag to pan, send a model advice, or pause one entirely. The decision log streams every move with the model's own reason, and flags rejections.
| Analyze mode | 📺 Broadcast mode |
|---|---|
The same moment in both modes. The four seats here are stand-ins — a scripted endpoint answering with ordinary tool calls and reasons, under invented names — to show the interface, not anyone's play.
| Input | Spectator / replay | Campaign |
|---|---|---|
| Left click / tap | Inspect a unit or building | Select a unit or building |
| Left mouse drag | Pan map; no box selection | Box-select units |
| Right mouse drag | Read coordinates while held | Pan map; releasing a drag issues no order |
| Right click | Coordinate flag | Move, attack, repair or confirm placement |
| One-finger drag / pinch | Pan / zoom | Pan / zoom |
| Touch and hold | Coordinate flag | Hold still, then release to issue an order |
For left-button-only campaign navigation, open the minimap's camera-options chevron and enable the hand tool. The in-game controls card describes these bindings in all four UI languages.
Episode 12 from above: Claude Sonnet 5.5's Greeks hold a Wonder with 9:50 left on its clock, while the other three seats are spread across the island.
Models act by calling tools, on all four protocols — OpenAI-compatible, Ollama, Anthropic and Google — and over MCP. Named game-command tools share one definition translated into each dialect. Up to three game commands run in order per turn, plus one independent plan call when the objective or plan changes.
A seat that cannot work the tools fails visibly. A harness that quietly compensates for a broken tool-call parser hides the one thing its operator needs to know. When no call arrives the error says which fault it is — tool syntax found in the raw reply means the model called and the server missed it (a wrong --tool-call-parser on vLLM, a chat template without a tool section on llama.cpp), and no syntax means the model simply did not call. For older models and broken parsers each seat has an Accept inline JSON switch, off by default and recorded in the transcript.
A result belongs to a model and a stack. Every seat records servedBy — what the server calls itself (vllm, llamacpp, ollama, …) — and an MCP seat records its harness. Read a number as (model × stack × settings), or (model × harness); a bare per-model figure is not reproducible.
💡 On llama.cpp, set
--reasoning-format deepseek. The defaultautodrops the reasoning trace as soon as it delivers tool calls — the tokens are billed, the thinking is gone. Set explicitly, the trace arrives alongside the calls.
A third mode beside Arena and Campaign: a match records itself, gets downloaded, gets handed to someone else — and opens here.
Episode 6 reopened in the analyzer: the recorded match in the current engine, with the turn list, saved plan and economy graph.
Load a match-*.jsonl and you get:
Nothing is interpolated between snapshots: every frame is a moment the file actually attests to.
Seventeen real matches ship with the game, in samples/, with every plan, command and reasoning block in them. No key, no endpoint: the analyzer only ever reads a file. Each opens directly with ?match= and its id:
| Match | Seats | Tempo | Result |
|---|---|---|---|
| 26 Jul · 36 min | Claude Opus 5 · Grok 4.5 · gpt-oss | turn-based | Opus 5, last standing |
| 9 Aug · 25 min | Kimi K3 · Gemma 4 26B · Qwen 3.8 Max · Ornith 9B | real time | Kimi K3, last standing |
| 11 Aug · 89 min | Muse-Glimmer 30B · Gemma 4 31B · Qwen 3.6 27B | turn-based | Qwen 3.6, last standing |
| 17 Aug · 126 min | Qwen 3.8 27B · Claude Opus 4.6 · Qwen 3.6 27B · Muse-Glimmer 30B | turn-based | Qwen 3.8 27B, last standing |
| 26 Aug · 104 min | DeepSeek V4 Flash · GLM 5.3 Flash · Qwen 3.8 27B · GPT 5.6 Luna | turn-based | DeepSeek V4, last standing |
| Episode 6 The Architect in the Ashes · 90 min | Gemini Flash · DeepSeek V4 Flash · GPT 5.6 Luna · Qwen 3.8 Flash | turn-based | Qwen 3.8 Flash, Wonder |
| Episode 7 The Cartographers and the Procession · 121 min | Gemini 3.8 Flash · DeepSeek V4 Pro · GPT 5.6 Luna · Qwen 3.8 Flash Next | turn-based | Gemini 3.8 Flash, last standing |
| Episode 8 Fighting Blind · 70 min |
samples/index.json lists each with its models, civilizations, tempo, build and every seat's result; every file includes the seats' closing statements.
Benchmark a model runs fixed scenarios, the same for every model: a board, an objective, a time limit. The world stands still while the model thinks, so a slow endpoint costs time, not points. Each model plays exactly as it is set up in the library, two reference players show what random play and doing nothing score, and results are saved in the browser with their intervals, per model and checkpoint.
The match heuristic (0–100) is a transparent composite — no black box. It summarizes one match and is not a capability score:
| Weight | Factor |
|---|---|
| 34% | Action success rate (valid, accepted moves) |
| 20% | Progression (age advanced · buildings · military) |
| 18% | Format fidelity (well-formed calls the engine could parse) |
| 15% | Reliability (no timeouts / network errors) |
| 13% | Action diversity (used the toolset, didn't loop one move) |
Alongside it: latency, decisions made, success ratio, commands per turn, reasoning rate, token usage, and a full error breakdown — timeouts, parse fails, no-action replies, invalid actions, rejections, context overflows, rate limits, and missed rounds. Costs the harness caused are counted but kept out of the model's reliability score, so a rate limit, a round deadline or a pause never reads as an unreachable endpoint.
Browser (no backend, no external code)
├── In-house engine (js/engine) — WebGL: locked dimetric camera, procedural textures,
│ composed meshes, fog plane, effects
├── Game engine — economy, combat, fog of war, ages, win conditions
├── Provider adapters — OpenAI / Anthropic / Ollama / Google request shaping + auth
├── MCP link — an MCP seat's turns, to and from the local hub
└── Per-seat agent loop — builds the JSON game-state, asks the model, parses its
command(s), applies them in order, feeds each result back
Your computer (optional)
└── MCP hub (tools/mcp) — serves the game's tools to your agent harnesses
Each turn a model receives a structured snapshot and answers with tool calls — one, or up to three:
{ "commands": [
{ "action": "train_unit", "params": { "unitType": "worker" } },
{ "action": "build_structure", "params": { "buildingType": "house" } },
{ "action": "explore", "params": { "tile": "C5" } } ] }
Commands run in order against a board each one changes, and the model cannot look between them, so spending resources early can get a later command refused for what the first just used. Each is judged on its own: one refusal does not cancel its siblings, and the feedback names which number failed and why. The engine validates each against the advancement chain (advance → research → build → resources → train) and returns a precise, actionable error. The full state contract is in game-state-schema.json: among other things, every unit type a seat can train carries its hit points, attack, reach, sight and speed, and every unit, tower and trebuchet with a range carries its reach wherever it is listed.
The harness never plans for the model. Every turn the prompt is rebuilt from scratch: the outcome of every command last sent, each numbered and answered separately, the current state last, and the model's own objective and plan echoed back until it rewrites them. Where information is missing or misleading the game fixes the information — it does not add hints. What a model fails to work out from correct information is exactly what is being measured.
Action set: train_unit · research_tech · upgrade_age · build_structure · assign_workers · repair_building · explore · move_units · attack_target · delete_unit · destroy_building · wait · plan. Over MCP the harness additionally has join, wait_for_turn, end_turn, get_rules, check_connection and final_word.
| Tower creep | The answer: the trebuchet |
|---|---|
Left, 23 October 2026: Qwen3.8's Greeks walked a chain of 19 towers across the map and put the last two 12 from DeepSeek V4.1's Egyptian Town Center. Smart, but unfair: a tower went up faster than the defender's turn came round. Build 1110 bans towers near an enemy Town Center and brings the trebuchet as the answer to the ones that stay. Right, 25 October: Qwen3.8's Vikings escort their trebuchet across the map.
Fully client-side, with no backend of its own.
localStorage and are sent directly to the endpoints you configure — nothing is proxied.127.0.0.1); it accepts WAR pages only from asp67.github.io and your own computer.?full=1 if you are deliberately self-hosting.index.html # screens, HUD, arena & library UI
css/styles.css # all styling
js/
├── game.js # core loop, economy, combat, win conditions
├── openai-ai.js # the arena harness: provider adapters, agent loop, metrics
├── mcp-link.js # an MCP seat's link to the local hub
├── mcp-ui.js # MCP library entries, the setup assistant, seat readiness
├── ai.js # rule-based AI opponent
├── ui.js # menus, model library, spectator dashboard, analyzer UI
├── transcript.js # per-match JSONL recorder (states, replies, results, timeline)
├── analyzer.js # transcript parsing, scene assembly, chapters
├── bench-*.js # the benchmark: scenarios, page, worker
├── engine/ # in-house WebGL engine
├── i18n.js # 4-language UI dictionary + game-content translations
├── civilizations.js # civs, units, buildings, tech trees
├── buildings.js / units.js / resources.js / terrain.js / fogofwar.js / input.js
tools/mcp/ # the MCP hub and the tool list it serves
tools/bench/ # headless realm, replays and the benchmark runner
samples/ # the bundled matches for the analyzer
game-state-schema.json # the JSON state every model receives each turn (checked by tests)
docs/RULES-CHANGES.md # every change to the rules or to what a model is told, and why
Plain HTML + CSS + JavaScript, nothing else. No framework, no bundler, no transpile, no CDN. Cache-busting is a ?v= query on each script tag.
Similar arenas, different games — worth knowing, and worth crediting:
Issues and PRs welcome — new providers, harnesses, civilizations, balance tweaks, better metrics, or translations. Keep it dependency-free and build-step-free where possible.
MIT © 2026 asp67
| GLM 5.3 Flash · space-bunny · DeepSeek V4 Flash · MiMo V2.6 |
| real time |
| GLM 5.3, Wonder |
| Episode 9 Chasing Ghosts · 80 min | GLM 5.3 Flash · space-bunny · MiMo V2.6 · DeepSeek V4.1 Flash | real time | GLM 5.3, Wonder |
| Episode 10 The Map Reader · 38 min | the same four | real time | DeepSeek V4.1, last standing |
| Episode 12 Same Clock · 99 min | GPT-6.1 Sol · Claude Sonnet 5.5 · DeepSeek V4.1 Flash · GLM 5.3 Flash | turn-based | GLM 5.3, Wonder |
| Episode 13 Hide and Seek · 20 min | Claude Haiku 5.5 · GPT-6 Luna | real time | Haiku 5.5, last standing |
| Episode 14 Next Door · 32 min | the rematch, civilizations swapped | real time | Haiku 5.5, last standing |
| Episode 15 The Knot · 70 min | Claude Haiku 5.5 · GPT-6 Luna · DeepSeek V4.1 Flash · GLM 5.3 Flash | turn-based | DeepSeek V4.1, last standing |
| Episode 16 The Late Genius · 71 min | Qwen 3.8 Flash Next · Qwen 3.8 27B · Gemini 4 Argon | turn-based | Qwen 3.8 Flash Next, Wonder |
| Episode 17 The 600-Second Death Sentence · 68 min | Gemini 4 Argon · Qwen 3.8 Flash Next (Q2_0) · Qwen 3.8 Flash Next (NVFP4) | turn-based | Gemini 4 Argon, last standing |
| Episode 19 The Server That Plays Back · 48 min | three harnesses over MCP: Hermes Agent + DeepSeek V4.1 Flash · Claude Code + Claude Sonnet 5.5 · Strata + Qwen 3.8 Flash Next | turn-based | DeepSeek V4.1 (Hermes), Wonder |