A newer version of the Gradio SDK is available: 6.20.0
WORLD_SIMULATOR Architecture Inventory
Документ фиксирует фактическую архитектуру репозитория на момент инвентаризации и целевой MVP-слой для более умных LLM-жителей. Current означает подтверждено кодом. Target означает предлагаемую следующую архитектуру. Gap означает отсутствующий слой или правило. Assumption означает вывод, который не хранится явно в коде и нужен только для дальнейшего планирования.
1. Project Overview
Current: WORLD_SIMULATOR - Python-first AI civilization sandbox с 3D/изометрическим клиентом и "консолью бога". MVP сейчас умеет загружать JSON-конфиг, создавать детерминированный мир, спавнить NPC, отдавать состояние через HTTP API, рендерить карту и персонажей в React Three Fiber, выполнять ручные/автоматические тики и отправлять God Console команды в OpenAI-compatible LLM.
| Area | Current Implementation |
|---|---|
| Цель игры | Симулятор общества, где игрок задает события через God Console, а мир хранит canonical state. |
| Реализовано | Terrain, NPC spawn, health/damage/personality/directive/memory, walk/talk/attack, REST API, 3D UI, model configs, Modal deployment scripts. |
| MVP | Маленький in-memory world loop: каждый живой NPC получает LLM/tool или deterministic directive, backend исполняет directive и обновляет WorldState. |
| Визуал | frontend/src/scene/*, frontend/src/components/*, src/world_simulator/rendering/scene_contract.py. |
| Симуляция | src/world_simulator/simulation/spawning.py, tick.py, mechanics.py, memory.py, connectors/*. |
| LLM | NPC connector: simulation/connectors/openai_compatible.py; God Console: simulation/god_console.py; configs: config/*.json. |
Gap: это еще не полноценная persistent society simulation. Нет world event store, factions, relationships, active story arcs, threat state machine или harness-style debug trace.
2. Current Repository Structure
Current: дерево проекта на 2-4 уровня, без .git, .venv, node_modules, cache-директорий:
config/
game.llm.local.json
game.local.json
game.modal.local.json
deployments/
modal_nemotron_nano*.py
modal_qwen36_27b_h100*.py
smoke_modal_endpoint.py
frontend/
index.html
src/
api.ts
App.tsx
main.tsx
styles.css
types.ts
components/
hooks/
scene/
logs/
api.err.log
api.out.log
vite.err.log
vite.out.log
src/
world_simulator/
__main__.py
config.py
domain.py
agents/
api/
rendering/
simulation/
tests/
e2e/world-simulator-scene.spec.ts
test_api_server.py
test_scene_contract.py
test_config.py
test_god_console.py
test_openai_connector.py
test_world_spawning.py
package.json
playwright.config.ts
pyproject.toml
README.md
tsconfig.json
tsconfig.node.json
vite.config.ts
uv.lock
| Path | Назначение | Ключевые файлы | Почему важно |
|---|---|---|---|
src/world_simulator |
Python backend package | __main__.py, domain.py, config.py |
Runtime entrypoint, canonical dataclasses, config parsing. |
src/world_simulator/api |
HTTP API | server.py |
ThreadingHTTPServer, endpoints, tick/god-command locks, Modal warmup. |
src/world_simulator/simulation |
Core simulation | tick.py, mechanics.py, memory.py, spawning.py |
Tick planning/execution, range rules, NPC spawn, memory compaction. |
src/world_simulator/simulation/connectors |
NPC decision providers | base.py, deterministic.py, openai_compatible.py, factory.py |
Action schema, LLM calls, deterministic fallback. |
src/world_simulator/rendering |
Backend-to-frontend snapshot | scene_contract.py |
Converts canonical state into UI-friendly schema_version: 1 payload. |
src/world_simulator/agents |
Early agent abstraction | base.py, scripted.py |
Currently not wired into main loop; useful future harness hook. |
frontend |
React/Vite client | App.tsx, api.ts, hooks/useWorldSimulation.ts, scene/* |
UI, polling, controls, God Console, R3F scene. |
config |
Runtime profiles | game.local.json, game.llm.local.json, game.modal*.json |
World/server/model parameters. |
deployments |
Modal vLLM deployments | modal_nemotron_nano*.py, modal_qwen36_27b_h100.py |
OpenAI-compatible model servers for NPC/God Console. |
tests |
Quality gates | pytest tests, Playwright spec | Verifies config, state, API, LLM connector parsing, God Console, scene smoke. |
logs |
Local runtime logs | api.*.log, vite.*.log |
Ignored runtime artifacts; not source of truth. |
Root configs:
| File | Current Role |
|---|---|
pyproject.toml |
Python package metadata, dependency groups, pytest/ruff/mypy config, world-simulator script. |
uv.lock |
Python dependency lock. |
package.json / package-lock.json |
Frontend scripts and npm dependencies. |
vite.config.ts |
Vite root is frontend, dev port 5173, build output dist/frontend. |
playwright.config.ts |
E2E config; starts Vite dev server only. |
tsconfig.json / tsconfig.node.json |
Strict TS compiler settings. |
.gitignore |
Ignores env, logs, tmp, build, cache, node_modules, .venv. |
3. Runtime Architecture
Current: backend and frontend are separate processes. There is no WebSocket implementation in the repository; the UI uses REST fetch and polling.
Runtime entrypoints:
| Process | Command | Entrypoint |
|---|---|---|
| Backend API | python -m world_simulator run --config config/game.modal.local.json |
src/world_simulator/__main__.py -> run_server |
| Backend inspect | python -m world_simulator inspect --config ... |
Prints initial WorldState JSON |
| Frontend dev | npm run dev |
Vite serving frontend/index.html and frontend/src/main.tsx |
| Frontend typecheck/build | npm.cmd run typecheck, npm.cmd run build |
tsc -b, Vite build |
HTTP/API:
| Endpoint | Method | Current Role |
|---|---|---|
/health |
GET | Backend health, current tick, simulator name, tick status. |
/state |
GET | Raw WorldState.to_dict() plus simulation status. |
/scene/state |
GET | UI snapshot via to_scene_snapshot; optional ?warmup=1 triggers Modal health warmup. |
/tick |
POST | Plans and applies one tick. |
/god-command |
POST | Sends freeform command to God Console LLM and applies tool edits. |
flowchart TD
User[Player] --> UI[React/Vite UI]
UI --> Scene[React Three Fiber Scene]
UI --> GodPanel[GodConsolePanel]
UI --> Controls[SimulationControls]
GodPanel -->|POST /god-command| API[ThreadingHTTPServer]
Controls -->|POST /tick| API
UI -->|GET /scene/state polling| API
API --> Lock[World Lock and tick/god status]
Lock --> Sim[plan_world_tick/apply_tick_plan]
Sim --> Connector[WorldSimulator connector]
Connector --> NPCModel[OpenAI-compatible NPC model]
API --> GodConsole[GodConsole]
GodConsole --> GodModel[OpenAI-compatible God Console model]
Sim --> World[Canonical in-memory WorldState]
GodConsole --> World
World --> Snapshot[to_scene_snapshot]
Snapshot --> UI
Config loading:
src/world_simulator/config.py loads JSON configs and optional .env files from cwd/config parent directories. Env variables override configured base_url/api_url/model when *_env fields are present.
Logging:
Current: backend uses print in server.py and openai_compatible.py; BaseHTTPRequestHandler.log_message prints requests. No structured logging subsystem exists. logs/ contains local ignored artifacts.
4. Current Game Loop
Current: a tick is one explicit backend update triggered by POST /tick. There is no autonomous backend timer loop. Frontend auto-ticking is implemented by frontend/src/hooks/useWorldSimulation.ts with a window.setTimeout(..., 500). SimulationConfig.tick_ms is parsed but not used by backend tick scheduling.
Tick flow:
- UI calls
tickWorld()infrontend/src/api.ts. server.pyhandlesPOST /tick.- Under lock, backend rejects if
tick_status.in_progressorgod_status.in_progress. - Backend computes
next_tick = world.tick + 1. - Backend deep-copies
worldtoplanning_world. - Backend sets
_TickStatus(in_progress=True, pending_tick=next_tick). - Outside lock,
plan_world_tick(planning_world, simulator, next_tick)callsWorldSimulator.propose_tick. OpenAICompatibleWorldSimulatororDeterministicWorldSimulatorreturns aTickPlan.- Under lock,
apply_tick_plan(world, planned_tick, plan)mutates liveWorldState. - Backend clears tick status and returns raw world JSON.
- UI refreshes
/scene/state.
Files:
| File | Current Role |
|---|---|
src/world_simulator/api/server.py |
Request handling, locks, in-progress status, snapshot while tick is planning. |
src/world_simulator/simulation/tick.py |
plan_world_tick, apply_tick_plan, action execution. |
src/world_simulator/simulation/connectors/base.py |
WorldSimulator, TickPlan, NpcDirective contracts. |
src/world_simulator/simulation/connectors/openai_compatible.py |
Per-NPC LLM planning and fallback merge. |
src/world_simulator/simulation/connectors/deterministic.py |
Offline deterministic walk directives. |
frontend/src/hooks/useWorldSimulation.ts |
Manual step, auto-tick, polling, UI pending state. |
Data updated per tick:
| Data | Current Update |
|---|---|
world.tick |
Set to next_tick after applying plan. |
world.last_tick_source |
Set to plan.source. |
Npc.position |
Updated by walk after clamp/snap/max-distance rules. |
Npc.intention |
Set to walking, talking to X, attacking X, error-like strings, idle, or dead. |
Npc.health |
Reduced by valid attack. |
Npc.memory / memory_summary |
Updated for talk, attack, death, directive memory; compacted after five meaningful memories. |
Pause/stop:
Current: backend has no pause state. UI has isAutoTicking; toggling it stops scheduling future POST /tick. In-progress ticks cannot be cancelled.
Error handling:
| Situation | Current Behavior |
|---|---|
| Concurrent tick | HTTP 409 tick_in_progress. |
| God command during tick | HTTP 409. |
| Tick planning exception | HTTP 500 tick_failed, tick status cleared. |
| LLM NPC request exception | Logged; deterministic fallback fills if enabled. |
| Malformed LLM tool args | Ignored; fallback may fill missing NPC. |
| God Console exception | HTTP 500 god_command_failed, god status cleared. |
5. Current NPC / Agent Logic
Current: NPCs are created in src/world_simulator/simulation/spawning.py:create_world / _spawn_npc.
NPC dataclass fields from src/world_simulator/domain.py:
| Field | Type | Current Meaning |
|---|---|---|
id |
str |
npc-001, npc-002, ... |
name |
str |
Deterministic from _NAMES: Ada, Boris, Cora, ... |
role |
str |
Deterministic from _ROLES: farmer, builder, forager, guard, scribe. |
position |
Vec3 |
World coordinates; gameplay plane uses x/z, y is 0.0. |
health |
int |
Starts at 100. is_alive currently returns health >= 0. |
attack_damage |
int |
Spawned deterministic random 5-15. |
personality |
str |
Starts "neutral"; God Console can overwrite. |
god_directive |
`str | None` |
intention |
str |
UI-facing current state/action label. |
memory_summary |
`str | None` |
memory |
list[MemoryEntry] |
Latest meaningful observations. |
Supported actions:
| Action | Current Source | Current Execution |
|---|---|---|
walk |
LLM tool or deterministic connector | Move up to one block (4.0 units), snapped/clamped to terrain. |
talk |
LLM tool | Requires living target and distance <= TALK_RADIUS (12.0 units); records memories. |
attack |
LLM tool | Requires living target and distance <= ATTACK_RADIUS (4.0 units); server computes deterministic damage. |
idle |
NpcDirective action kind |
Sets intention to idle; not exposed as current LLM tool. |
NPC decision flow:
OpenAICompatibleWorldSimulator.propose_tickcomputes deterministic fallback plan._call_modelfilters living NPCs and calls_call_model_for_npcs._call_model_for_npcsdispatches per-NPC requests inThreadPoolExecutor, bounded bymax_parallel_npc_calls._build_requestsends model, messages,ACTION_TOOLS, generation params._build_messagescreates a single-NPC prompt._parse_model_responseaccepts OpenAI tool calls first, or fallback JSON content._actions_to_planconverts recognized action objects intoNpcDirective.- Missing NPCs are retried individually once; then deterministic fallback fills missing directives if enabled.
Prompt:
Current: system prompt says the model is exactly one NPC, must choose only its own next action, and must use exactly one tool: walk, talk, or attack. User payload includes rules, terrain, one npc context, recent_observations, memory_summary, god_directive, and visible_npcs.
Model output:
| Aspect | Current |
|---|---|
| Primary format | OpenAI Chat Completions tool calls. |
| Fallback format | JSON content with action or actions, parsed by _parse_json_content. |
Validation before TickPlan |
Known living npc_id, known action kind, known target ID, short message, coordinate clamp. |
| Execution validation | Living actor, target availability, talk/attack range, terrain bounds, walk max distance. |
| Retry | Missing NPCs retried once individually; no structured repair retry for invalid semantic action. |
| Cooldown | None. |
| Distance check | Yes at execution for talk/attack; visible context includes can_talk and can_attack. |
| Threat detection | No canonical threat model. |
| Memory | Rolling per-NPC memory and summary; no retrieval/indexing. |
| Personality/traits/goals | personality string and god_directive string only; no typed goals. |
| Reaction to other NPCs | Emergent via visible NPC summaries and memories only. |
Current problem: scenario "Ada wants to kill everyone".
- God Console likely turns this into
set_npc_directivefor Ada, possibly personality/damage edits. - Ada receives
god_directivein her next NPC prompt. - If Ada chooses
attackwhile target is outsideATTACK_RADIUS,apply_tick_planblocks damage and records "too far to attack X"; there is no automatic conversion towalk/move_to_target. - Other NPCs do not receive a global active event saying Ada is hostile. They only see current visible summaries and their own memories.
- If Ada has not successfully attacked nearby, victims may have no concrete threat observation.
- If Ada does attack in range, target receives memory, but there is no forced threat-response policy; next tick still relies on general LLM prompt.
- Conversation can continue because
talkonly checks target alive and distance, not hostility, willingness, combat lock, or social availability.
Gap: current NPC logic mixes intent, action selection, range feasibility, attack resolution, and social response. There is no explicit two-phase intent -> validation -> resolution, no target pursuit fallback, and no canonical active_threats/combat_state.
6. Current God Console Flow
Current UI path:
| Step | File | Behavior |
|---|---|---|
| Input field | frontend/src/components/GodConsolePanel.tsx |
Text input placeholder "Ada wants to kill everyone". |
| Submit handling | frontend/src/hooks/useWorldSimulation.ts |
runGodCommand stops auto-ticking, sends command, stores returned snapshot/message. |
| API call | frontend/src/api.ts |
sendGodCommand(command) POSTs JSON { "command": "..." } to /god-command. |
| Backend handler | src/world_simulator/api/server.py |
Rejects during tick/other command, deep-copies world, calls GodConsole.propose. |
| LLM planning | src/world_simulator/simulation/god_console.py |
Sends world/NPC summary and GOD_TOOLS to OpenAI-compatible model. |
| Apply edits | apply_god_command_plan |
Mutates live WorldState fields and returns applied strings. |
| UI update | useWorldSimulation.ts |
Sets snapshot from response and displays first applied message or summary. |
God Console tools:
| Tool | Current Effect |
|---|---|
set_npc_health |
Sets health; health < 0 sets intention dead; reviving from dead sets idle. |
set_npc_attack_damage |
Sets non-negative base damage. |
set_npc_personality |
Sets short freeform personality string. |
set_npc_directive |
Sets npc.god_directive, remembers God directive: .... |
add_npc_memory |
Adds one memory to one NPC. |
set_npc_position |
Clamps and sets position directly. |
Directive persistence:
| Question | Current Answer |
|---|---|
| Где сохраняется директива? | В поле конкретного Npc.god_directive. |
| Видят ли все NPC? | Нет. Только NPC, которому выставлен directive, если модель вызвала tool для него. Остальные могут получить memory только через отдельные tool calls. |
| Как долго действует? | Пока поле не перезаписано. Нет duration_ticks. |
| Это persistent event/story arc? | Нет. Нет Event/StoryArc dataclass или active event store. |
| Забывается ли через 1-2 ticks? | god_directive не забывается; memory может быть compacted в summary после лимита. |
| Priority/urgency/severity? | Нет. |
| Target extraction? | Только неявно внутри God Console LLM tool choice; typed target model нет. |
| Structured event model? | Нет. |
Gap: God Console меняет поля NPC напрямую. Она не создает canonical event вроде DirectiveEvent(type=hostile_intent, primary_actor=ada, targets=all_citizens, status=active).
7. Current Model Provider Architecture
Current: модельная архитектура основана на ConnectorConfig из src/world_simulator/config.py.
Connector fields:
type, base_url, api_url, model, api_key_env, timeout_seconds, max_tokens, temperature, top_p, tool_choice, max_parallel_npc_calls, fallback_to_deterministic, extra_body.
Config profiles:
| Config | Purpose | NPC Connector | God Console | Key Params |
|---|---|---|---|---|
config/game.local.json |
Offline deterministic MVP | No connector block, defaults to deterministic |
None | 80x80 world, 10 NPCs, tick_ms 500, port 8000. |
config/game.llm.local.json |
Generic OpenAI-compatible profile | openai_compatible, base https://api.openai.com/v1, model your-tool-capable-model, key env OPENAI_API_KEY |
Modal Qwen qwen3.6-27b-h100-fp8 |
timeout 300/600, temp 0.6, top_p 0.95. |
config/game.modal.local.json |
Active Modal profile | Modal Nemotron nemotron3-nano-30b-a3b-fp8, env MODAL_NEMOTRON_*, max parallel 8 |
Modal Qwen qwen3.6-27b-h100-fp8, env GOD_CONSOLE_QWEN_* |
tool_choice: required, enable_thinking: false, max_tokens 1400/1200. |
Secrets:
Current: no literal API keys were found in checked config files. Keys are referenced via env vars such as OPENAI_API_KEY. If future docs include key values, write [REDACTED].
Provider implementation:
| Component | File | Current Behavior |
|---|---|---|
| NPC OpenAI-compatible client | openai_compatible.py |
Uses official openai.OpenAI client and Chat Completions tool calls. |
| God Console OpenAI-compatible client | god_console.py |
Same client style, separate connector config allowed. |
| Connector factory | connectors/factory.py |
Switches only deterministic vs openai_compatible. |
| Modal health warmup | api/server.py |
Derives .modal.run/health URLs from connector base URLs and polls in daemon threads. |
| Deployments | deployments/*.py |
vLLM OpenAI-compatible Modal web servers for Nemotron and Qwen. |
Current: NPC residents use config.connector. In default game.modal.local.json, this is Nemotron Nano. God Console can use separate config.god_console; default Modal profile uses Qwen3.6-27B FP8.
Target: Qwen-like model should become World Orchestrator. Nemotron-like model should remain per-citizen decision model.
8. Current State Model
Current: canonical state is in-memory Python dataclasses in src/world_simulator/domain.py.
@dataclass(frozen=True, slots=True)
class Vec3:
x: float
y: float
z: float
@dataclass(slots=True)
class MemoryEntry:
tick: int
text: str
@dataclass(slots=True)
class Npc:
id: str
name: str
role: str
position: Vec3
health: int = 100
attack_damage: int = 10
personality: str = "neutral"
god_directive: str | None = None
intention: str = "idle"
memory_summary: str | None = None
memory: list[MemoryEntry] = field(default_factory=list)
@dataclass(slots=True)
class WorldState:
tick: int
seed: int
terrain: Terrain
npcs: list[Npc]
last_tick_source: str = "initial"
Other state contracts:
| Concept | Current Representation |
|---|---|
| Terrain | Terrain(kind="plain_green", width, depth, color="#43a047"). |
| Events | No canonical event dataclass. God commands are applied immediately as edits. |
| Actions | NpcDirective in connectors/base.py. |
| Tick plan | TickPlan(source, directives) in connectors/base.py. |
| Objects/buildings/resources | Not present. |
| Memory | Npc.memory latest meaningful entries and Npc.memory_summary. |
| Persistence | None; state resets on process start. |
| Import/export | WorldState.to_json and /state output only; no load saved world. |
| SQLite/local JSON database | None. |
| Frontend DTO | frontend/src/types.ts mirrors to_scene_snapshot. |
Current: src/world_simulator/rendering/scene_contract.py converts backend state to:
schema_version: 1ticksimulation.last_tick_sourcesimulation.tick_in_progresssimulation.pending_tickterrainentities[]for NPCs, including health, max_health, attack_damage, personality, god_directive, memory data, render primitive.
Gap: no global memory/event timeline, no relation/faction maps, no action history except per-NPC memories and UI-facing intention.
9. Current Action System (primitives refactor, 2026-06)
Current: the whole action surface is six parameterized primitives,
ActionKind = Literal["move", "speak", "strike", "use", "transfer", "idle"]
(connectors/base.py). Behavioral flavor (fleeing, pleading, threatening,
stealing, gathering, healing...) is expressed through parameters and free-text
intent, never through dedicated action ids:
| Primitive | Key parameters | Covers |
|---|---|---|
move |
target coords, target_npc_id/target_entity_id/resource_id, away |
walking, pursuit, investigation, approaching resources, fleeing (away=true) |
speak |
target_npc_id, message, communication_intent |
direct talk, pleading, threatening, trade requests; without target it is a broadcast shout (call for help) |
strike |
target_npc_id / target_entity_id |
attacking NPCs and beasts |
use |
resource_id (gather) or resource_type food/herbs (eat/heal) |
gathering, eating, healing |
transfer |
target_npc_id, resource_type, amount, take |
giving resources; take=true is stealing |
idle |
intent (observe/defend) |
waiting, observing, defending |
Design principles:
- The engine validates only physical possibility.
validate_directive(tick.py) andvalidate_survival_action(survival.py) repair missing or dead targets, out-of-range interactions (converted to approach movement), and empty inventories. They never reject an action for being behaviorally implausible — that judgment belongs to the planner/LLM. - Hints, not masks.
build_action_hints(actions/hints.py) produces rankedsuggested_actionsplus a reason from the citizen's goal and perception. They are injected into the LLM prompt as hints; all six tools are always available (primitive_tools()inopenai_compatible.py). The survival prompt equivalently usesGOAL_SUGGESTED_ACTIONS. - The deterministic planner is an autopilot, not a pre-filter.
autopilot.py(social worlds) andpropose_survival_tick/survival_directive_for(survival worlds) fill gaps when no LLM directive exists or the output is unusable. They never restrict the LLM's choices. - Effect resolution stays engine-owned. Damage, hunger, inventory,
movement steps, and memory episodes are resolved deterministically in
tick.py/survival.py; inside the survival resolver primitives are translated to private effect verbs (_verb_for_action) that never leave the module.
Action lifecycle:
| Stage | Implementation |
|---|---|
| Selection | LLM picks one of six primitive tools; deterministic autopilot otherwise. |
| Parsing | _parse_tool_calls / _parse_json_content / _actions_to_plan. |
| Validation | Physical-possibility repairs with ValidationResult debug traces. |
| Execution | apply_tick_plan dispatches _apply_move/_apply_speak/_apply_strike/_apply_idle; survival worlds resolve through apply_survival_plan. |
| Consequences | Position, intention, health, inventory, memory episode, relationship updates. |
10. Current Frontend Architecture
Current: frontend is React + TypeScript + Vite with React Three Fiber and Drei.
| Concern | Files | Current Role |
|---|---|---|
| App composition | frontend/src/App.tsx |
Arranges scene, status bar, controls, God Console, agent panel, toasts. |
| Entry point | frontend/src/main.tsx |
createRoot(...).render(<App />). |
| API client | frontend/src/api.ts |
fetchWorldSnapshot, tickWorld, sendGodCommand; base URL from VITE_WORLD_SIMULATOR_API or localhost. |
| State sync | frontend/src/hooks/useWorldSimulation.ts |
Initial load, warmup, manual tick, auto tick, polling, God Console command, selected entity. |
| DTOs | frontend/src/types.ts |
WorldSnapshot, EntitySnapshot, ModelStatusSnapshot. |
| 3D scene | frontend/src/scene/WorldSimulatorScene.tsx, WorldView.tsx |
Canvas, terrain, grid, NPC actors, camera controls. |
| Character rendering | NpcActor.tsx, NpcBody.tsx, NpcHead.tsx, NpcTorso.tsx, useNpcActorAnimation.ts |
Capsule-like NPC body, labels, walk/death animation. |
| God Console UI | components/GodConsolePanel.tsx |
Text input and submit button. |
| HUD/status | StatusBar.tsx, SimulationControls.tsx, AgentPanel.tsx, StatusToasts.tsx |
Tick, NPC count, model status, selected NPC details, memories, errors. |
State synchronization:
- Initial load fetches
/scene/state?warmup=1. - Normal refresh fetches
/scene/state. - Auto tick posts
/tick, then refreshes. - If backend reports
tick_in_progress, UI polls until status clears. - Model warmup status is displayed if
simulation.modelsexists.
Current: displayed NPC action is entity.state.intention, coming from backend Npc.intention.
11. Current Backend Architecture
Current: backend is a Python package using only the standard library HTTP server plus openai for model calls.
| Module | Current Role |
|---|---|
__main__.py |
CLI parser, config loading, world creation, inspect/run. |
config.py |
JSON config parser, .env loader, dataclass configs. |
domain.py |
Canonical world dataclasses. |
api/server.py |
HTTP endpoints, locking, tick/god orchestration, Modal health warmer. |
simulation/spawning.py |
Deterministic NPC spawn. |
simulation/mechanics.py |
Constants and basic mechanics: block size, radii, is_alive, distance. |
simulation/tick.py |
Tick planning wrapper and action execution. |
simulation/memory.py |
Meaningful memory storage and compaction. |
simulation/connectors/base.py |
WorldSimulator protocol and action plan DTOs. |
simulation/connectors/deterministic.py |
Offline random walk simulator. |
simulation/connectors/openai_compatible.py |
Per-NPC LLM connector. |
simulation/god_console.py |
God Console LLM tools and edit application. |
rendering/scene_contract.py |
Snapshot contract for frontend. |
agents/base.py, agents/scripted.py |
Unused/incomplete agent planner abstraction. |
Current: there is no FastAPI, aiohttp app, WebSocket server, database, background scheduler, or dependency injection framework.
12. Current Tests and Quality Gates
Current: tests are mostly pytest unit/integration tests plus one Playwright E2E spec.
| Test File | Coverage |
|---|---|
tests/test_config.py |
Config parsing, connector config, dotenv precedence, invalid NPC count. |
tests/test_world_spawning.py |
Spawn determinism, tick movement, attack/talk range, memory compaction, death, occupancy behavior. |
tests/test_openai_connector.py |
Tool-call parsing, per-NPC context, parallel calls, fallback, malformed tool args, configured params. |
tests/test_god_console.py |
God Console tool-call parsing and world edits. |
tests/test_api_server.py |
Snapshot while tick planning, Modal health warmup, health URL derivation. |
tests/test_scene_contract.py |
Snapshot schema fields. |
tests/e2e/world-simulator-scene.spec.ts |
Canvas visibility, status/God Console UI, keyboard controls, collapsed text tooltip behavior. |
Documented commands in README.md:
uv run pytest
uv run ruff check .
uv run mypy src
npm run typecheck
npm run build
npm run test:e2e
Observed during inventory:
uv run pytestfailed becauseuvwas not in PATH..venv\Scripts\python.exe -m pytest --basetemp tmp\pytest-runpassed: 33 tests passed.npm run typecheckwas blocked by PowerShell execution policy fornpm.ps1.npm.cmd run typecheckpassed.
Gap: no tests yet for threat response, directive persistence as active event, action validation object, invalid JSON repair policy, or "Ada wants to kill everyone" behavioral acceptance criteria.
13. Current Limitations
Confirmed limitations:
| Limitation | Evidence |
|---|---|
| No persistent story arcs | No event/story dataclasses or persistence layer. |
| No canonical event memory | God Console edits NPC fields directly; no events field in WorldState. |
No explicit action schema beyond NpcDirective |
Current action contract lacks intent, validation, resolution result, confidence, failure policy. |
No two-phase intent -> validation -> resolution object model |
apply_tick_plan executes directives directly. |
| No threat response system | No active threat fields/functions; prompt only gives visible NPC summaries. |
| No combat state machine | Npc.intention is a string, not a typed mode. |
| No social state machine | talk has only range/alive checks. |
| No relationship model | No relation fields in Npc or WorldState. |
| No faction model | No faction fields/classes. |
| No proper memory retrieval | Only recent five meaningful memories plus rolling string summary. |
| No compact context builder module | Context is built inside _npc_context / _build_messages. |
| No deterministic rules layer for social/combat priority | Mechanics cover distance/damage, not behavior priority. |
| No structured orchestration loop | No orchestrator service; God Console is direct tool-edit model. |
| No debug trace | Only print logs and memories/intention. |
Potential limitations:
| Potential Limitation | Why Potential |
|---|---|
| NPCs may small-talk with active attacker | Code permits talk if alive and in range; exact behavior depends on model output. |
| God directive may be applied to too few/many NPCs | God Console target extraction is LLM tool choice, not deterministic parser. |
Health 0 means alive |
is_alive returns health >= 0; game design may intend <= 0 death but code currently does not. |
14. Target Architecture for Smarter Citizens
Target: realistic MVP layer on top of current code, not a full rewrite.
| Layer | Purpose | First Integration |
|---|---|---|
| 1. Canonical World State | Add typed events/threats/action history while keeping WorldState authoritative. |
domain.py |
| 2. Event / Directive Parser | Convert God Console text into DirectiveEvent instead of direct-only NPC edits. |
god_console.py |
| 3. World Orchestrator | Maintain active events, choose relevant citizens, summarize tick; does not directly kill/move NPCs. | New orchestrator around /god-command and tick planning |
| 4. Citizen Context Builder | Produce compact per-NPC context with nearby citizens, threats, active events, allowed actions. | Extract from openai_compatible._npc_context |
| 5. Citizen Decision Model | Small model chooses one allowed action as JSON/tool call. | openai_compatible.py |
| 6. Action Validator | Check range, visibility, target validity, social/combat constraints; produce ValidationResult. |
Before apply_tick_plan |
| 7. Action Resolver | Apply valid/corrected actions to world; server owns health/death/position. | Refactor tick.py execution path |
| 8. Threat Response System | Force/boost flee/defend/call_for_help/attack_back/hide when hostile action observed. | Between validation and citizen context |
| 9. Memory Store | Store active events and meaningful observations separately from compact per-NPC memory. | domain.py + memory.py |
| 10. Tick Summary / Narrator | Summarize important outcomes for UI/debug and future context. | After apply_tick_plan |
| 11. Debug Panel | Show prompt context, chosen action, validation, resolution, world changes. | Add to snapshot/frontend later |
Recommendation: implement layers 4, 6, 7, and minimal 8 first. They directly fix bad combat behavior without requiring a full database or story system.
15. Target Two-Model Architecture
Model A: World Orchestrator
Target role:
- Parse God Console directive.
- Produce/maintain structured
DirectiveEvent. - Create/update active story arcs.
- Select relevant citizens.
- Summarize tick outcomes.
- Never directly execute
attack,death,position, or numeric health changes without engine validation. - Never mutate canonical state except through validated event/action APIs.
Likely code integration:
| File | Current Role | Target Use |
|---|---|---|
simulation/god_console.py |
Direct tool edits to NPC fields | Add/replace tools for create_directive_event, update_event_status, maybe keep direct edit tools for debug/admin. |
domain.py |
WorldState has no events |
Add events/timeline dataclasses in later coding task. |
api/server.py |
/god-command calls GodConsole.propose and applies edits |
Route orchestrator output into event store and snapshot response. |
Model B: Citizen Model
Target role:
- Roleplay one resident.
- Receive compact
CitizenContext. - Choose exactly one action from
allowed_actions. - Return structured JSON/tool call.
- Not decide action result.
- Not change numeric world state.
- Not declare death/victory.
- Not invent entities unless orchestrator/event system allowed them.
Likely code integration:
| File | Current Role | Target Use |
|---|---|---|
simulation/connectors/openai_compatible.py |
Builds prompt and parses LLM tools | Extract context builder and require target action schema. |
simulation/connectors/base.py |
NpcDirective/TickPlan |
Extend with intent/confidence or introduce CitizenAction before converting to NpcDirective. |
simulation/tick.py |
Direct execution | Insert validator/resolver boundary. |
simulation/mechanics.py |
Constants/range helpers | Add reusable visibility/range/threat helpers. |
16. Target Agent Harness Concept
Target: "agent harness" for this project means a bounded loop that converts observations into validated actions against a controlled environment. It should resemble Codex-like structure conceptually, not copy an unavailable internal design.
Harness primitives:
| Primitive | Meaning in WORLD_SIMULATOR |
|---|---|
| Loop | observation -> context -> model decision -> parse -> validate -> resolve -> observe. |
| Context builder | Compact per-agent slice from canonical state. |
| Tool registry | Allowed world/citizen actions with schemas and constraints. |
| Action schema | JSON/tool contract for action kind, target, intent, speech, confidence. |
| Validation | Deterministic checks before any world mutation. |
| Memory | Event memory + per-NPC observations + summaries. |
| Retries | Repair invalid JSON/tool args or fallback to safe deterministic action. |
| Error handling | Invalid model output cannot break tick. |
| Persistence | Later: save/load world/events/debug traces. |
| Debug trace | Store prompt context, raw response, parsed action, validation, resolution. |
| Bounded autonomy | Models choose only from allowed actions; engine owns truth. |
World Orchestrator harness:
- Input: God Console command, current world summary, active events.
- Tools: create/update event, annotate NPC directive, request summary.
- Output: structured event updates and affected NPC IDs.
- Validation: event schema, known targets, severity/urgency bounds.
Citizen Agent harness:
- Input: one
CitizenContext. - Tools/actions:
walk,talk,attack,flee,defend,call_for_help,wait,hide,plead. - Output: one
CitizenAction. - Validation: allowed action, known target, range/visibility, threat/social rules.
- Resolution: deterministic engine state changes.
Minimal MVP harness:
- Extract
CitizenContextbuilder. - Add
CitizenActionparser contract. - Add
ValidationResult. - Convert invalid out-of-range
attackintowalk/move_to_target. - Add threat observation after successful/attempted hostile action.
- Log trace in memory or temporary debug field.
17. Proposed Integration Points
| Area | File | Current Role | Proposed Change | Risk | Complexity | Priority |
|---|---|---|---|---|---|---|
| Action schema | simulation/connectors/base.py |
NpcDirective, TickPlan |
Add CitizenAction/ValidationResult or extend directives with intent/confidence. |
Medium: affects tests/imports. | Medium | 1 |
| Context builder | simulation/connectors/openai_compatible.py |
_npc_context, _build_messages inline prompt logic |
Extract reusable builder with nearby_citizens, visible_threats, active_events, allowed_actions. |
Low: can preserve output shape first. | Medium | 2 |
| Validation | simulation/tick.py |
Directly applies directives | Add pre-execution validation and action conversion before mutation. | Medium: behavior changes. | Medium | 3 |
| Mechanics helpers | simulation/mechanics.py |
Constants and distance | Add can_talk, can_attack, visible_to, nearest target helpers. |
Low. | Low | 4 |
| Threat response | New or simulation/tick.py |
None | After hostile intent/attack, mark nearby NPC contexts with threat and safe fallback actions. | Medium. | Medium | 5 |
| God directive events | simulation/god_console.py |
Direct NPC edits | Add event-creation path for directives; keep direct tools for admin/debug. | High: changes God Console semantics. | High | 6 |
| Canonical state | domain.py |
World/NPC/Terrain only | Add DirectiveEvent, TimelineEntry, maybe debug_traces. |
Medium. | Medium | 7 |
| Frontend debug | frontend/src/components/AgentPanel.tsx |
Shows current NPC state/memories | Later show debug trace and active events. | Low. | Medium | 8 |
| Tests | tests/test_world_spawning.py, tests/test_openai_connector.py, tests/test_god_console.py |
Current contracts | Add acceptance tests for Ada scenario and invalid JSON/action fallback. | Low. | Medium | 1 |
18. Minimal Implementation Plan
Phase 1 - Documentation and schemas
- Define current-compatible
CitizenAction,DirectiveEvent,ValidationResult,ResolutionResult. - Document allowed actions and required fields.
- Keep
NpcDirectiveas execution adapter for first implementation.
Phase 2 - Context Builder
- Extract per-NPC context builder from
openai_compatible.py. - Include actor state, nearby/visible NPCs, visible threats, current directive, current goal, recent memories, active events, allowed actions.
- Keep context compact; no full world dump.
Phase 3 - Action Validation
- Validate target exists, alive state, visibility, range, and allowed action.
- For out-of-range melee
attack, convert towalk/move_to_targettoward target. - For invalid JSON/tool args, produce safe fallback (
wait,flee, or deterministic walk depending context). - Require move before melee when target is not in attack range.
Phase 4 - Threat Response
- If an NPC is near an active hostile attacker, allowed/high-priority actions become
flee,defend,call_for_help,attack_back,hide,plead. - Suppress ordinary small talk with active attacker unless negotiation is safe and intentional.
- Add threat observations to nearby NPC contexts.
Phase 5 - Two-model split
- Orchestrator parses God Console directives into structured events.
- Citizen model picks actions from allowed list using compact context.
- Engine remains sole owner of health, death, position, range, and success/failure.
Phase 6 - Debugging and logs
- Store debug JSON per tick or per NPC:
- prompt context
- raw model output
- parsed action
- validation result
- resolution result
- world changes
- Surface minimal debug data in
/stateor/scene/stateonly after schema decision.
One-day scope recommendation:
- Add schema dataclasses.
- Add validator converting out-of-range
attacktowalk. - Add nearby threat response fallback after successful attack.
- Add pytest coverage for Ada/Bob acceptance criteria.
- Defer full orchestrator/event store to day two.
19. Recommended First Feature to Implement
Recommendation: implement "Structured Action Pipeline for NPCs" first.
Core behavior:
- Citizen Model still returns one action.
- Backend parses action into structured
CitizenAction. - Action enters
validate_action. - Invalid/out-of-range
attackbecomesmove_to_target/walkinstead of "attack air". - If target is in range,
attackproceeds and engine computes damage. - Nearby victims receive threat context and avoid ordinary small talk with attacker.
- Invalid JSON/tool output falls back safely and logs a debug trace.
Why this first:
- It directly fixes the visible "Ada wants to kill everyone" failure mode.
- It preserves the existing
WorldSimulatorandTickPlanboundaries. - It does not require a database, full event store, new frontend screens, or full orchestrator rewrite.
- It improves both small and large models because deterministic validation catches impossible actions.
20. Concrete Schemas for Next Step
Target: schemas below are adapted to current project naming: NPC IDs are npc-001; the movement plane uses x/z; existing execution action is walk, but validator may expose semantic move_to_target in debug/result.
DirectiveEvent
{
"id": "event_001",
"raw_text": "Ada wants to kill everyone",
"type": "hostile_intent",
"severity": 9,
"urgency": 95,
"source": "god_console",
"targets": ["all_citizens"],
"primary_actor_id": "npc-001",
"primary_actor_name": "Ada",
"duration_ticks": 20,
"status": "active"
}
CitizenContext
{
"citizen_id": "npc-001",
"citizen_name": "Ada",
"tick": 75,
"location": { "x": 10, "z": 4 },
"health": 100,
"attack_damage": 12,
"personality": ["aggressive", "impulsive"],
"god_directive": "Hunt every visible NPC until nobody remains.",
"current_goal": "harm_other_citizens",
"nearby_citizens": [],
"visible_threats": [],
"active_events": [],
"recent_observations": [],
"allowed_actions": ["walk", "wait", "shout", "search_target"]
}
CitizenAction
{
"citizen_id": "npc-001",
"action": "walk",
"target_id": "npc-002",
"target_position": { "x": 14, "z": 4 },
"intent": "approach_target_for_attack",
"speech": "You cannot hide forever.",
"confidence": 0.82
}
ValidationResult
{
"valid": true,
"reason": "Target is not in melee range, action converted to move_to_target.",
"original_action": "attack",
"resolved_action": "move_to_target",
"resolved_engine_action": "walk"
}
ResolutionResult
{
"tick": 76,
"actor_id": "npc-001",
"action": "move_to_target",
"engine_action": "walk",
"target_id": "npc-002",
"success": true,
"world_changes": {
"npc-001.position": { "x": 14, "y": 0, "z": 4 }
},
"timeline_entry": "Ada moves toward Boris with hostile intent."
}
21. Mermaid Diagrams
Current runtime architecture
flowchart TD
Player[Player] --> Frontend[React/Vite Frontend]
Frontend -->|GET /scene/state| SnapshotAPI[Backend HTTP API]
Frontend -->|POST /tick| TickAPI[POST /tick]
Frontend -->|POST /god-command| GodAPI[POST /god-command]
TickAPI --> TickLock[Tick/God Locks]
GodAPI --> TickLock
TickLock --> Planner[plan_world_tick]
Planner --> Connector{WorldSimulator}
Connector -->|deterministic| Det[DeterministicWorldSimulator]
Connector -->|openai_compatible| NPCModel[NPC LLM per living NPC]
NPCModel --> TickPlan[TickPlan/NpcDirective]
Det --> TickPlan
TickPlan --> Apply[apply_tick_plan]
GodAPI --> GodConsole[GodConsole.propose]
GodConsole --> GodModel[God Console LLM]
GodModel --> GodEdits[GodCommandPlan/GodEdit]
GodEdits --> ApplyGod[apply_god_command_plan]
Apply --> World[In-memory WorldState]
ApplyGod --> World
World --> Snapshot[to_scene_snapshot]
Snapshot --> Frontend
Target LLM decision pipeline
flowchart TD
GodText[God Console Text] --> Orchestrator[World Orchestrator Model]
Orchestrator --> Directive[DirectiveEvent]
Directive --> EventStore[Canonical Active Events]
EventStore --> ContextBuilder[Citizen Context Builder]
World[Canonical WorldState] --> ContextBuilder
ContextBuilder --> Context[CitizenContext]
Context --> CitizenModel[Citizen Model]
CitizenModel --> Action[CitizenAction JSON]
Action --> Validator[Action Validator]
Validator -->|valid or converted| Resolver[Action Resolver]
Validator -->|invalid unrecoverable| Fallback[Safe Fallback Action]
Fallback --> Resolver
Resolver --> World
Resolver --> Memory[Memory/Timeline/Debug Trace]
Target tick loop with harness
sequenceDiagram
participant UI as Frontend
participant API as Backend API
participant H as Tick Harness
participant W as WorldState
participant O as Orchestrator/Event Store
participant C as Citizen Model
participant V as Validator
participant R as Resolver
UI->>API: POST /tick
API->>H: start next tick
H->>W: read canonical snapshot
H->>O: get active events and tick priorities
loop each living NPC
H->>H: build compact CitizenContext
H->>C: request one action
C-->>H: CitizenAction
H->>V: validate range/visibility/threat rules
V-->>H: ValidationResult
end
H->>R: resolve validated actions together
R->>W: apply world changes
R-->>H: ResolutionResult + debug trace
H-->>API: updated state
API-->>UI: JSON response
22. Next Agent Instructions
Read this file first, especially sections 5, 8, 9, 14, 17, 18, 19, and 20. Then read these source files in order:
src/world_simulator/domain.pysrc/world_simulator/simulation/connectors/base.pysrc/world_simulator/simulation/connectors/openai_compatible.pysrc/world_simulator/simulation/tick.pysrc/world_simulator/simulation/god_console.pytests/test_world_spawning.pytests/test_openai_connector.py
First coding task:
Implement Structured Action Pipeline for NPCs without changing model configs. Start by adding minimal schemas and validation around current NpcDirective flow. Preserve existing endpoints and frontend snapshot schema unless explicitly extending debug output.
Files likely to change first:
| File | Expected Change |
|---|---|
simulation/connectors/base.py |
Add structured action/validation DTOs or extend directive metadata. |
simulation/connectors/openai_compatible.py |
Build compact context with allowed actions and parse model result into structured action. |
simulation/tick.py |
Add validation/conversion before execution, especially attack range fallback to movement. |
simulation/mechanics.py |
Add helper checks for range/visibility/nearest target. |
tests/test_world_spawning.py |
Add engine-level action validation/resolution tests. |
tests/test_openai_connector.py |
Add model-output parsing/fallback tests. |
Do not:
- Let LLM set health/death/position as an action result.
- Let out-of-range attack apply damage.
- Let invalid JSON/tool args break a tick.
- Remove deterministic fallback before replacing it with better safe fallback.
- Rework frontend rendering while fixing backend action semantics.
- Treat God Console text as memory only; hostile directives need active event semantics in the next phase.
Recommended test commands:
.venv\Scripts\python.exe -m pytest --basetemp tmp\pytest-run
npm.cmd run typecheck
Acceptance criteria for scenario "Ada wants to kill everyone":
- If Ada wants to kill everyone but nobody is nearby, she moves toward the nearest valid target instead of attacking empty space.
- If Ada reaches Bob and attacks, Bob and other nearby NPCs detect the threat.
- Nearby NPCs do not continue ordinary small talk with an active attacker.
- Bob chooses
flee,defend,call_for_help,attack_back, orpleaddepending on personality and state. - Attack result is resolved by the game engine, not by the LLM.
- Every action logs a debug trace with context, chosen action, validation, resolution, and world changes.
- Invalid JSON or malformed tool output from the model does not break the tick.
- God Console directive persists as an active event and does not disappear after 1-2 ticks.
Recommendation: ship the smallest version where out-of-range hostile attack becomes walk toward target and nearby victims get explicit threat context. That gives the largest behavior improvement before building the full orchestrator.