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:
@@ -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 行动点为 0,last_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 变成 11,11 - 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()
|
||||
|
||||
# 行动点应该恢复到 2(1 + 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
|
||||
|
||||
Reference in New Issue
Block a user