feat(engine): 实现行动点系统

- 扩展 AgentState 添加 action_points, max_action_points, last_action_tick 字段
- 新增 ActionFeedback 模型用于返回行动执行结果
- 创建 action_points.py 模块实现行动点消耗与恢复逻辑
- 行动消耗表: vote=1, trigger_skill=2, influence=2, comment/support/chaos=0
- 每 tick 恢复 1 点行动点(不超过 max)
- 行动点不足时拒绝执行并返回失败反馈
- 新增 7 个测试用例,全部 37 个测试通过

🤖 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:48:33 +08:00
parent 4664796d0b
commit 5ae63d9df9
6 changed files with 267 additions and 7 deletions

View File

@@ -541,3 +541,136 @@ def test_skill_requires_prerequisite(client):
assert skills["unity"]["requires"] == ["festival_boost"]
# 初始状态下 unity 未解锁
assert skills["unity"]["unlocked"] is False
# ==================== 行动点系统测试 ====================
def test_action_points_exists_in_agent(client):
"""测试 agent 包含行动点字段"""
resp = client.post("/step", json={"events": []})
data = resp.json()
for agent_id, agent in data["world_state"]["agents"].items():
assert "action_points" in agent
assert "max_action_points" in agent
assert "last_action_tick" in agent
assert agent["action_points"] >= 0
assert agent["max_action_points"] >= 1
def test_action_feedbacks_in_response(client):
"""测试响应包含 action_feedbacks 字段"""
resp = client.post("/step", json={"events": []})
data = resp.json()
assert "action_feedbacks" in data
assert isinstance(data["action_feedbacks"], list)
def test_vote_consumes_action_points(client):
"""测试投票消耗行动点"""
# alice 投票(消耗 1 点)
events = [
{"type": "vote", "faction": "optimists", "text": "", "user": "alice", "ts": 1}
]
resp = client.post("/step", json={"events": events})
data = resp.json()
# 验证行动反馈
assert len(data["action_feedbacks"]) == 1
feedback = data["action_feedbacks"][0]
assert feedback["success"] is True
assert feedback["user"] == "alice"
# 初始 3 点,消耗 1 点,恢复 1 点 = 3 点
# 但消耗在恢复之后,所以是 3 - 1 = 2
assert feedback["remaining_ap"] == 2
def test_insufficient_action_points_rejected(client):
"""测试行动点不足时拒绝执行"""
# 重置状态,设置 alice 行动点为 0last_action_tick 为当前 tick
# 这样在下一个 tick 恢复后只有 1 点
state = get_default_state()
state.tick = 10
state.agents["alice"].action_points = 0
state.agents["alice"].last_action_tick = 10 # 刚行动过,不会恢复
save_state(state)
# 尝试投票(需要 1 点),但恢复后有 1 点
# 为了测试不足,我们需要让 last_action_tick = tick这样不会恢复
# 但 regenerate 检查的是 tick - last_action_tick >= 1
# tick 会先 +1 变成 1111 - 10 = 1 >= 1所以会恢复
# 重新设计:设置 last_action_tick 为未来的 tick
state = get_default_state()
state.tick = 10
state.agents["alice"].action_points = 0
state.agents["alice"].last_action_tick = 11 # 未来 tick不会恢复
save_state(state)
events = [
{"type": "vote", "faction": "optimists", "text": "", "user": "alice", "ts": 10}
]
resp = client.post("/step", json={"events": events})
data = resp.json()
# 验证被拒绝
assert len(data["action_feedbacks"]) == 1
feedback = data["action_feedbacks"][0]
assert feedback["success"] is False
assert "insufficient" in feedback["reason"]
def test_action_points_regenerate_per_tick(client):
"""测试行动点每 tick 恢复"""
# 设置 alice 行动点为 1
state = get_default_state()
state.agents["alice"].action_points = 1
state.agents["alice"].last_action_tick = 0
save_state(state)
# 执行一个 tick无事件
resp = client.post("/step", json={"events": []})
data = resp.json()
# 行动点应该恢复到 21 + 1
assert data["world_state"]["agents"]["alice"]["action_points"] == 2
def test_action_points_not_exceed_max(client):
"""测试行动点不超过最大值"""
# 设置 alice 行动点为 3已满
state = get_default_state()
state.agents["alice"].action_points = 3
state.agents["alice"].max_action_points = 3
save_state(state)
# 执行多个 tick
for _ in range(3):
client.post("/step", json={"events": []})
resp = client.post("/step", json={"events": []})
data = resp.json()
# 行动点不应超过 max
assert data["world_state"]["agents"]["alice"]["action_points"] <= 3
def test_zero_cost_action_always_allowed(client):
"""测试 0 消耗的行动始终允许"""
# 设置 alice 行动点为 0
state = get_default_state()
state.agents["alice"].action_points = 0
save_state(state)
# comment 类型消耗 0 点
events = [
{"type": "comment", "text": "hello", "user": "alice", "ts": 1}
]
resp = client.post("/step", json={"events": events})
data = resp.json()
# 应该成功执行
assert len(data["action_feedbacks"]) == 1
feedback = data["action_feedbacks"][0]
assert feedback["success"] is True