Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
42e90e1
sprint 021: plan — unified save schema, world RNG reproducibility, au…
vladmesh Jul 10, 2026
46cc185
sprint 021 phase 1: task breakdown
vladmesh Jul 10, 2026
5356c50
sprint 021 phase 1 task 1: thread world seed into layers
vladmesh Jul 10, 2026
9f08a37
sprint 021 phase 1 task 2: move world randomness to layer rng
vladmesh Jul 10, 2026
bafd788
sprint 021 phase 1 task 3: pin world determinism
vladmesh Jul 10, 2026
4ca43ae
sprint 021 phase 1: close — world seed threaded, layers off global ra…
vladmesh Jul 10, 2026
3c3f9e8
sprint 021 phase 2: task breakdown
vladmesh Jul 10, 2026
1c0ac07
sprint 021 phase 2 task 1: add typed layer state models
vladmesh Jul 10, 2026
7457181
sprint 021 phase 2 task 2: add typed entities state
vladmesh Jul 10, 2026
3e786d9
sprint 021 phase 2 task 3: add versioned save envelope
vladmesh Jul 10, 2026
d7191ae
sprint 021 phase 2: review — task 4 rework spec (entities models as s…
vladmesh Jul 10, 2026
700c12b
sprint 021 phase 2 task 4: make entities save models authoritative
vladmesh Jul 10, 2026
25b4e8b
sprint 021 phase 2: e2e report
vladmesh Jul 10, 2026
b660db4
sprint 021 phase 2: close — save schema v1, entities models authorita…
vladmesh Jul 10, 2026
4dd5040
sprint 021 phase 3: task breakdown
vladmesh Jul 10, 2026
07a1117
sprint 021 phase 3 task 1: add periodic autosave
vladmesh Jul 10, 2026
cbcf44b
sprint 021 phase 3 task 2: log autosave failures and clean integratio…
vladmesh Jul 10, 2026
7a2296d
sprint 021 phase 3: skip evict autosave for explicitly deleted sessions
vladmesh Jul 10, 2026
b5cfc5d
sprint 021 phase 3: close — periodic autosave, de-silenced errors, sa…
vladmesh Jul 10, 2026
1faec75
sprint 021: audit
vladmesh Jul 10, 2026
9a7af29
sprint 021: audit triage — close sprint items, file save-round-concur…
vladmesh Jul 10, 2026
444a653
sprint 021: post-audit e2e smoke
vladmesh Jul 10, 2026
cb81afb
sprint 021: e2e close triage — reclassify automation blocker, file te…
vladmesh Jul 10, 2026
2fec982
docs: update living docs (full sync, 6 files)
vladmesh Jul 10, 2026
ca93170
sprint 021: close — save schema v1, world reproducibility, autosave h…
vladmesh Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .claude/skills/update-docs/state.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"last_run": "2026-06-29",
"last_run": "2026-07-10",
"last_run_mode": "full",
"last_commit": "c37507c",
"last_full_run": "2026-06-29"
}
"last_commit": "cb81afb",
"last_full_run": "2026-07-10"
}
8 changes: 8 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,14 @@ Centralized derived stat computation replacing ad-hoc logic scattered across com

**Convenience API:** `effective_speed(creature)`, `effective_ac(creature)`, `attack_modifiers(attacker, target, melee)`.

## Save Schema & Reproducibility

**Save format** (Sprint 021): one versioned Pydantic envelope — `SaveGame(schema_version=1, meta, world)` in `storage/save_schema.py`. `WorldSave` carries the world seed, dice RNG state, time, last tick times, and typed layer states: each layer owns a state model (`layers/*/state.py`, `layers/entities/save_models.py`) that is the authoritative format (`extra="forbid"`), while the `Layer` ABC keeps its dict-facing `get_state()/load_state()` signatures (core stays pydantic-free). Entity payloads are a discriminated union on `entity_type` (`PlayerSave`/`NpcSave`/`CreatureSave`/`ContainerSave`) built directly from live objects in `entity_serialization.py`; combat state persists turn order, round, battle map, and sides. `save_game()` and `autosave_session()` build the same envelope; `load_game()` validates it and rejects legacy saves without `schema_version`.

