# 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-директорий: ```text 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. | ```mermaid 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: 1. UI calls `tickWorld()` in `frontend/src/api.ts`. 2. `server.py` handles `POST /tick`. 3. Under lock, backend rejects if `tick_status.in_progress` or `god_status.in_progress`. 4. Backend computes `next_tick = world.tick + 1`. 5. Backend deep-copies `world` to `planning_world`. 6. Backend sets `_TickStatus(in_progress=True, pending_tick=next_tick)`. 7. Outside lock, `plan_world_tick(planning_world, simulator, next_tick)` calls `WorldSimulator.propose_tick`. 8. `OpenAICompatibleWorldSimulator` or `DeterministicWorldSimulator` returns a `TickPlan`. 9. Under lock, `apply_tick_plan(world, planned_tick, plan)` mutates live `WorldState`. 10. Backend clears tick status and returns raw world JSON. 11. 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` | Durable per-NPC instruction until overwritten. | | `intention` | `str` | UI-facing current state/action label. | | `memory_summary` | `str | None` | Rolling compacted memory string. | | `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: 1. `OpenAICompatibleWorldSimulator.propose_tick` computes deterministic fallback plan. 2. `_call_model` filters living NPCs and calls `_call_model_for_npcs`. 3. `_call_model_for_npcs` dispatches per-NPC requests in `ThreadPoolExecutor`, bounded by `max_parallel_npc_calls`. 4. `_build_request` sends model, messages, `ACTION_TOOLS`, generation params. 5. `_build_messages` creates a single-NPC prompt. 6. `_parse_model_response` accepts OpenAI tool calls first, or fallback JSON content. 7. `_actions_to_plan` converts recognized action objects into `NpcDirective`. 8. 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". 1. God Console likely turns this into `set_npc_directive` for Ada, possibly personality/damage edits. 2. Ada receives `god_directive` in her next NPC prompt. 3. If Ada chooses `attack` while target is outside `ATTACK_RADIUS`, `apply_tick_plan` blocks damage and records "too far to attack X"; there is no automatic conversion to `walk`/`move_to_target`. 4. Other NPCs do not receive a global active event saying Ada is hostile. They only see current visible summaries and their own memories. 5. If Ada has not successfully attacked nearby, victims may have no concrete threat observation. 6. 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. 7. Conversation can continue because `talk` only 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`. ```python @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: 1` - `tick` - `simulation.last_tick_source` - `simulation.tick_in_progress` - `simulation.pending_tick` - `terrain` - `entities[]` 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`) and `validate_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 ranked `suggested_actions` plus 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()` in `openai_compatible.py`). The survival prompt equivalently uses `GOAL_SUGGESTED_ACTIONS`. - **The deterministic planner is an autopilot, not a pre-filter.** `autopilot.py` (social worlds) and `propose_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()`. | | 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.models` exists. `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`: ```powershell 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 pytest` failed because `uv` was not in PATH. - `.venv\Scripts\python.exe -m pytest --basetemp tmp\pytest-run` passed: 33 tests passed. - `npm run typecheck` was blocked by PowerShell execution policy for `npm.ps1`. - `npm.cmd run typecheck` passed. `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: 1. Extract `CitizenContext` builder. 2. Add `CitizenAction` parser contract. 3. Add `ValidationResult`. 4. Convert invalid out-of-range `attack` into `walk/move_to_target`. 5. Add threat observation after successful/attempted hostile action. 6. 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 `NpcDirective` as 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 to `walk`/`move_to_target` toward 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 `/state` or `/scene/state` only after schema decision. One-day scope recommendation: 1. Add schema dataclasses. 2. Add validator converting out-of-range `attack` to `walk`. 3. Add nearby threat response fallback after successful attack. 4. Add pytest coverage for Ada/Bob acceptance criteria. 5. 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 `attack` becomes `move_to_target`/`walk` instead of "attack air". - If target is in range, `attack` proceeds 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 `WorldSimulator` and `TickPlan` boundaries. - 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 ```json { "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 ```json { "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 ```json { "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 ```json { "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 ```json { "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 ```mermaid 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 ```mermaid 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 ```mermaid 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: 1. `src/world_simulator/domain.py` 2. `src/world_simulator/simulation/connectors/base.py` 3. `src/world_simulator/simulation/connectors/openai_compatible.py` 4. `src/world_simulator/simulation/tick.py` 5. `src/world_simulator/simulation/god_console.py` 6. `tests/test_world_spawning.py` 7. `tests/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: ```powershell .venv\Scripts\python.exe -m pytest --basetemp tmp\pytest-run npm.cmd run typecheck ``` Acceptance criteria for scenario "Ada wants to kill everyone": 1. If Ada wants to kill everyone but nobody is nearby, she moves toward the nearest valid target instead of attacking empty space. 2. If Ada reaches Bob and attacks, Bob and other nearby NPCs detect the threat. 3. Nearby NPCs do not continue ordinary small talk with an active attacker. 4. Bob chooses `flee`, `defend`, `call_for_help`, `attack_back`, or `plead` depending on personality and state. 5. Attack result is resolved by the game engine, not by the LLM. 6. Every action logs a debug trace with context, chosen action, validation, resolution, and world changes. 7. Invalid JSON or malformed tool output from the model does not break the tick. 8. 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.