Rust game engine for the SideQuest AI Narrator. Ported from Python (sq-2).
This is a personal project under the slabgorb GitHub account.
- No Jira integration. Never create, reference, or interact with Jira tickets.
- No 1898 org. Nothing goes to the work GitHub org. Ever.
- All repos live under
github.com/slabgorb/.
Four repos compose the SideQuest Rust rewrite:
- sidequest-api — Rust game engine and WebSocket API (workspace with 12 crates)
- sidequest-ui — React/TypeScript game client
- sidequest-daemon — Python media services (image gen, audio)
- sidequest-content — Genre packs (YAML configs, audio, images, world data)
Orchestrator repo (orc-quest) coordinates sprint tracking, docs, ADRs, and cross-repo scripts.
- No stubs, no hacks, no "we'll fix it later" shortcuts
- No skipping tests to save time
- No half-wired features — connect the full pipeline or don't start
- If something needs 5 connections, make 5 connections. Don't ship 3 and call it done.
- Never say "the right fix is X" and then do Y. Do X.
- Never downgrade to a "quick fix" because you think the context is "just a playtest." Every playtest is production tomorrow. Fix it right.
If something isn't where it should be, fail loudly. Never silently try an alternative path, config, or default. Silent fallbacks mask configuration problems and lead to hours of debugging "why isn't this quite right."
Don't create stub implementations, placeholder modules, or skeleton code. If a feature isn't being implemented now, don't leave empty shells for it. Dead code is worse than no code.
Before building anything new, check if the infrastructure already exists in the codebase. Many systems are fully implemented but not wired into the server or UI. The fix is integration, not reimplementation.
When checking that something works, verify it's actually connected end-to-end. Tests passing and files existing means nothing if the component isn't imported, the hook isn't called, or the endpoint isn't hit in production code. Check that new code has non-test consumers.
Unit tests prove a component works in isolation. That's not enough. Every set of tests must include at least one integration test that verifies the component is wired into the system — imported, called, and reachable from production code paths.
If it doesn't involve operating LLMs, it goes in Rust. If it needs to run model inference (Flux, ACE-Step — not Claude), use Python for library maturity. Claude calls go through Rust as CLI subprocesses.
Every backend fix that touches a subsystem MUST add OTEL watcher events so the GM panel can verify the fix is working. Claude is excellent at "winging it" — writing convincing narration with zero mechanical backing. The only way to catch this is OTEL logging on every subsystem decision:
- Intent classification — what was the action classified as, and why?
- Agent routing — which agent handled the action?
- State patches — what changed in game state (HP, location, inventory)?
- Inventory mutations — items added/removed, with source
- NPC registry — NPCs detected, names assigned, collisions prevented
- Trope engine — tick results, keyword matches, activations
- Encounter engine — beat selections, metric changes, resolution
The GM panel is the lie detector. If a subsystem isn't emitting OTEL spans, you can't tell whether it's engaged or whether Claude is just improvising.
Not needed for: Cosmetic UI changes (labels, spacing, colors).
Before designing or modifying a subsystem, check the relevant ADR (78 total):
| Domain | ADRs |
|---|---|
| Core architecture | 001 (Claude CLI only), 002 (SOUL principles), 005 (background-first), 006 (graceful degradation) |
| Genre packs | 003 (pack architecture), 004 (lazy binding) |
| Prompt engineering | 008 (three-tier taxonomy), 009 (attention-aware zones), 066 (persistent Opus sessions / Full vs Delta tier) |
| Agent system | 010 (intent routing), 011 (JSON patches), 012 (session mgmt), 013 (lazy extraction), 057 (narrator-crunch separation), 059 (monster manual server-side pregen), 067 (unified narrator agent — no keyword matching) |
| Characters | 007 (unified model), 014 (diamonds/coal), 015 (builder FSM), 016 (three-mode chargen) |
| Encounters | 017 (cinematic chase — superseded by 033), 033 (confrontation engine + resource pools), 071 (tactical ASCII grids) |
| World / NPCs | 018 (trope engine), 019 (cartography), 020 (NPC disposition), 022 (world maturity), 055 (room graph navigation) |
| Progression | 021 (four-track progression), 052 (narrative axis system) |
| Narrative pacing | 024 (dual-track tension), 025 (pacing detection), 050 (image pacing throttle), 051 (two-tier turn counter) |
| Session persistence | 023 (state + recap) |
| Frontend / protocol | 026 (client state mirror), 027 (reactive state messaging), 054 (WebRTC voice chat disabled), 065 (protocol message decomposition), 076 (narration protocol collapse post-TTS) |
| Multiplayer | 028 (perception rewriter), 029 (guest NPC players), 030 (scenario packs), 053 (scenario system) |
| Telemetry | 031 (game watcher semantic telemetry), 058 (Claude subprocess OTEL passthrough) |
| Media | 032 (genre LoRA style training), 034 (portrait identity consistency), 056 (script tool generators) |
| Codebase structure | 060 (genre models decomposition), 061 (lore module decomposition), 062 (server lib extraction), 063 (dispatch handler splitting), 064 (game crate domain modules), 068 (magic literal extraction), 072 (system/milieu decomposition) |
| Dice | 074 (dice resolution protocol), 075 (3D dice rendering) |
| Fine-tuning | 069 (scenario fixtures), 073 (local fine-tuned model architecture) |
| Image pipeline | 070 (MLX image renderer) |
- Fully spoilable:
mutant_wasteland/flickering_reachonly - Fully unspoiled: Everything else
cargo build # Build
cargo test # Run tests
cargo clippy # Lint
cargo fmt # Format
cargo run # Run the serverPort of the Python game engine with these Rust equivalents:
- Pydantic models → serde structs
- asyncio → tokio
- aiohttp → axum
- pyyaml → serde_yaml
- sqlite3 → rusqlite
- claude CLI subprocess → tokio::process::Command
The ML stack (image gen, audio) stays in Python as a sidecar daemon.
| Crate | Role |
|---|---|
sidequest-protocol |
GameMessage, typed payloads (serde) — ~6.3k LOC |
sidequest-genre |
YAML genre pack loader, models, validation — ~5.2k LOC |
sidequest-game |
State, characters, encounters (StructuredEncounter), tropes, audio, rendering — ~29.3k LOC |
sidequest-agents |
Claude CLI subprocess orchestration, prompt framework, tools — ~10.3k LOC |
sidequest-daemon-client |
Unix socket client for Python media daemon — ~500 LOC |
sidequest-server |
axum HTTP/WebSocket, sessions, dispatch pipeline — ~16k LOC |
sidequest-encountergen |
CLI: enemy stat block generator from genre pack data — ~840 LOC |
sidequest-loadoutgen |
CLI: starting equipment generator from genre pack data — ~270 LOC |
sidequest-namegen |
CLI: NPC identity block generator from genre pack data — ~460 LOC |
sidequest-validate |
CLI: genre pack YAML schema validator — ~770 LOC |
sidequest-telemetry |
OTEL span definitions and watcher macros — ~290 LOC |
sidequest-promptpreview |
CLI: prompt preview and inspection — ~550 LOC |
Each crate has its own CLAUDE.md with a feature inventory. Read those before modifying a crate.
- Branch strategy: gitflow
- Default branch: develop
- Feature branches:
feat/{description} - PRs target: develop