From 2eb98a00bd73ebbcedec84638a4fdbc0cecc7396 Mon Sep 17 00:00:00 2001 From: anadoris007 Date: Wed, 22 Apr 2026 14:56:36 +0530 Subject: [PATCH] feat(narrative): add world + god mode API endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six new endpoints under /api/narrative/*: - GET /world/ — full world state - POST /world//rules — replace rules list - POST /world//locations — upsert location - POST /godmode//inject-event — inject world event - POST /godmode//modify-emotion — overwrite emotions - POST /godmode//kill — kill character Validation: - 400 on missing required fields (description, character_id, rules) - 400 on invalid round (must be non-negative int or null) - 404 on character not found (from ValueError in handlers) Smoke-tested via Flask test client — all 11 response codes correct. Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/app/api/narrative.py | 98 ++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/backend/app/api/narrative.py b/backend/app/api/narrative.py index cf316231..f7baa66b 100644 --- a/backend/app/api/narrative.py +++ b/backend/app/api/narrative.py @@ -12,6 +12,12 @@ from ..config import Config from ..services.narrative.story_store import StoryStore from ..services.narrative.narrative_translator import translate_round from ..services.narrative.character_engine import CharacterEngine +from ..services.narrative.world_state import WorldStateStore +from ..services.narrative.god_mode import ( + inject_event as gm_inject_event, + modify_emotion as gm_modify_emotion, + kill_character as gm_kill_character, +) def _sim_dir(sim_id: str) -> str: @@ -80,3 +86,95 @@ def initialize_characters(sim_id): engine = CharacterEngine(store) characters = engine.initialize_from_profiles(profiles) return jsonify({"count": len(characters), "characters": characters}) + + +# --------------------------------------------------------------------------- +# World State endpoints +# --------------------------------------------------------------------------- + +@narrative_bp.route('/world/', methods=['GET']) +def get_world(sim_id): + """Return the full world_state.json for this simulation.""" + store = WorldStateStore(_sim_dir(sim_id)) + return jsonify(store.load()) + + +@narrative_bp.route('/world//rules', methods=['POST']) +def set_rules(sim_id): + """Replace the full rules list. Body: {"rules": [str, ...]}.""" + data = request.get_json() or {} + rules = data.get('rules') + if not isinstance(rules, list): + return jsonify({"error": "rules must be a list of strings"}), 400 + store = WorldStateStore(_sim_dir(sim_id)) + store.set_rules([str(r) for r in rules]) + return jsonify(store.load()) + + +@narrative_bp.route('/world//locations', methods=['POST']) +def upsert_location(sim_id): + """Insert or update a location. Body: {"id", "name", "description"}.""" + data = request.get_json() or {} + if not data.get('id') or not data.get('name'): + return jsonify({"error": "id and name are required"}), 400 + store = WorldStateStore(_sim_dir(sim_id)) + loc = store.upsert_location({ + "id": str(data['id']), + "name": str(data['name']), + "description": str(data.get('description', '')), + }) + return jsonify(loc) + + +# --------------------------------------------------------------------------- +# God Mode endpoints +# --------------------------------------------------------------------------- + +@narrative_bp.route('/godmode//inject-event', methods=['POST']) +def godmode_inject_event(sim_id): + """Inject a world event. Body: {"description", "round"? (optional, >=0)}.""" + data = request.get_json() or {} + description = data.get('description') + if not description: + return jsonify({"error": "description is required"}), 400 + + round_num = data.get('round') + if round_num is not None: + try: + round_num = int(round_num) + if round_num < 0: + raise ValueError() + except (TypeError, ValueError): + return jsonify({"error": "round must be a non-negative integer"}), 400 + + evt = gm_inject_event(_sim_dir(sim_id), description=str(description), round_num=round_num) + return jsonify(evt) + + +@narrative_bp.route('/godmode//modify-emotion', methods=['POST']) +def godmode_modify_emotion(sim_id): + """Overwrite emotion values. Body: {"character_id", "emotions": {name: float}}.""" + data = request.get_json() or {} + char_id = data.get('character_id') + emotions = data.get('emotions') + if not char_id or not isinstance(emotions, dict): + return jsonify({"error": "character_id and emotions are required"}), 400 + try: + char = gm_modify_emotion(_sim_dir(sim_id), str(char_id), emotions) + except ValueError as e: + return jsonify({"error": str(e)}), 404 + return jsonify(char) + + +@narrative_bp.route('/godmode//kill', methods=['POST']) +def godmode_kill(sim_id): + """Mark a character as dead. Body: {"character_id"}.""" + data = request.get_json() or {} + char_id = data.get('character_id') + if not char_id: + return jsonify({"error": "character_id is required"}), 400 + try: + char = gm_kill_character(_sim_dir(sim_id), str(char_id)) + except ValueError as e: + return jsonify({"error": str(e)}), 404 + return jsonify(char)