From 296e65ff955fd86c40c8dd89b9d62e4182a90916 Mon Sep 17 00:00:00 2001 From: empty Date: Tue, 30 Dec 2025 11:03:52 +0800 Subject: [PATCH] feat(engine): add faction system for social emergence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Factions model (optimists/fearful/neutral) - Implement classify_faction() based on stance thresholds - Add update_factions() to track faction distribution - Add apply_faction_influence() for faction→mood feedback - Integrate faction system into tick flow 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- engine-python/app/engine.py | 13 +++++++++++ engine-python/app/factions.py | 40 ++++++++++++++++++++++++++++++++ engine-python/app/models.py | 8 +++++++ engine-python/tests/test_step.py | 24 +++++++++++++++++++ 4 files changed, 85 insertions(+) create mode 100644 engine-python/app/factions.py diff --git a/engine-python/app/engine.py b/engine-python/app/engine.py index 46c771c..86b4bbc 100644 --- a/engine-python/app/engine.py +++ b/engine-python/app/engine.py @@ -7,6 +7,7 @@ from .models import ( from .global_events import GLOBAL_EVENT_POOL, GlobalEvent from .opinions import generate_opinions from .social_influence import apply_social_influence +from .factions import update_factions, apply_faction_influence MAX_EVENTS = 20 MAX_MEMORY_PER_AGENT = 3 @@ -90,6 +91,12 @@ def process_events(state: WorldState, events: List[Event]) -> WorldState: # 应用社交影响 apply_social_influence(state) + # 更新派系分布 + update_factions(state) + + # 派系影响世界情绪 + apply_faction_influence(state) + return state @@ -126,6 +133,12 @@ def check_and_trigger_global_event( # 应用社交影响 apply_social_influence(state) + # 更新派系分布 + update_factions(state) + + # 派系影响世界情绪 + apply_faction_influence(state) + result = GlobalEventResult( triggered=True, event=global_event.to_info() diff --git a/engine-python/app/factions.py b/engine-python/app/factions.py new file mode 100644 index 0000000..249aab0 --- /dev/null +++ b/engine-python/app/factions.py @@ -0,0 +1,40 @@ +"""派系系统 - 基于立场分类角色并影响世界""" +from typing import Dict +from .models import WorldState, AgentState, Factions + +# 派系分类阈值 +OPTIMIST_THRESHOLD = 0.6 +FEARFUL_THRESHOLD = 0.6 + + +def classify_faction(agent: AgentState) -> str: + """根据 stance 分类角色所属派系""" + if agent.stance.optimism > OPTIMIST_THRESHOLD: + return "optimists" + elif agent.stance.fear > FEARFUL_THRESHOLD: + return "fearful" + else: + return "neutral" + + +def update_factions(state: WorldState) -> None: + """统计各派系人数并更新 world_state""" + counts = {"optimists": 0, "fearful": 0, "neutral": 0} + + for agent in state.agents.values(): + faction = classify_faction(agent) + counts[faction] += 1 + + state.factions = Factions(**counts) + + +def apply_faction_influence(state: WorldState) -> None: + """派系分布影响世界情绪""" + optimists = state.factions.optimists + fearful = state.factions.fearful + + if optimists > fearful: + state.town_mood = min(10, state.town_mood + 1) + elif fearful > optimists: + state.town_mood = max(-10, state.town_mood - 1) + # 平局时不变化 diff --git a/engine-python/app/models.py b/engine-python/app/models.py index 35962ad..4b8ceff 100644 --- a/engine-python/app/models.py +++ b/engine-python/app/models.py @@ -44,6 +44,13 @@ class Stance(BaseModel): fear: float = Field(default=0.5, ge=0.0, le=1.0) +class Factions(BaseModel): + """派系分布""" + optimists: int = 0 + fearful: int = 0 + neutral: int = 0 + + class AgentState(BaseModel): emotion: Emotion = Emotion.CALM goal: str = "" @@ -63,6 +70,7 @@ class WorldState(BaseModel): events: List[str] = Field(default_factory=list) global_meter: GlobalMeter = Field(default_factory=GlobalMeter) world_effects: List[WorldEffect] = Field(default_factory=list) + factions: Factions = Field(default_factory=Factions) class Event(BaseModel): diff --git a/engine-python/tests/test_step.py b/engine-python/tests/test_step.py index 23daf76..82ead5f 100644 --- a/engine-python/tests/test_step.py +++ b/engine-python/tests/test_step.py @@ -295,3 +295,27 @@ def test_social_influence_deterministic(client): for aid in stances1: assert stances1[aid]["optimism"] == stances2[aid]["optimism"] assert stances1[aid]["fear"] == stances2[aid]["fear"] + + +def test_factions_exists_in_world_state(client): + """测试 world_state 包含 factions 字段""" + resp = client.post("/step", json={"events": []}) + data = resp.json() + + assert "factions" in data["world_state"] + factions = data["world_state"]["factions"] + assert "optimists" in factions + assert "fearful" in factions + assert "neutral" in factions + + +def test_factions_count_matches_agents(client): + """测试派系总数等于 agent 数量""" + resp = client.post("/step", json={"events": []}) + data = resp.json() + + factions = data["world_state"]["factions"] + total = factions["optimists"] + factions["fearful"] + factions["neutral"] + agent_count = len(data["world_state"]["agents"]) + + assert total == agent_count