**Reproducibility**: `DND_WORLD_SEED` (env; random + logged when absent) seeds the world in `game_service` — per-layer seeds are derived deterministically and passed to layer constructors, which own their `random.Random` streams (weather, politics, ecology roam/retreat/lair depletion, entity encounters). The dice RNG (`rules/dice.py`, `DND_DICE_SEED`) is a separate stream. All RNG states are serialized into the save, so a loaded game continues the same random sequences — same seed, same content → identical world evolution (pinned by `tests/unit/test_world_seed.py`).

**Autosave**: per-action (create_player), on session evict, on shutdown, plus a periodic lifespan task every `DND_AUTOSAVE_SECONDS` (default 120, cancelled before the final shutdown autosave). Autosave failures are logged, never suppressed.

## Logging

Structured logging via `structlog` (`logging_config.py`, `logging_file_dispatch.py`). `LOG_LEVEL` env var controls verbosity (default: WARNING). When `LOG_LEVEL=DEBUG` and stderr is a TTY, uses pretty console renderer; otherwise JSON. `LOG_DIR` enables denormalized JSONL file dispatch per domain tag. See [docs/LOGGING.md](docs/LOGGING.md).
Expand Down
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ adapters/ — FastAPI REST + WebSocket API

rules/ — pure D&D mechanics: combat, validation, conditions, weapons, modifiers, proficiency, sneak attack, divine smite, fighting style, resources, character creation (point buy, HP, starting equipment), leveling (XP-by-CR, thresholds, perform_level_up), action providers, handlers/ package, reputation, combat_sides, encounters (time-of-day gate), inventory (transfer_items), loot, rule_brain (no deps)
llm/ — LLM client, prompt builders, tool schemas (OpenRouter)
storage/ — SaveStore interface, JsonFileStore
storage/ — SaveStore interface, JsonFileStore, versioned save schema (SaveGame, schema_version=1, world seed + RNG state в сейве)
content_loader/ — loads worlds, nations, settlements, NPCs, player from YAML; Pydantic content schemas, JSON Schema generation, entity CRUD, manifest resolver, library catalog, world assembly, catalog loader (monsters/items)
content/ — YAML world definitions (data, not code); library/ (reusable layer templates), worlds/ (manifest + optional custom layers)
frontend/ — React + TypeScript SPA (Vite, shadcn/ui, Zustand)
Expand Down Expand Up @@ -164,6 +164,8 @@ Kill reputation drop (`rules/reputation.py`): omniscient, delta scaled by victim
- Requires `.env` with `OPENROUTER_API_KEY` for LLM features (only if NPCs use `ai: llm`)
- `LLM_MODEL` env var selects model (required if `OPENROUTER_API_KEY` is set, no default)
- `DND_LANGUAGE` env var selects game language (default: `ru`); locale files in `src/dnd_simulator/locale/`
- `DND_WORLD_SEED` env var seeds world simulation layers; when absent, `GameService` logs the generated seed.
- `DND_AUTOSAVE_SECONDS` env var controls periodic autosave interval (default: `120`; must be greater than `0`).
- Save files: `saves/` directory (JSON)
- Backend API: `make serve` → http://localhost:8001/docs (Swagger UI)
- Frontend: `make frontend` → http://localhost:5173 (entry point, proxies /api to :8001)
3 changes: 3 additions & 0 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ services:
UV_CACHE_DIR: /tmp/uv-cache
volumes:
- ./tests/integration/content:/app/test-content
- ./saves:/app/saves
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8001/health')"]
interval: 2s
Expand All @@ -23,4 +24,6 @@ services:
condition: service_healthy
environment:
BACKEND_URL: http://backend:8001
volumes:
- ./saves:/app/saves
entrypoint: ["uv", "run", "pytest", "tests/integration/", "-v"]
20 changes: 14 additions & 6 deletions docs/BACKLOG.md

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ Paladin L1-L2 как первый caster-класс. Phase 1: spell slots как
Техспринт по результатам термоядерного ревью. Phase 1: save/load integrity (accessory modifiers, XP), visible bugs, deterministic handlers, i18n errors, pure action providers. Phase 2: typed query accessors, SquadInfo/LairInfo payloads, LayerSource/BrainType/EntityKind cleanup, `World.get_layer`, app-level exception handlers, unified player-status. Phase 3: backend decomposition — `combat_manager` lifecycle vs `combat_resolution`, `activation_manager` → encounters/materialization, ecology submodules, `AwarenessBuilder`, entity serialization split, backend equipment registry. Phase 4: frontend decomposition — `TargetDropdown`, `SchemaForm`, `EventLog`, `WorldOverview`, shared `PlayerStatus`, typed world-state rows, store/transport dedup. Audit closed with no blockers; deferred RNG threading filed for simulation-core determinism.
→ [план спринта](sprints/020-thermo-sweep/sprint.md)

