A historical simulation you observe through one person's eyes.
You're dropped into a random era as a random person. The world moves whether you act or not. NPCs have their own lives. Sometimes nobody cares what you do. When you die, people forget you. Then it's over.
CHRONOS is a turn-based, natural-language simulation. There is no HUD, no score, no quest log. You type what your character does; the world — a 7-stage autonomous pipeline of personality-driven NPCs, scheduled consequences, and historically grounded events — figures out what happens next.
- Screens
- Quick start
- Environment variables
- How to play
- Controls
- Eras
- API
- Architecture
- Design & research
- Model & cost
- Project structure
- Ingest historical corpus
- Run tests
- Status
- License, contributing, security
UI mockups for the in-progress redesign live in
docs/ui-prototypes/ — start_screen,
loading_screen_text_only, game_active, observation_mode, erasure,
hamburger_panel, plus the
DESIGN.md of the
"Ink and Earth" design system. The shipped UI lives in
frontend/ and a standalone demo page in
demo/demo.html.
- Python 3.11 (tested on macOS M4 Max; the backend and frontend are cross-platform and also run on Linux and Windows)
- Google Gemini API key — required for all LLM calls. Get one at
ai.google.dev. Without it, all LLM calls return a
canned NoOp response (
"...") and the world still ticks, but the prose is intentionally degraded.
git clone https://github.com/Ijtihed/chronos.git
cd chronos
python3.11 -m venv .venv # or: uv venv --python 3.11 .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# Edit .env and set: GEMINI_API_KEY=your_key_here
uvicorn backend.main:app --reload
open http://localhost:8000 # Linux: xdg-open http://localhost:8000git clone https://github.com/Ijtihed/chronos.git
cd chronos
py -3.11 -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
Copy-Item .env.example .env
# Edit .env and set: GEMINI_API_KEY=your_key_here
uvicorn backend.main:app --reload
Start-Process http://localhost:8000| Variable | Purpose |
|---|---|
GEMINI_API_KEY |
Required. All LLM calls route to Gemini. Loaded from .env or the shell. Never commit this. |
CHRONOS_GEMINI_MODEL |
Override the Gemini model ID. Default: gemini-2.5-flash-lite. |
CHRONOS_GEMINI_MAX_CONCURRENT |
Cap on concurrent Gemini requests. Default: 15. Hard ceiling: 20. |
- Click Begin. A random era is assigned. Wait for character generation (1-2 min).
- Type anything in the text box. Anything at all — negotiate, flee, hoard, revolt, wait.
- The world simulates. NPCs do their own things. Your action is one thread among many.
- Press M to toggle the map. Click visited NPC markers for your impression of them.
- Highlight any word for a dictionary definition.
- When you die, you observe as memories fade. When the last person forgets you, it's over.
| Action | How |
|---|---|
| Submit action | Type + Enter |
| Toggle map | Press M (not in text input) |
| NPC perception | Click green marker on map |
| Word definition | Highlight a word in the narrative |
| Menu | Hamburger icon (top right) |
| Connections (NPC interaction graph) | Menu → Connections |
| New run | Menu → New Run |
| Continue saved run | Start screen → Continue |
| Era | Year | Region |
|---|---|---|
| Roman Late Empire | ~410 | Italia |
| Viking Age | ~870 | Scandinavia / North Sea |
| Crusader States | ~1190 | Levant |
| Black Death | ~1348 | Northern Italy / Southern France |
| Fall of Constantinople | ~1453 | Byzantine / Ottoman frontier |
Modern eras (1800s, 1900s, 2000s) planned for future expansion.
| Method | Path | What it does |
|---|---|---|
| POST | /api/run/preview |
Get era info instantly (for loading screen) |
| POST | /api/run |
Create new run (generates characters via LLM) |
| GET | /api/run/{id} |
Get run state |
| POST | /api/run/{id}/turn |
Player types anything — action, travel, or inaction |
| POST | /api/run/{id}/skip |
Advance time N ticks (1-30) without player action |
| GET | /api/run/{id}/npc/{npc_id}/perception |
Character's subjective impression of an NPC |
| GET | /api/run/{id}/interaction_graph |
Player-centric NPC interaction graph (read-only snapshot) |
| GET | /api/run/{id}/events/visible |
Map event pins. Civilizational + regional events only, lifetime window, regional events constrained to era home region. |
| POST | /api/run/{id}/reset |
Reset run |
| GET | /api/runs |
List all runs |
| GET | /api/geo/{era_key} |
Historical border GeoJSON for an era |
| GET | /api/health |
Health check |
The turn endpoint handles everything. Type "go to Ravenna" and it routes to travel. Type "wait" and your character acts on their own. Type "start a revolt" and the simulation figures out what happens.
Each turn runs a 7-stage autonomous pipeline. Stages 1–4 run without the player — the world is alive whether or not you type:
1. Structural drift — tension, dispositions, needs decay, rumors (no LLM)
2. Scheduled consequences — delayed effects from past actions fire (no LLM)
3. World events — probabilistic skirmishes, unrest, trade disruption (no LLM)
4. NPC autonomous actions — utility-scored decisions, LLM narration for nearby NPCs
5. Player action parsed (if they typed something)
6. Narrative assembled — what the player sees
7. State saved to database
The player is not the center. They are one person in a living world.
Every NPC has personality traits (ambition, compassion, courage, piety, pragmatism) and 17 inner needs (survival, safety, duty, faith, trade, etc.) that decay each turn. When needs go critical they override normal behavior — a merchant who normally chases profit will flee if survival spikes during a siege. Events permanently shift traits: prolonged hunger makes NPCs more pragmatic and less compassionate.
Significant actions schedule delayed effects. A betrayal on turn 5 might spread as a rumor on turn 7, shift NPC dispositions on turn 9, and increase tension on turn 12. Consequences are validated when they fire — if the world has diverged (the target NPC died, the location was destroyed), obsolete consequences are cancelled.
Two engines feed the narrative:
- HKE (Historical Knowledge Engine) — Gutenberg + Wikipedia chunked
into Chroma with
nomic-embed-text; retrieved to ground NPC speech in era-appropriate detail. - HCE (Historical Context Engine) — a SQLite table of 1,569 canonical events across the 5 eras, sourced from Wikidata SPARQL + Wikipedia, then filtered through a Knowledge Matrix (archetype × event type × geographic distance) so a farmer knows what farmers know, not what scribes know.
When a player action contradicts a canonical event — e.g. "prevent the Sack of Rome" in 410 — the divergence is detected end-to-end and the matching scheduled consequences are superseded.
CHRONOS is built design-first. Code serves the design, not the other way around. The design narrative is split into nine living documents:
| Doc | What it covers |
|---|---|
overview.md |
What the game is; perspective not protagonist; character assignment distribution |
gameplay.md |
Turn loop, NPC autonomy, perception, voice and tone, info, travel, factions, death |
npc-voice-system.md |
How NPCs speak — archetype voice, period-accurate language, what NPCs never do |
simulation-and-world.md |
World model, map (region knowledge + event markers), family, difficulty, run setup |
historical-context-engine.md |
HCE: Events DB + ground-level context generator (distinct from HKE) |
visuals.md |
Scene-illustration rules: first-person POV, honest bodies, prompt template, current candidate provider |
roadmap.md |
Phased delivery, success criteria, what is and isn't in each phase, completion log |
open-questions.md |
Unresolved design items — do not assume when implementing |
cursor_spec.md |
Index + current project status |
Related artifacts:
prompts/— every LLM call has a corresponding prompt template, reviewed separately from code. Adding a call site means adding a template.versions/— weekly snapshot changelogs (state of the project at the end of each week).context/other/lessons-learned.md— structural mistakes in thinking and their resolution, e.g. why the Phase 0–2 player-centric loop had to be rebuilt simulation-first on 2026-04-01..cursor/rules/— binding rules for any AI collaborator on the project: design constraints, dev protocol, model tier policy, roadmap gates, commit hygiene, weekly version cadence.
All LLM calls dispatch to a single provider — Google Gemini
(CHRONOS_GEMINI_MODEL, default gemini-2.5-flash-lite). The historic
fast/quality tier split has been retired; Ollama is no longer used at
runtime. The full policy is in
.cursor/rules/chronos-model-tier.mdc.
NoOp fallback. When GEMINI_API_KEY is missing, or when the Gemini
circuit breaker is open (3 failures in 60 s → 5 min), calls return
"..." and turns continue with degraded prose. Prevents cascading
failures on transient API issues. Set the key in .env to enable real
prose.
Per-run cost. ~€0.022 median per 10-turn run. €1.00 soft cap
(dismissible banner). €2.00 hard cap (turn submission blocked).
Per-turn cost is displayed in the top bar and persisted in
turn_logs.turn_cost_usd.
backend/ Python server (FastAPI)
main.py API — unified turn endpoint, run management, map geo, NPC perception
world_state.py Data models, state mutation, consequence queue
world_engine.py Simulation engine — NPC autonomous actions, consequence processing
npc_personality.py Personality traits, needs system, decay, event-driven shifts
action_parser.py Player input → structured action (LLM)
npc_engine.py NPC POV generation (LLM + RAG)
death_engine.py Death check, memory decay, erasure
character_gen.py Character + NPC generation at run start (LLM)
persistence.py SQLite session storage (WAL mode, performance-tuned)
llm_provider.py Gemini dispatcher + circuit breaker + NoOp fallback + cost accounting
config.py Environment config — API keys, pricing constants, cost caps
eras/ 5 era configs with locations, archetypes, coordinates
geo/ Region centroid resolution for event markers (YAML)
hke/ Historical Knowledge Engine (RAG — Chroma + Gutenberg/Wikipedia)
frontend/ Browser UI (vanilla JS + HTML + Tailwind)
index.html, app.js, map.js, graph.js, styles.css, geo/ (GeoJSON border files + Natural Earth coastlines)
prompts/ LLM prompt templates (design artifacts; reviewed separately from code)
context/ Game design docs (source of truth; living)
docs/ Long-form docs — UI prototype mockups, test status
versions/ Weekly snapshot changelogs
seeds/ Per-era seed data (NPC archetypes, locations)
scripts/ One-off CLI helpers (events DB builder, image-gen POC)
cache/ Wikidata / Wikipedia lookup cache (regenerated by HKE ingest)
demo/ Standalone demo page
tests/ Automated pytest suite (~842 cases across 35 modules)
.cursor/rules/ Binding rules for AI collaborators on the project
.github/workflows/ CI (offline pytest on push + PR)
# macOS / Linux
source .venv/bin/activate
# One era
python -m backend.hke.ingest --era roman_late_empire
# All 5 eras
python -m backend.hke.ingest --all# macOS / Linux
source .venv/bin/activate
pip install -r requirements-dev.txt
# Offline suite (no API key needed; this is what CI runs)
python -m pytest tests/ -m "not live" -q
# Live tests (require a real GEMINI_API_KEY; burn a small amount of API credit)
python -m pytest tests/test_live.py -v
# Everything
python -m pytest tests/ -v# Windows / PowerShell
.\.venv\Scripts\Activate.ps1
pip install -r requirements-dev.txt
python -m pytest tests/ -m "not live" -qFive tests are currently xfail-quarantined — they were written against
the retired Ollama mock setup. Tracking, repair plan, and root cause in
docs/TEST_STATUS.md.
| Phase | Status | What landed |
|---|---|---|
| 0 — Proof of Life | complete (2026-03-31) | Era selection, NL action parsing, basic loop |
| 1 — Playable Text Loop | complete (2026-04-01) | 7-stage pipeline, NPC personality/needs, structural drift, probabilistic events, utility scoring, consequence queue, time-skip |
| 2 — The Map | complete (2026-04-01) | 2D Leaflet map, historical borders, visited/unvisited NPC markers |
| 2.5 — Map Intelligence + HCE | complete (2026-04-21) | 1,569-event canonical DB, ground context generator, Knowledge-Matrix-filtered event markers (97.2% coverage), divergence detection |
| 3 — Scene Illustrations | in progress | Candidate provider soft-locked: mflux + FLUX.2 Klein 9B Q8 distilled — POC in scripts/imagegen_poc/ |
Weekly snapshots in versions/. Open design questions in
open-questions.md.
If you already have the repo and are pulling new changes:
# macOS / Linux
source .venv/bin/activate
pip install -r requirements.txt
rm -f data/chronos.db # schema may have changed
python -m pytest tests/ -m "not live" -q# Windows / PowerShell
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
Remove-Item -Force -ErrorAction SilentlyContinue data\chronos.db
python -m pytest tests/ -m "not live" -qThe database is recreated automatically on first run. Existing save games from before the schema change are not compatible — start a new run.
- Released under the MIT License.
CONTRIBUTING.md— design-first workflow, test conventions, commit style.SECURITY.md— how to report a vulnerability privately.