Engine Evaluation — LangGraph Game Surface Rendering Layer

Status: ACTIVE (research synthesis) Agent: opencode/ext-agent (sandshrew) Timestamp UTC: 2026-05-11T19:20:00Z Claim: synthesis | 2026-05-11T19:15:00Z Session: MjF engine evaluation request — pygame-based RTS/strategy engines for RG40XXV rendering layer

Prior Context

Engine Survey

Wargame Engine — Best Fit

Repo: maximinus/wargame Stack: Python 100%, Pygame Status: Active (52 commits, last updated Jul 2024, 27 stars) License: GPL-3.0 Pip installable: Yes

Architectural alignment with LangGraph:

Wargame Feature LangGraph Mapping
Node-tree viewport LangGraph nodes = viewport nodes. Each graph node can render as a viewport node
Scene system Discrete game phases (planning, execution, resolution). Switch scenes per phase
Messaging system Decoupled state update propagation. Engine sends "clicked hex_17" message, LangGraph processes, state updates push back
Save/load/replay Complements LangGraph checkpointer. Engine handles file serialization, LangGraph handles state persistence
GUI system (built-in) interrupt() prompts rendered as GUI dialogs. Intel suggestions as sidebar/overlay
Tween animations Unit movement animations between nodes, state transition visual feedback
Terminal (runtime REPL) Debug interface for inspecting LangGraph state during development
Automatic config loading Unit configs, node access lists loaded from config files
Logging system Trace LangGraph node execution alongside engine events
Board wargame domain Turn-based, tile/hex-based, tactical — exactly the game paradigm

Key quote from README:

"A tree of nodes is used as a viewport, with each node usually representing either a view of sprites, or the sprites themselves."

This is the engine explicitly designed around a node-tree viewport. The LangGraph graph IS a node tree. The mapping is architectural, not metaphorical.

Example integration sketch:

# LangGraph owns state, Wargame Engine owns the view
def game_loop():
    controller = wargame.engine.init(os.cwd())

    # Build scene from LangGraph state
    scene = build_scene_from_langgraph_state(graph.get_state(config))
    controller.add_scene('game', scene)

    # Override input handler to fire LangGraph invocations
    controller.on_click = lambda pos: graph.invoke({
        "action": "move", 
        "target": screen_to_hex(pos)
    }, config)

    # Post-invoke: rebuild scene from updated state
    controller.on_post_update = lambda: rebuild_scene_from_state(...)

    controller.run('game')

Risk: Opinionated engine — owns the main loop. If LangGraph's invoke/stream pattern conflicts with the engine's internal loop, integration may require fighting the engine's assumptions. The "not a library" design means less flexibility. Mitigation: the messaging system is designed for decoupling — the engine may be more hackable than it appears.


Sequtus — RTS Pattern Reference

Repo: Teifion/sequtus Stack: Python 100%, Pygame Status: Dead (391 commits, last updated May 2012, 24 stars) Note: Python 2.6 — would need porting to Python 3

Value: Reference implementation, not an engine to build on. Has well-structured separation between engine/ and game/ directories. Demonstrates: - Unit selection (single, shift, drag, double-click, control groups) - Order queues and build queues - Minimap with click-to-scroll - Collision detection - Teams, unit life/death, tech trees - HP bars and animated units

Not suitable as foundation due to Python 2 and 14-year staleness. But the engine/ directory (~15 files) is worth studying for RTS patterns in Pygame: selection logic, minimap rendering, scrolling, order queuing.


OpenRTS — Historical Reference Only

Location: pygame.org project #189, SourceForge (arrakis.sf.net) Status: Abandoned since 2006 Stack: Python + Pygame Features: Isometric graphics, networked multiplayer, customizable rulesets and tilesets

Value: The tile/ruleset customization concept is conceptually relevant — a ruleset is essentially a LangGraph node config. But the code is 20 years old, Python 2-era, and dead. Reference only for isometric tile rendering patterns.


Fabula — Not Found

Search across GitHub and PyPI returned no results. The user's description (client-server architecture, event-based protocols, pygame graphical editor) sounds promising but the project could not be located. Likely a niche/private project, renamed, or described from memory with an incorrect name. Skip unless a specific URL surfaces.


Godot — Overkill, Wrong Platform

GDScript (Python-like) with dedicated IDE. Extremely capable but: - Not Python — would need to bridge GDScript ↔ LangGraph's Python runtime - Runs its own editor, its own scene tree, its own everything — would fight LangGraph, not complement it - Heavy for RG40XXV - The effort-to-fit ratio is poor for a 10-unit prototype


Panda3D — Wrong Domain

3D engine with Python scripting. Overkill for a 2D hex/tile strategy game. Wrong fit.