### Sprint 021 — Save Schema & World Reproducibility (фазы 1-3)
Первый эпик цепочки simulation-core. Phase 1: единый `DND_WORLD_SEED` — слоевые сиды выводятся детерминированно в `game_service`, слои владеют своими `random.Random` (погода, политика, roam/retreat/деплит логова, encounter rolls), процесс-глобальный `random` из `layers/` убран, сквозной пин детерминизма (`test_world_seed.py`: один сид → идентичный `World.save()`). Phase 2: версионированная Pydantic-схема сейва — `SaveGame(schema_version=1)` в `storage/save_schema.py`, типизированные state-модели слоёв (`extra="forbid"`), entity-сейвы как discriminated union, построение напрямую из объектов, combat sides в сейве (закрыт lossless-пробел), состояние RNG (слоевые + dice) сериализуется и продолжает последовательности после load, legacy-форматы отклоняются. Phase 3: периодический автосейв (`DND_AUTOSAVE_SECONDS`, cancel до финального сейва), ошибки автосейва логируются вместо suppress, гвард на evict-после-DELETE (заодно закрыл воскрешение удалённой сессии), интеграционный стек чистит `saves/`. Закрыты backlog: `save-schema`, `layer-rng-threading`, `test-gap-world-rng-determinism`, `periodic-autosave-scheduler`, `silent-failure-autosave`.
→ [план спринта](sprints/021-save-schema/sprint.md)

## Planned

### Level 2 — Расходуемые ресурсы
Expand All @@ -134,7 +138,7 @@ Spell slots, ki, rage. Дополнительные типы брони и ор
→ [брейншторм](brainstorms/ecs-and-content.md)

