From 484689735ffd6a9f9e896f9388614375278064a8 Mon Sep 17 00:00:00 2001 From: anadoris007 Date: Wed, 22 Apr 2026 15:01:08 +0530 Subject: [PATCH] feat(narrative): add GodModeView, WorldBuilderView, cinematic locations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New Vue views: GodModeView (inject event, modify emotions with preloaded current values, kill character with typed-name confirmation) and WorldBuilderView (rules editor, locations with cinematic atmosphere field, event log viewer) - Shared sim-nav strip across Story/GodMode/World views - Location schema extended with optional atmosphere field — a short mood phrase that anchors the opening visual of every scene set there - Translator's _format_world_locations surfaces atmosphere to the LLM prompt so the field actually influences generation - Routes added for /godmode/:simId and /world/:simId Tests: 39/39 passing (added test_location_atmosphere_surfaces_in_prompt). Frontend builds clean. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../narrative/narrative_translator.py | 16 +- backend/tests/test_narrative_translator.py | 30 +++ frontend/src/router/index.js | 14 ++ frontend/src/views/GodModeView.vue | 225 +++++++++++++++++ frontend/src/views/StoryTimelineView.vue | 24 ++ frontend/src/views/WorldBuilderView.vue | 235 ++++++++++++++++++ 6 files changed, 540 insertions(+), 4 deletions(-) create mode 100644 frontend/src/views/GodModeView.vue create mode 100644 frontend/src/views/WorldBuilderView.vue diff --git a/backend/app/services/narrative/narrative_translator.py b/backend/app/services/narrative/narrative_translator.py index 607c563d..085aaaaf 100644 --- a/backend/app/services/narrative/narrative_translator.py +++ b/backend/app/services/narrative/narrative_translator.py @@ -37,10 +37,18 @@ def _format_world_locations(world: dict) -> str: locs = list(world.get("locations", {}).values())[:5] if not locs: return "(none)" - return "\n ".join( - f"{_escape_braces(l.get('name', ''))} — {_escape_braces(l.get('description', ''))}" - for l in locs - ) + lines = [] + for l in locs: + name = _escape_braces(l.get("name", "")) + desc = _escape_braces(l.get("description", "")) + line = f"{name} — {desc}" + # Cinematic schema: if atmosphere is present, surface it to the LLM as + # a mood anchor for any scene set here. + atmosphere = l.get("atmosphere") + if atmosphere: + line += f" [atmosphere: {_escape_braces(atmosphere)}]" + lines.append(line) + return "\n ".join(lines) def read_actions_for_round( diff --git a/backend/tests/test_narrative_translator.py b/backend/tests/test_narrative_translator.py index ea361bd9..0a9d91bd 100644 --- a/backend/tests/test_narrative_translator.py +++ b/backend/tests/test_narrative_translator.py @@ -142,3 +142,33 @@ def test_generate_prose_includes_world_context(): assert "Magic is forbidden" in prompt assert "A stranger arrived" in prompt assert "The Tower" in prompt + + +def test_location_atmosphere_surfaces_in_prompt(): + """Cinematic location schema — atmosphere should reach the LLM.""" + actions = [{"agent_name": "Alice", "action_type": "CREATE_POST", "action_args": {}}] + characters = [ + {"id": "1", "name": "Alice", "status": "alive", + "emotional_state": {"current": {"anger": 0, "fear": 0, "joy": 0, + "sadness": 0, "trust": 0.5, "surprise": 0}}}, + ] + world = { + "rules": [], + "locations": { + "tower": { + "id": "tower", + "name": "The Iron Tower", + "description": "dark spire", + "atmosphere": "oppressive silence, dust in shafts of cold light", + } + }, + "event_log": [], + } + + with patch("app.services.narrative.narrative_translator.call_llm") as mock_llm: + mock_llm.return_value = "prose" + generate_prose(actions, characters, tone="noir", previous_beats=[], world=world) + + prompt = mock_llm.call_args[0][0] + assert "atmosphere" in prompt.lower() + assert "oppressive silence" in prompt diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 4cefb536..05ee36d5 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -6,6 +6,8 @@ import SimulationRunView from '../views/SimulationRunView.vue' import ReportView from '../views/ReportView.vue' import InteractionView from '../views/InteractionView.vue' import StoryTimelineView from '../views/StoryTimelineView.vue' +import GodModeView from '../views/GodModeView.vue' +import WorldBuilderView from '../views/WorldBuilderView.vue' const routes = [ { @@ -48,6 +50,18 @@ const routes = [ name: 'Story', component: StoryTimelineView, props: true + }, + { + path: '/godmode/:simulationId', + name: 'GodMode', + component: GodModeView, + props: true + }, + { + path: '/world/:simulationId', + name: 'World', + component: WorldBuilderView, + props: true } ] diff --git a/frontend/src/views/GodModeView.vue b/frontend/src/views/GodModeView.vue new file mode 100644 index 00000000..abb9a5b3 --- /dev/null +++ b/frontend/src/views/GodModeView.vue @@ -0,0 +1,225 @@ + + + + + diff --git a/frontend/src/views/StoryTimelineView.vue b/frontend/src/views/StoryTimelineView.vue index 8f080711..413f33d8 100644 --- a/frontend/src/views/StoryTimelineView.vue +++ b/frontend/src/views/StoryTimelineView.vue @@ -1,5 +1,11 @@