feat(narrative): add god_mode.modify_emotion with audit logging

This commit is contained in:
anadoris007 2026-04-20 22:24:54 +05:30
parent f91dd00f37
commit f25f68837c
2 changed files with 79 additions and 0 deletions

View File

@ -26,3 +26,36 @@ def inject_event(sim_dir: str, description: str, round_num: Optional[int] = None
"description": description,
"round": round_val,
})
def modify_emotion(sim_dir: str, character_id: str, emotions: dict) -> dict:
"""Overwrite specified emotion values for a character. Clamps to [0, 1].
Raises ValueError if character_id is not found. Unknown emotion keys are
silently ignored (they don't corrupt state; they just don't apply).
Logs the intervention to world_state.event_log for auditability.
"""
store = StoryStore(sim_dir)
characters = store.load_characters()
target = next((c for c in characters if str(c.get("id")) == str(character_id)), None)
if target is None:
raise ValueError(f"character not found: {character_id}")
current = target["emotional_state"]["current"]
changed = {}
for emo, val in emotions.items():
if emo in current:
new_val = max(0.0, min(1.0, float(val)))
changed[emo] = {"from": current[emo], "to": new_val}
current[emo] = new_val
store.save_characters(characters)
WorldStateStore(sim_dir).append_event({
"type": "god_mode_emotion_change",
"description": f"{target['name']} emotional state modified: {changed}",
"round": _current_round(sim_dir),
})
return target

View File

@ -36,3 +36,49 @@ def test_inject_event_defaults_round_to_beats_plus_one(temp_sim_dir):
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
# ---- modify_emotion ----
from app.services.narrative.god_mode import modify_emotion
def _seed_character(sim_dir, char_id="1", name="Elena"):
store = StoryStore(sim_dir)
neutral = {k: 0.0 for k in ["anger", "fear", "joy", "sadness", "surprise"]}
store.save_characters([{
"id": char_id, "name": name, "status": "alive",
"emotional_state": {"current": {**neutral, "trust": 0.5}, "history": []},
}])
return store
def test_modify_emotion_overwrites_specified_emotions(temp_sim_dir):
_seed_character(temp_sim_dir)
result = modify_emotion(temp_sim_dir, "1", {"anger": 0.8, "joy": 0.2})
assert result["emotional_state"]["current"]["anger"] == 0.8
assert result["emotional_state"]["current"]["joy"] == 0.2
assert result["emotional_state"]["current"]["trust"] == 0.5
def test_modify_emotion_clamps(temp_sim_dir):
_seed_character(temp_sim_dir)
result = modify_emotion(temp_sim_dir, "1", {"anger": 1.5, "fear": -0.3})
assert result["emotional_state"]["current"]["anger"] == 1.0
assert result["emotional_state"]["current"]["fear"] == 0.0
def test_modify_emotion_character_not_found_raises(temp_sim_dir):
_seed_character(temp_sim_dir)
with pytest.raises(ValueError, match="not found"):
modify_emotion(temp_sim_dir, "nonexistent", {"anger": 0.5})
def test_modify_emotion_audit_logs_to_event_log(temp_sim_dir):
_seed_character(temp_sim_dir)
modify_emotion(temp_sim_dir, "1", {"anger": 0.8})
log = WorldStateStore(temp_sim_dir).load()["event_log"]
assert len(log) == 1
assert log[0]["type"] == "god_mode_emotion_change"
assert "Elena" in log[0]["description"]