Agent Playtest¶
an LLM agent drives a running SimVX game, headless.
📄 Docs onlyTags: ai
Run it two ways:
Real LLM loop against your OpenWebUI / vLLM / llama.cpp endpoint:¶
SIMVX_LLM_BASE_URL=http://host:8000/v1 SIMVX_LLM_MODEL=your-model SIMVX_LLM_API_KEY=sk-… uv run python examples/features/ai/agent_playtest.py
Offline (no endpoint): runs a scripted walk-through of the session verbs.¶
uv run python examples/features/ai/agent_playtest.py
The game is a trivial “reach 5 points” toy so the loop is easy to follow; the session verbs (observe / send_input / set_state / step, terminated vs truncated) are identical for any real game.
Source¶
1"""Agent Playtest: an LLM agent drives a running SimVX game, headless.
2
3Run it two ways:
4
5 # Real LLM loop against your OpenWebUI / vLLM / llama.cpp endpoint:
6 SIMVX_LLM_BASE_URL=http://host:8000/v1 SIMVX_LLM_MODEL=your-model \
7 SIMVX_LLM_API_KEY=sk-... uv run python examples/features/ai/agent_playtest.py
8
9 # Offline (no endpoint): runs a scripted walk-through of the session verbs.
10 uv run python examples/features/ai/agent_playtest.py
11
12The game is a trivial "reach 5 points" toy so the loop is easy to follow; the
13session verbs (observe / send_input / set_state / step, terminated vs
14truncated) are identical for any real game.
15
16# /// simvx
17# web = { disabled = true, reason = "requires a local or remote LLM endpoint; not available in the browser runtime" }
18# ///
19"""
20
21from __future__ import annotations
22
23import asyncio
24import os
25
26from simvx.ai import AgentSession, OpenAICompatibleClient, dispatch, run_agent
27from simvx.core import Input, Node2D, Property
28from simvx.core.input import Key
29
30
31class Scorer(Node2D):
32 score = Property(0)
33
34 def on_update(self, dt):
35 # Holding SPACE scores a point per frame (a stand-in for real gameplay).
36 if Input.is_key_pressed(Key.SPACE):
37 self.score += 1
38
39
40class Toy(Node2D):
41 def on_ready(self):
42 self.add_child(Scorer(name="Scorer"))
43
44
45def _won(root) -> str | None:
46 scorer = next((n for n in root.walk(include_self=True) if isinstance(n, Scorer)), None)
47 return "win" if scorer and scorer.score >= 5 else None
48
49
50def _make_session() -> AgentSession:
51 return AgentSession(Toy(name="Toy"), terminal_fn=_won)
52
53
54async def _real_run() -> None:
55 client = OpenAICompatibleClient.from_env()
56 session = _make_session()
57 print(f"Driving model {client.model!r} at {client.base_url} ...\n")
58 result = await run_agent(
59 session,
60 client,
61 goal="Make the Scorer reach 5 points, then report how you did it.",
62 max_turns=15,
63 )
64 print("\n=== AGENT REPORT ===")
65 print(result.final_text)
66 print(
67 f"\nturns={result.turns} tool_calls={result.tool_calls} "
68 f"terminated={result.terminated} reason={result.reason!r}"
69 )
70
71
72def _scripted_run() -> None:
73 """No LLM configured: drive the same tools by hand to show the surface."""
74 session = _make_session()
75 print("No SIMVX_LLM_BASE_URL set -- running a scripted tool walk-through.\n")
76 print(dispatch(session, "observe", {"kind": "describe"})["tree"], "\n")
77
78 print(dispatch(session, "send_input", {"kind": "key", "key": "space", "mode": "down"}))
79 for _ in range(6):
80 out = dispatch(session, "step", {"frames": 1})
81 score = dispatch(session, "observe", {"kind": "node", "path": "Scorer"})["properties"]["score"]
82 print(f" frame={out['result']['frame']} score={score} terminated={out['terminated']}")
83 if out["terminated"]:
84 print(f"\nReached terminal state: {out['reason']!r}")
85 break
86
87
88def main() -> None:
89 if os.environ.get("SIMVX_LLM_BASE_URL"):
90 asyncio.run(_real_run())
91 else:
92 _scripted_run()
93
94
95if __name__ == "__main__":
96 main()