### Simulation Core — намерения, триггеры, внутреннее я, лестница детализации
Заменяет прежний план «Phase 3 — Автономные тики» (периодические тики отброшены в пользу decision-точек). Цепочка эпиков: единая схема сейва → якорь-как-свойство + намерения (спит/идёт/ждёт, travel по рёбрам) → парные триггеры `{on, until}` активации/гашения → внутреннее я NPC (цели, отношения, живой alignment, переваривание + правиловый близнец) → лестница детализации поселений (событийная запись, храповик субъектности) → квесты как контент поверх целей и триггеров.
Заменяет прежний план «Phase 3 — Автономные тики» (периодические тики отброшены в пользу decision-точек). Первый эпик (единая схема сейва + воспроизводимость) закрыт Sprint 021. Цепочка эпиков: ~~единая схема сейва~~ → якорь-как-свойство + намерения (спит/идёт/ждёт, travel по рёбрам) → парные триггеры `{on, until}` активации/гашения → внутреннее я NPC (цели, отношения, живой alignment, переваривание + правиловый близнец) → лестница детализации поселений (событийная запись, храповик субъектности) → квесты как контент поверх целей и триггеров.
→ [брейншторм](brainstorms/simulation-core.md), эпики в [BACKLOG](BACKLOG.md#simulation-core-брейншторм-2026-07-04)

### World Builder (advanced)
Expand Down
7 changes: 5 additions & 2 deletions docs/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
Текущее состояние проекта. Один файл — быстрый ответ на "где мы сейчас".

**Last updated:** 2026-07-10
**Position:** Классовые механики и система уровней доведены до D&D L2 (Fighter / Rogue / Paladin; XP & leveling — Sprint 017). Sprint 018 закрыл логова, лут, встречи и время суток. Sprint 019 отвердел control-plane под будущий разрез на роли. Sprint 020 закрыл thermo-sweep: корректностные баги, чистота `rules/`, типизация границ, backend/frontend decomposition и сверка с новым [simulation-core](brainstorms/simulation-core.md).
**Next:** активного спринта нет. Следующий `/new-sprint` должен выбрать направление: цепочка simulation-core (`save-schema` → `anchor-as-property` / `intents` → `trigger-table` → ...), `control-interfaces`, либо точечный debt из свежего audit (`layer-rng-threading`, `test-gap-world-rng-determinism`). `quest-system` планировать только после триггеров и целей.
**Position:** Sprint 020 закрыл thermo-sweep (корректность, чистота `rules/`, типизация границ, decomposition). Sprint 021 закрыл первый эпик simulation-core: версионированная Pydantic-схема сейва (`SaveGame`, schema_version=1), воспроизводимость мира от `DND_WORLD_SEED` (слоевые RNG, их состояние в сейве), периодический автосейв. Классовые механики на уровне D&D L2 (Fighter / Rogue / Paladin).
**Next:** активного спринта нет. Следующий `/new-sprint`: продолжение цепочки simulation-core (`anchor-as-property` / `intents` + `travel-action-type` → `trigger-table` → ...), при этом `save-round-concurrency` из свежего audit — top-кандидат на включение (сейв гоняется с живым раунд-тредом без синхронизации). Альтернатива — `control-interfaces`. `quest-system` планировать только после триггеров и целей.
**Blockers:** нет.

## Current Sprint
Expand All @@ -13,6 +13,8 @@ No active sprint.

## Recent activity (non-sprint)

- 2026-07-10 — Sprint 021 save-schema закрыт: unit 2429, integration 160, два E2E-прогона, audit triaged (свежий риск `save-round-concurrency` в бэклоге), PR в main.

- 2026-07-10: перенесены ценные фичи из `sprint/020-control-interfaces`: disconnect grace-period закрыл `session-disconnect-debounce`, spectator-listener добавил read-only WS `?spectate=true` и live-вкладку в master session view.
- 2026-07-10 — Sprint 020 thermo-sweep закрыт: integration 154 passed, post-audit E2E smoke 5/5, audit triaged, PR opened to main.
- 2026-07-04 — брейншторм [simulation-core](brainstorms/simulation-core.md): консенсус-модель времени/активности/внутреннего я/лестницы детализации. VISION.md переписан, BACKLOG реструктурирован (секция Simulation Core, поглощённые/переформулированные айтемы, чекбоксы фаз 1-2 спринта 020), ROADMAP Planned обновлён, указатели-актуализации в старых брейнштормах.
Expand All @@ -23,6 +25,7 @@ No active sprint.

| Sprint | Goal | Started | Completed |
|--------|------|---------|-----------|
| 021-save-schema | Версионированная Pydantic-схема сейва (schema_version=1, RNG в сейве, combat sides), воспроизводимость мира от DND_WORLD_SEED, периодический автосейв | 2026-07-10 | 2026-07-10 |
| 020-thermo-sweep | Закрыть структурный долг из термоядерного ревью: корректность + чистота rules, типизация границ, backend/frontend decomposition, сверка с simulation-core | 2026-06-30 | 2026-07-10 |
| 019-control-plane-prep | Отвердить control-plane под разрез на роли: GameService 1044→357 (миксины WorldBuilderCommands/PlayerCommands), тест-сетка на session, развязка core/adapter (action_parsing seam, public World query API), видимые дырки (combat-log i18n, encounter-перцептор, труп-кнопки) | 2026-06-28 | 2026-06-29 |
| 018-lairs-encounters-loot | Логова (active→depleted), лут/контейнеры (`take`, `transfer_items`), региональные таблицы встреч, время суток; закрыт `monster-spawn` | 2026-06-28 | 2026-06-28 |
Expand Down
Loading
Loading