| # 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(<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.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. |
|
|