OpenRA — Wrong Runtime

C# (79%), Lua (16%). Cannot integrate with Python LangGraph. Map format and trait system are conceptually borrowable but the engine itself is unusable.


Recommendation

Wargame Engine is the best-fit rendering layer. Its node-tree viewport, scene system, messaging architecture, and board wargame domain align with the LangGraph graph model at an architectural level, not just a metaphorical one. The engine is small (52 commits), Python/pygame, pip-installable, and maintained as of 2024. It runs on any platform pygame supports (including ARM Linux on RG40XXV).

Fallback: If Wargame Engine proves too opinionated (owns the game loop, resists LangGraph's invoke-driven state model), fall back to raw pygame or Python Arcade. The cost is having to rebuild the node-tree viewport, scene management, and GUI system from scratch — but the prototype scope (10 units, turn-based) makes this tractable.

Sequtus is worth a skim for its Pygame RTS patterns (selection, orders, minimap) but not as a foundation.

Integration Risk: The Main Loop Collision

The critical integration question is whether Wargame Engine's main loop can accommodate LangGraph's invoke-driven state model:

Observed resolution paths: 1. Interrupt-driven: LangGraph calls interrupt() → engine shows prompt → human clicks → Command(resume=...) → LangGraph continues → engine re-renders. This maps naturally — the engine's event loop IS the interrupt handler. 2. Polling: LangGraph state is queried each frame. Engine renders whatever state exists. On click, invoke() runs synchronously (blocking). For turn-based with 10 units, the blocking time is negligible. 3. Frame-skip: If invoke() is slow (LLM call inside node), engine shows a "processing..." overlay or loading screen via Wargame's GUI system.

The messaging system is the bridge: engine sends "input event" messages → LangGraph node receives → LangGraph returns state update → engine rebuilds view from new state. This is the cleanest decoupling pattern and Wargame Engine was designed for it.

Open Questions


Addendum: Fabula — Found and Evaluated

Source: pygame.org/project-fabula-1746-3160.html Author: Florian Berger (fberger) Last release: 0.8.3, June 2012 (14 years dead) Stack: Python 3, Pygame, clickndrag library Distribution: Tarballs from personal static host (no GitHub, no PyPI)

Architectural Fit

Fabula's architecture is the closest conceptual match to the LangGraph game surface model of any engine surveyed:

Fabula Feature LangGraph Mapping
Client-server architecture LangGraph = server (state/rules), pygame on RG40XXV = client (rendering)
Event-based protocol Client emits {"action": "move", "target": "hex_17"} → LangGraph node receives → state update returns → client re-renders
Visualization-agnostic world model LangGraph owns world state independent of renderer. Same state powers any client (RG40XXV, Wii canvas, PS2 homebrew)
Plugin system for game rules/entities New LangGraph nodes as plugins that extend graph topology
Recording and playback Complements LangGraph checkpointer for time-travel and review
Multi-threaded server LangGraph's concurrent Send fan-out for parallel unit execution
Pygame-based graphical editor Visual graph editor for placing/configuring nodes

Relevance (Despite Dead Code)

Fabula's code is not usable — 14 years of Python 3 drift, tied to a dead clickndrag library, distributed as personal tarballs. But its architecture documentation and design intent serve as a blueprint for how to separate LangGraph (server/state/rules) from the rendering layer (client/input/display).

The central insight: treat LangGraph nodes as event handlers in a client-server protocol. The renderer knows nothing about game rules — it only knows how to draw state and emit events. LangGraph knows nothing about rendering — it only knows how to process events and return state updates.

Synthesis: Wargame Engine + Fabula Pattern

Layer What to Use Source
Rendering Wargame Engine's node-tree viewport, scene system, GUI Wargame Engine (alive, 2024)
Integration pattern Client-server event protocol, visualization-agnostic world model Fabula (dead, 2012, conceptual only)
Save/load Both — Wargame handles visual state serialization, LangGraph checkpointer handles game state Combined
Input handling Wargame's input system → serialized as events → dispatched to LangGraph Wargame for capture, Fabula pattern for dispatch

Updated Ranking

Engine Code Viability Conceptual Fit What to Steal
Wargame Engine Alive (2024) Node-tree matches graph Rendering code, GUI, scene system
Fabula Dead (2012) Client-server matches exactly Architecture pattern, event protocol design
Sequtus Dead (2012) Low RTS patterns (selection, orders)
OpenRTS Dead (2006) Low Isometric tile concepts
Godot Alive Wrong runtime (GDScript, not Python)
Panda3D Alive Wrong domain (3D)
OpenRA Alive (C#) Wrong runtime, design patterns borrowable Trait system mental model, map format concepts