diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..8105e51c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,28 @@ +# Supervillain — agent entry point + +A persistent lair-building sim (Evil Genius x Dwarf Fortress) in Rust, built +spec-first by agents. Before changing anything, read in this order: + +1. **[DESIGN.md](DESIGN.md)** — the constitution. What the game is. Code + follows it, never the reverse. +2. **[knowledge/development-style.md](knowledge/development-style.md)** — how + to work here (the constitution rule and the working loop). +3. The relevant **[knowledge/](knowledge/)** files — architecture, sim + mechanics, art pipeline, workflows. + +## The one rule + +Any change to how the game functions ships with its DESIGN.md amendment in the +same commit. No amendment, no functional change. + +## Non-negotiables + +- Game rules live only in the lib (`src/sim.rs` and friends); the terminal and + Bevy binaries are thin views. `Sim` never reads the wall clock or does I/O. +- Definition of done: `cargo test` green, `cargo clippy --all-targets` clean + (both with and without `--features bevy_ui`), `cargo fmt` applied, and the + change observed in an actual run (see knowledge/workflows.md for the pty + smoke test and Bevy launch check). +- Commits: no AI attribution, explicit `git add` of intended files only. +- Update `knowledge/` files your change made stale, add a `devlogs/` entry for + the session, and push to `origin main` when done. diff --git a/DESIGN.md b/DESIGN.md index dba9b2c1..b5216410 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,7 +1,20 @@ -# Supervillain — Vision & Design Notes +# Supervillain — The Constitution -*Working draft, started 2026-07-05. This is the "what are we actually building" document. -Open questions are marked **[OPEN]** and get resolved into decisions as we make them.* +*Started 2026-07-05. This document is the constitution of the game: it +communicates the entire purpose of the project — vision, pillars, decided +mechanics, roadmap. Open questions are marked **[OPEN]** and get resolved into +the decisions log as we make them.* + +## The constitution rule + +This project is spec-driven. **Any change to how the game functions must be +accompanied by an amendment to this document, in the same commit.** The +amendment is the decision; the code implements it. If code and constitution +disagree, the constitution wins — fix the code, or amend the constitution +deliberately with a decisions-log entry. The working method is described in +[knowledge/development-style.md](knowledge/development-style.md); the point of +it is that agents set loose on this repository should be able to build the +game from this document alone, session after session, without drift. ## The pitch @@ -175,3 +188,8 @@ strike team hit my lair — and I never pressed SPACE."* desertion/informants. Full DF inner lives deferred, not rejected. - **2026-07-05 — First milestone: heat + schemes in terminal.** Prove the loop in ASCII before the art push. +- **2026-07-05 — Development style: spec-driven, constitution-first.** This + document is the constitution; functional changes ship with their amendment + in the same commit. Supporting structure: `knowledge/` (current-state facts, + edited in place), `devlogs/` + `DEVLOG.md` (history, append-only), and a + repo `CLAUDE.md` that points every agent session here first. diff --git a/README.md b/README.md index cf05b483..484c4071 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,26 @@ A persistent lair-building simulation — Evil Genius meets Dwarf Fortress. Build an underground lair, run schemes against the outside world, and survive the raids your infamy provokes. +## How this game is built + +This project is an experiment in **spec-driven game development**: +[DESIGN.md](DESIGN.md) is the *constitution* — one document that communicates +the entire purpose of the game. Every change to how the game functions must +ship with an amendment to the constitution in the same commit; the spec change +is the decision, the code is its implementation. Then we set agents at the +constitution, repeatedly, and see if a good video game can be described into +existence. + +The supporting structure: + +- **[DESIGN.md](DESIGN.md)** — the constitution. Prescriptive. Read it first. +- **[knowledge/](knowledge/)** — current-state facts (architecture, mechanics, + pipelines, workflows). Edited in place; staleness is a bug. +- **[devlogs/](devlogs/)** and **[DEVLOG.md](DEVLOG.md)** — history. + Append-only: session writeups and the quick ledger. +- **[CLAUDE.md](CLAUDE.md)** — the entry point that routes agent sessions + through all of the above. + ## Gameplay You are the **Supervillain**. Time flows continuously (pause with `SPACE`); there are no levels, waves, or phases — everything runs on one clock: diff --git a/devlogs/2026-07-05-constitution-and-knowledge.md b/devlogs/2026-07-05-constitution-and-knowledge.md new file mode 100644 index 00000000..a133fd73 --- /dev/null +++ b/devlogs/2026-07-05-constitution-and-knowledge.md @@ -0,0 +1,32 @@ +# 2026-07-05 — The constitution rule, formalized; knowledge/ created + +Meta-session: no game code changed. We made the development style itself a +first-class, documented thing. + +## What shipped + +- **The constitution rule**, written into the front of DESIGN.md (now titled + "The Constitution"): any change to how the game functions ships with its + spec amendment in the same commit. If code and constitution disagree, the + constitution wins. The stated goal: set agents at the constitution, + repeatedly, and see if a good video game can be described into existence. +- **knowledge/** — the current-state layer between the prescriptive + constitution and the append-only history. Five files: development-style + (the rule + working loop), architecture (sim/frontend contract and its + invariants), sim-mechanics (every tuning constant with file pointers), + art-pipeline (Pixel Lab facts + Bevy integration), workflows (build/verify + commands, the pty smoke test, git conventions). The maintenance contract: + knowledge files are edited in place, and a stale one is a bug to be fixed in + the same commit that made it stale. +- **CLAUDE.md** at the repo root — the agent entry point. Routes every future + session through constitution → development style → knowledge before any + change. This is the mechanism that makes "set agents at the constitution" + actually happen rather than remaining an aspiration. +- README now leads with the development-style section, before gameplay. + +## The document taxonomy (worth restating) + +- DESIGN.md — prescriptive, the law. Amended deliberately, with a decisions log. +- knowledge/ — present tense, how things work now. Edited in place. +- devlogs/ + DEVLOG.md — past tense, append-only, allowed to be opinionated. +- CLAUDE.md — the router that puts the above in every agent's path. diff --git a/knowledge/README.md b/knowledge/README.md new file mode 100644 index 00000000..6f238044 --- /dev/null +++ b/knowledge/README.md @@ -0,0 +1,27 @@ +# Knowledge base + +Durable, current-state facts about this project, for anyone — human or agent — +who works on it. Read this folder before making changes. + +How it differs from the other documents: + +- **[DESIGN.md](../DESIGN.md)** is the *constitution*: what the game is and why. + It is prescriptive. Code follows it, never the other way around. +- **`devlogs/` and `DEVLOG.md`** are *history*: what happened, in order. Never + edited after the fact. +- **`knowledge/`** is *present tense*: how things actually work right now. + When reality changes, these files are edited in place. A stale knowledge file + is a bug — fix it in the same commit that made it stale. + +## Contents + +- [development-style.md](development-style.md) — the constitution rule and how + to work here. Read this first. +- [architecture.md](architecture.md) — crate layout, the sim/frontend split, + and the invariants that protect it. +- [sim-mechanics.md](sim-mechanics.md) — the implemented game rules and tuning + constants, with file pointers. +- [art-pipeline.md](art-pipeline.md) — Pixel Lab facts beyond what the skill + covers, and how art flows into the Bevy frontend. +- [workflows.md](workflows.md) — build/test/run/verify commands, git + conventions, and testing tricks that caught real bugs. diff --git a/knowledge/architecture.md b/knowledge/architecture.md new file mode 100644 index 00000000..7a75dcc2 --- /dev/null +++ b/knowledge/architecture.md @@ -0,0 +1,68 @@ +# Architecture + +One library crate holding all game rules, plus two binary frontends that are +pure view/input layers. + +## Crate layout + +``` +src/ + lib.rs — module list only + sim.rs — Sim: owns ALL game state; fixed-tick advance(); the heart + map.rs — GameMap: tiles grid, power flood-fill, pathfinding + tiles.rs — TileType data (costs, power, effects) + scheme/research/loot tables + combat.rs — CombatSystem: agent/minion/henchman turns, traps, powers + entities/ — Player, Minion, Agent, Henchman, core Entity + events.rs — mid-raid random events + build.rs — BuildMode (cursor, category/item selection, place/demolish) + superpowers.rs — the 9 powers, unlock rules, cooldowns + save.rs — line-based text save format (see below) + bin/terminal/ — crossterm frontend (default feature "terminal") + bin/bevy.rs — Bevy 0.18 frontend (feature "bevy_ui") +``` + +## The sim/frontend contract + +These invariants are load-bearing (they keep replays, debugging, and the +future async-multiplayer option open — see DESIGN.md guardrails): + +- **Game rules live only in the lib.** If a frontend needs an `if` about game + behavior, that logic belongs in `Sim`. The Bevy frontend once forked the + rules and diverged badly; that cost a full rewrite (see + devlogs/2026-07-05-bevy-sim-port.md). +- **`Sim` never reads the wall clock and does no I/O.** Frontends own the + wall-clock-to-tick mapping (`tick_ms`, pause, bounded catch-up of max 5 + ticks per frame). `Instant::now()` appears only in frontend code. +- **Frontends talk to the sim through command methods** (`move_player`, + `launch_scheme`, `buy_research`, `hire_minion`, `activate_power`, + `create_save_state`/`apply_save_state`) and read state directly for + rendering. Messages flow back via `drain_log()` — call it once per + frame/turn, not per command batch. +- **After build actions** (place/demolish, done by frontends via `BuildMode` + mutating `sim.map`/`sim.player` directly), call `sim.recompute_derived()`. + This is the one place frontends mutate sim internals; a future cleanup could + move BuildMode application into a sim command. + +## Save format + +`save.rs`, line-based text, header `SUPERVILLAIN_SAVE_v1`, written to +`dirs::data_dir()/supervillain/supervillain_save.txt` (macOS: +`~/Library/Application Support/supervillain/`). + +- One record type per line (`PLAYER`, `UNLOCKS`, `SCHEME`, `TILES`, `SIM`...). + Unknown lines are ignored, missing lines get defaults — so **adding a line + type is backward compatible**; changing an existing line's field order is + not. +- `SIM ` carries the clock; `next_raid_in <= 0` means + "reschedule on load". +- Active raids are not persisted: on load, agents clear and the raid is + dropped ("the raiders withdraw"). + +## Known architectural debts + +- **RNG is unseeded** (`rand::rng()` in agent generation and combat). + Determinism guardrail says this must become a seeded, serialized RNG before + any multiplayer/replay work. +- The Bevy frontend respawns every unit sprite each frame `Game` changes + (simple, fine at this scale; revisit if entity counts grow). +- `BuildMode` lives in the lib but is really frontend-shared UI state. diff --git a/knowledge/art-pipeline.md b/knowledge/art-pipeline.md new file mode 100644 index 00000000..4f395ebe --- /dev/null +++ b/knowledge/art-pipeline.md @@ -0,0 +1,50 @@ +# Art pipeline + +The generation workflow lives in the **pixellab skill** +(`.claude/skills/pixellab/SKILL.md`) — auth, endpoints, script usage, +conventions. This file covers what matters beyond generating: state and +integration. + +## Facts + +- Token: `PIXELLAB_API_KEY` exported in `~/.zshrc` (harness shells may need + `source ~/.zshrc`). Billing is generation-credit based — `/balance` showing + `{"usd": 0.0}` does NOT mean blocked. +- Real minimum canvas for transparent (`no_background`) generations is + **32x32** despite the OpenAPI schema claiming 16. Everything is standardized + on 32x32. +- Style coherence comes from two manifest defaults: seed **1337** and the + shared style suffix ("dark evil lair theme, muted palette with purple and + teal accents"). Change them in `assets/pixellab/manifest.json` only, never + per-prompt. + +## Current inventory (assets/pixellab/) + +- `sprites/` (transparent, low top-down, facing south): villain, henchman, + minion_{worker,guard,technician,scientist}, + agent_{investigator,thief,saboteur,soldier,super}. +- `tiles/` (opaque, high top-down): floor, wall, rock, throne, power_core, + lab, vault, barracks, guard_post, trap_spike, trap_fire, door. + +## Integration (Bevy frontend) + +- `src/bin/bevy.rs` loads everything into an `Art` resource in `Art::load`: + `HashMap, Color)>` for tiles (the Color is a tint, + WHITE = as-authored) plus per-class/type maps for units. +- Tile types **without** dedicated art either reuse a base texture with a tint + (Entry/FakeEntry = tinted floor; SecurityDoor1-3 = increasingly red door; + TrapElec/Gas/Laser/Wind = tinted trap_spike) or fall back to flat colors + (`tile_color`: Medbay, TrainingRoom, Hotel, Freezer, Armory, ControlRoom, + LootRoom — these are the ones most wanting new art). +- Unpowered functional tiles (power_usage > 0) render tint-dimmed to 0.45. +- Nearest-neighbor sampling is set globally via + `ImagePlugin::default_nearest()` — don't load pixel art without it. +- The terminal frontend uses none of this (chars + colors). + +## Adding an asset, end to end + +1. Add an entry to `assets/pixellab/manifest.json`. +2. `python3 .claude/skills/pixellab/scripts/generate.py --batch assets/pixellab/manifest.json` + (idempotent; only the new entry generates). +3. Map it in `Art::load` (replace a tint-reuse or color fallback). +4. Commit the PNG — regeneration costs credits and a different result. diff --git a/knowledge/development-style.md b/knowledge/development-style.md new file mode 100644 index 00000000..99849f64 --- /dev/null +++ b/knowledge/development-style.md @@ -0,0 +1,56 @@ +# Development style: spec-driven, constitution-first + +This project is an experiment in **describing a good video game into +existence**. The spec comes first; the code is downstream of it. + +## The constitution rule + +[DESIGN.md](../DESIGN.md) is the constitution: a single document that +communicates the entire purpose of the game — vision, pillars, decided +mechanics, and roadmap. + +**Any change to how the game functions must be accompanied by a change to the +constitution, in the same commit.** Not as an afterthought — the spec change +*is* the decision; the code change is its implementation. If you cannot express +a change as an amendment to the constitution, that is the signal it does not +belong in the game. + +Corollaries: + +- If code and constitution disagree, the constitution wins. Either fix the + code or amend the constitution deliberately (with a decisions-log entry) — + never let them drift silently. +- Pure refactors, formatting, tooling, and bug fixes toward already-specified + behavior need no amendment. Tuning changes that alter game *feel* do. +- Decisions get dated entries in the constitution's decisions log. Rejected + alternatives are worth a sentence — future agents will otherwise re-propose + them. + +## Why this shape + +The intent is to run agents at this repository over many sessions and have +them build the game coherently without a human re-explaining it each time. +That only works if: + +1. **The spec is complete enough to implement from.** An agent reading only + DESIGN.md should know what the game is supposed to be. +2. **The knowledge base is honest.** `knowledge/` says how things work *now*; + stale knowledge poisons every downstream session. +3. **History is preserved.** `devlogs/` (session writeups) and `DEVLOG.md` + (quick ledger) explain why the code looks the way it does, including the + dead ends. + +## The working loop + +1. Read the constitution ([DESIGN.md](../DESIGN.md)) and relevant + `knowledge/` files. +2. Amend the constitution with what is about to change (or confirm it already + specifies it). +3. Implement in the sim core first, frontends second (see + [architecture.md](architecture.md)). +4. Verify: tests, clippy, both-feature builds, and an actual run (see + [workflows.md](workflows.md)). +5. Update any `knowledge/` file the change made stale. +6. Write it down: devlog entry for a session's work, DEVLOG.md line for the + ledger. +7. Commit (spec + code + knowledge together) and push. diff --git a/knowledge/sim-mechanics.md b/knowledge/sim-mechanics.md new file mode 100644 index 00000000..67d645f9 --- /dev/null +++ b/knowledge/sim-mechanics.md @@ -0,0 +1,75 @@ +# Sim mechanics — implemented rules and tuning constants + +Current as of 2026-07-05. Constants live at the top of `src/sim.rs` unless +noted. Changing game feel here requires a DESIGN.md amendment first (see +development-style.md). + +## The clock + +- One fixed-tick simulation; frontends default to **150ms/tick**, adjustable + 20-500ms (`+`/`-`), pausable (SPACE/p). +- Cadences (in ticks): raid combat **every tick** during a raid; economy every + **20** (`ECONOMY_INTERVAL`); schemes + heat decay every **40** + (`SLOW_INTERVAL`). +- Sidebar "Day" = `1 + tick / 400` (~1 minute per day at default speed). + +## Threat and raids + +- Heat below **10** (`RAID_HEAT_THRESHOLD`): countdown paused, lair is safe. +- Countdown scheduled after each raid (and at start): + `max(900 - heat*6 - notoriety, 180)` ticks. +- Countdown decrements by `1 + heat/50` per tick — rising heat accelerates an + already-scheduled raid. +- Warning log line when the countdown crosses **80** ticks (~12s). +- Raid strength = `min(1 + notoriety/25 + heat/30, 12)`; fed to + `generate_agent_wave(strength, heat, entries)` (agent.rs) which also adds + `heat/30` bonus agents, multi-entry spawns at strength >= 5, elites at + higher strengths. +- Raid start: 5-tick announcement pause; guard posts auto-staff. +- Repelling a raid: gold `20 + 10*strength`, notoriety `+2*strength`, heat + `-10`, villain heals 10, trap combo bonus gold, next raid scheduled. +- Agents reaching the throne deal 15 damage to the villain (blocked by + invulnerability); villain death = game over. +- Killing agents grants notoriety by type: Investigator 2, Thief 3, Soldier 4, + Saboteur 5, SuperAgent 15 (in `sim.rs::combat_step`). + +## Economy + +- Income only on economy ticks: each living Worker earns `3 + vault_count` + gold; research from powered tiles' `research_gen()` + Scientist bonuses + + `player.research_bonus`. +- Power: cores generate (`power_gen`, mk2 doubles, + `power_bonus` each); + powered tiles consume; grid is a flood fill from cores + (`map.update_power`). Unpowered traps/facilities do nothing. +- Minion cap = `3 + 2*barracks`. +- Heat cooling per slow tick: `1 + powered facility heat_reduction (hotels, + freezers) + player.heat_reduction_bonus`. + +## Schemes (world map) + +- 9 schemes defined in `tiles.rs::available_schemes()` (gold 100-600, heat + 5-35, notoriety 3-20, duration 2-6 scheme-turns, some with loot). +- One scheme-turn = one slow tick (40 ticks, ~6s default) — a 3-turn scheme is + ~18s. +- Launching consumes living minions (Workers preferred, then anyone). + **Debt: they never come home** — DESIGN.md wants deployed-and-returned with + risk/failure rolls. +- Completion pays gold/notoriety/heat and possibly loot; loot bonuses apply + immediately (`tiles.rs::loot_items()`). + +## Powers + +- 9 superpowers (`superpowers.rs`), unlocked by research keys + notoriety + gates; `player.update_powers()` must be called after research/notoriety + changes (sim does this on research buy and scheme completion). +- Cooldowns tick per combat turn during raids, per economy tick during peace. + +## Known mechanical debts (also flagged in DESIGN.md) + +- Scheme minions consumed, not returned. +- Single global heat pool — no per-agency escalation ladder yet. +- Minions have no needs/morale (Milestone 1 remainder). +- Difficulty knobs are untested at long horizons: nothing yet prevents + parking heat below 10 forever and playing SimCity. The heat *floor* from + schemes being the only income pressure is what forces engagement — watch + this in playtesting. diff --git a/knowledge/workflows.md b/knowledge/workflows.md new file mode 100644 index 00000000..6765b3ea --- /dev/null +++ b/knowledge/workflows.md @@ -0,0 +1,64 @@ +# Workflows + +## Build, test, verify + +```bash +cargo test # 130 tests, all in the lib (sim has 19 dedicated) +cargo clippy --all-targets # must stay at zero warnings +cargo fmt # rustfmt is enforced-by-convention since 05048e4 +cargo build --features bevy_ui --bin supervillain-bevy # keep the Bevy build green +cargo clippy --features bevy_ui --all-targets # ...and lint-clean +``` + +The definition of done for a functional change: tests pass, both clippy runs +clean, both frontends build, and the change was *seen running* (below). + +## Running the game + +```bash +cargo run --release # terminal frontend +cargo run --release --features bevy_ui --bin supervillain-bevy # Bevy frontend +``` + +- The Bevy frontend must be run via `cargo run` (or with `BEVY_ASSET_ROOT` + pointing at the repo root): Bevy resolves `assets/` from `BEVY_ASSET_ROOT`, + then `CARGO_MANIFEST_DIR`, then the executable's directory. Running the + binary directly from `target/` silently loads zero assets. + +## Headless smoke tests (they catch real bugs) + +Terminal frontend through a pty — this found a capacity-overflow crash and an +asset regression on the first two uses: + +```bash +(printf '\n'; sleep 4; printf 'm'; sleep 1; printf '\r'; sleep 2; printf '\x1b'; sleep 1; printf 'q') | \ + script -q /dev/null sh -c 'stty rows 40 cols 140; ./target/debug/supervillain' +``` + +The `stty` matters: `script`'s pty is otherwise 0x0 (the game guards against +< 60x20 now, but you want a real render). + +Bevy launch check (opens a window briefly; greps the log for failures): + +```bash +BEVY_ASSET_ROOT=$PWD ./target/debug/supervillain-bevy > /tmp/bevy.log 2>&1 & +sleep 8 && kill %1; grep -iE "panic|ERROR" /tmp/bevy.log +``` + +## Git conventions + +- Commits: **no AI attribution**, plain descriptive messages, body bullets for + multi-part changes. Stage files explicitly — `git add -A` is not permitted. +- Spec-driven rule: functional change commits include their DESIGN.md + amendment and any knowledge/ updates (see development-style.md). +- Remote: `origin` is a Tangled knot (`tangled.org`, SSH). Push `main` + directly; no PR flow currently. +- `.letta/settings.local.json` is gitignored (machine-local session state); + `.letta/.lettaignore` is tracked. + +## Documentation flow per session + +1. DESIGN.md amendment (if functionality changed). +2. knowledge/ updates (if reality changed). +3. `devlogs/YYYY-MM-DD-topic.md` session writeup; DEVLOG.md gets the short + ledger line.