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)