diff --git a/backend/app/services/narrative/god_mode.py b/backend/app/services/narrative/god_mode.py new file mode 100644 index 00000000..d1409855 --- /dev/null +++ b/backend/app/services/narrative/god_mode.py @@ -0,0 +1,28 @@ +"""God Mode intervention handlers. + +All handlers mutate on-disk JSON under narrative/ and return the result. +Each intervention is logged to world_state.event_log for auditability. +""" +from typing import Optional + +from app.services.narrative.story_store import StoryStore +from app.services.narrative.world_state import WorldStateStore + + +def _current_round(sim_dir: str) -> int: + """Return the 'current' round: last translated beat's round + 1, or 1.""" + beats = StoryStore(sim_dir).get_all_beats() + if not beats: + return 1 + return beats[-1].get("round", 0) + 1 + + +def inject_event(sim_dir: str, description: str, round_num: Optional[int] = None) -> dict: + """Append a user-described event to the world event log.""" + world = WorldStateStore(sim_dir) + round_val = round_num if round_num is not None else _current_round(sim_dir) + return world.append_event({ + "type": "god_mode_injection", + "description": description, + "round": round_val, + }) diff --git a/backend/tests/test_god_mode.py b/backend/tests/test_god_mode.py new file mode 100644 index 00000000..5c2fd367 --- /dev/null +++ b/backend/tests/test_god_mode.py @@ -0,0 +1,38 @@ +import os +import tempfile +import pytest +from app.services.narrative.god_mode import inject_event +from app.services.narrative.story_store import StoryStore +from app.services.narrative.world_state import WorldStateStore + + +@pytest.fixture +def temp_sim_dir(): + with tempfile.TemporaryDirectory() as d: + sim_dir = os.path.join(d, "sim_test") + os.makedirs(sim_dir) + yield sim_dir + + +def test_inject_event_appends_to_log(temp_sim_dir): + evt = inject_event(temp_sim_dir, description="A storm arrives.", round_num=5) + assert evt["description"] == "A storm arrives." + assert evt["round"] == 5 + assert evt["id"] == "evt_1" + assert evt["type"] == "god_mode_injection" + + log = WorldStateStore(temp_sim_dir).load()["event_log"] + assert len(log) == 1 + + +def test_inject_event_defaults_round_to_beats_plus_one(temp_sim_dir): + store = StoryStore(temp_sim_dir) + store.append_beat({"round": 3, "prose": "beat 3"}) + + evt = inject_event(temp_sim_dir, description="auto round") + assert evt["round"] == 4 + + +def test_inject_event_defaults_round_to_one_when_no_beats(temp_sim_dir): + evt = inject_event(temp_sim_dir, description="first event") + assert evt["round"] == 1