Agent Playtest

an LLM agent drives a running SimVX game, headless.

📄 Docs only

Tags: 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()