Add Phase 12: native RPG world state

Structured world/player/NPC stats, two-way flags, and sticky milestones
per scenario (stat_schema). The AI proposes a per-turn delta; a Python
engine referees it (clamp to min/max, per-turn cap, cooldown, counters).
Band word-labels plus a fixed stat guide (descriptions + full ranges)
keep the model grounded. World State drawer + Insights delta report;
undo/retry roll it back via the Phase 11 snapshot pattern.

- migrations 26-28 (scenarios.stat_schema, adventures.world_state,
  actions.world_state_before); all nullable, additive, safe on existing rows
- migration 29 raises the default context budget 4096 -> 16384
  (custom values preserved)
- seeded demo scenario 04-rpg-world-state.json (Bandit Camp)
- 19 new tests (33 total pass)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parththakkar106
2026-07-21 14:47:29 +05:30
co-authored by Claude Opus 4.8
parent 64356414b1
commit 4dac445f90
18 changed files with 1509 additions and 5 deletions
+4
View File
@@ -94,6 +94,8 @@ def _matches(scenario: models.Scenario, data: dict) -> bool:
the write and avoid churning rows on every boot."""
if any(getattr(scenario, f) != data.get(f, "") for f in _SCALARS):
return False
if (scenario.stat_schema or None) != (data.get("stat_schema") or None):
return False
have_cards = sorted(_card_tuple(c, lambda o, f: getattr(o, f)) for c in scenario.story_cards)
want_cards = sorted(
_card_tuple(c, lambda o, f: o.get(f, ""))
@@ -133,6 +135,8 @@ def _update_scenario(db, scenario: models.Scenario, data: dict) -> None:
def _apply_scalars(scenario: models.Scenario, data: dict) -> None:
for field in _SCALARS:
setattr(scenario, field, data.get(field, ""))
# Phase 12: RPG world-state template (a JSON dict, not a scalar string).
scenario.stat_schema = data.get("stat_schema") or None
def _populate_children(db, scenario: models.Scenario, data: dict) -> None: