feat(engine): 实现阵营投票系统

- 新增 Votes 模型,包含 optimists/fearful 计数和 voted_users 去重列表
- 扩展 Event 模型,添加可选 faction 字段支持投票事件
- 新增 voting.py 模块处理投票逻辑
- 投票规则:每用户每 tick 限投一次,下一 tick 生效
- 投票累加到 factions.power,达到 threshold 触发阵营技能
- 添加 5 个投票系统测试用例,全部 26 个测试通过

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2025-12-30 13:12:22 +08:00
parent 87007d9b43
commit 75e84f2ba3
4 changed files with 163 additions and 0 deletions

View File

@@ -381,3 +381,103 @@ def test_story_event_in_response(client):
assert "story_event" in data
assert "triggered" in data["story_event"]
assert "arc_id" in data["story_event"]
# ==================== 投票系统测试 ====================
def test_votes_exists_in_world_state(client):
"""测试 world_state 包含 votes 字段"""
resp = client.post("/step", json={"events": []})
data = resp.json()
assert "votes" in data["world_state"]
votes = data["world_state"]["votes"]
assert "optimists" in votes
assert "fearful" in votes
assert "voted_users" in votes
def test_vote_records_correctly(client):
"""测试投票正确记录"""
events = [
{"type": "vote", "faction": "optimists", "text": "", "user": "user1", "ts": 1},
{"type": "vote", "faction": "fearful", "text": "", "user": "user2", "ts": 2},
]
resp = client.post("/step", json={"events": events})
data = resp.json()
votes = data["world_state"]["votes"]
assert votes["optimists"] == 1
assert votes["fearful"] == 1
assert "user1" in votes["voted_users"]
assert "user2" in votes["voted_users"]
def test_vote_user_deduplication(client):
"""测试同一用户在同一 tick 只能投一次票"""
events = [
{"type": "vote", "faction": "optimists", "text": "", "user": "user1", "ts": 1},
{"type": "vote", "faction": "optimists", "text": "", "user": "user1", "ts": 2},
{"type": "vote", "faction": "fearful", "text": "", "user": "user1", "ts": 3},
]
resp = client.post("/step", json={"events": events})
data = resp.json()
votes = data["world_state"]["votes"]
# 只计算第一次投票
assert votes["optimists"] == 1
assert votes["fearful"] == 0
def test_votes_apply_to_faction_power_next_tick(client):
"""测试投票在下一个 tick 累加到阵营能量"""
# 第一个 tick投票
events = [
{"type": "vote", "faction": "optimists", "text": "", "user": "user1", "ts": 1},
{"type": "vote", "faction": "optimists", "text": "", "user": "user2", "ts": 2},
{"type": "vote", "faction": "fearful", "text": "", "user": "user3", "ts": 3},
]
resp1 = client.post("/step", json={"events": events})
data1 = resp1.json()
# 记录第一个 tick 后的阵营能量
power1_opt = data1["world_state"]["factions"]["optimists"]["power"]
power1_fear = data1["world_state"]["factions"]["fearful"]["power"]
# 第二个 tick投票应该生效
resp2 = client.post("/step", json={"events": []})
data2 = resp2.json()
power2_opt = data2["world_state"]["factions"]["optimists"]["power"]
power2_fear = data2["world_state"]["factions"]["fearful"]["power"]
# 验证投票已累加(+2 optimists, +1 fearful
assert power2_opt >= power1_opt + 2
assert power2_fear >= power1_fear + 1
# 验证投票已清空
assert data2["world_state"]["votes"]["optimists"] == 0
assert data2["world_state"]["votes"]["fearful"] == 0
def test_votes_trigger_faction_event(client):
"""测试投票累积触发阵营技能"""
# 多轮投票累积能量直到触发
for i in range(5):
events = [
{"type": "vote", "faction": "optimists", "text": "", "user": f"user_{i}_a", "ts": i},
{"type": "vote", "faction": "optimists", "text": "", "user": f"user_{i}_b", "ts": i + 0.1},
]
resp = client.post("/step", json={"events": events})
# 再投一轮让投票生效
resp = client.post("/step", json={"events": []})
data = resp.json()
# 验证阵营能量已累积(可能已触发并重置)
# 如果触发了power 会重置为 0
factions = data["world_state"]["factions"]
triggered = data["triggered_faction_event"]["type"] is not None
# 要么触发了事件,要么能量在累积
assert triggered or factions["optimists"]["power"] > 0