world-simulator / architecture.md
DeltaZN
feat: UX improvements & remove openrouter
5a29df5
|
Raw
History Blame Contribute Delete
51.4 kB

A newer version of the Gradio SDK is available: 6.20.0

Upgrade

WORLD_SIMULATOR Architecture Inventory

Документ фиксирует фактическую архитектуру репозитория на момент инвентаризации и целевой MVP-слой для более умных LLM-жителей. Current означает подтверждено кодом. Target означает предлагаемую следующую архитектуру. Gap означает отсутствующий слой или правило. Assumption означает вывод, который не хранится явно в коде и нужен только для дальнейшего планирования.

1. Project Overview

Current: WORLD_SIMULATOR - Python-first AI civilization sandbox с 3D/изометрическим клиентом и "консолью бога". MVP сейчас умеет загружать JSON-конфиг, создавать детерминированный мир, спавнить NPC, отдавать состояние через HTTP API, рендерить карту и персонажей в React Three Fiber, выполнять ручные/автоматические тики и отправлять God Console команды в OpenAI-compatible LLM.

Area Current Implementation
Цель игры Симулятор общества, где игрок задает события через God Console, а мир хранит canonical state.
Реализовано Terrain, NPC spawn, health/damage/personality/directive/memory, walk/talk/attack, REST API, 3D UI, model configs, Modal deployment scripts.
MVP Маленький in-memory world loop: каждый живой NPC получает LLM/tool или deterministic directive, backend исполняет directive и обновляет WorldState.
Визуал frontend/src/scene/*, frontend/src/components/*, src/world_simulator/rendering/scene_contract.py.
Симуляция src/world_simulator/simulation/spawning.py, tick.py, mechanics.py, memory.py, connectors/*.
LLM NPC connector: simulation/connectors/openai_compatible.py; God Console: simulation/god_console.py; configs: config/*.json.

Gap: это еще не полноценная persistent society simulation. Нет world event store, factions, relationships, active story arcs, threat state machine или harness-style debug trace.

2. Current Repository Structure

Current: дерево проекта на 2-4 уровня, без .git, .venv, node_modules, cache-директорий:

config/
  game.llm.local.json
  game.local.json
  game.modal.local.json
deployments/
  modal_nemotron_nano*.py
  modal_qwen36_27b_h100*.py
  smoke_modal_endpoint.py
frontend/
  index.html
  src/
    api.ts
    App.tsx
    main.tsx
    styles.css
    types.ts
    components/
    hooks/
    scene/
logs/
  api.err.log
  api.out.log
  vite.err.log
  vite.out.log
src/
  world_simulator/
    __main__.py
    config.py
    domain.py
    agents/
    api/
    rendering/
    simulation/
tests/
  e2e/world-simulator-scene.spec.ts
  test_api_server.py
  test_scene_contract.py
  test_config.py
  test_god_console.py
  test_openai_connector.py
  test_world_spawning.py
package.json
playwright.config.ts
pyproject.toml
README.md
tsconfig.json
tsconfig.node.json
vite.config.ts
uv.lock
Path Назначение Ключевые файлы Почему важно
src/world_simulator Python backend package __main__.py, domain.py, config.py Runtime entrypoint, canonical dataclasses, config parsing.
src/world_simulator/api HTTP API server.py ThreadingHTTPServer, endpoints, tick/god-command locks, Modal warmup.
src/world_simulator/simulation Core simulation tick.py, mechanics.py, memory.py, spawning.py Tick planning/execution, range rules, NPC spawn, memory compaction.
src/world_simulator/simulation/connectors NPC decision providers base.py, deterministic.py, openai_compatible.py, factory.py Action schema, LLM calls, deterministic fallback.
src/world_simulator/rendering Backend-to-frontend snapshot scene_contract.py Converts canonical state into UI-friendly schema_version: 1 payload.
src/world_simulator/agents Early agent abstraction base.py, scripted.py Currently not wired into main loop; useful future harness hook.
frontend React/Vite client App.tsx, api.ts, hooks/useWorldSimulation.ts, scene/* UI, polling, controls, God Console, R3F scene.
config Runtime profiles game.local.json, game.llm.local.json, game.modal*.json World/server/model parameters.
deployments Modal vLLM deployments modal_nemotron_nano*.py, modal_qwen36_27b_h100.py OpenAI-compatible model servers for NPC/God Console.
tests Quality gates pytest tests, Playwright spec Verifies config, state, API, LLM connector parsing, God Console, scene smoke.
logs Local runtime logs api.*.log, vite.*.log Ignored runtime artifacts; not source of truth.

Root configs:

File Current Role
pyproject.toml Python package metadata, dependency groups, pytest/ruff/mypy config, world-simulator script.
uv.lock Python dependency lock.
package.json / package-lock.json Frontend scripts and npm dependencies.
vite.config.ts Vite root is frontend, dev port 5173, build output dist/frontend.
playwright.config.ts E2E config; starts Vite dev server only.
tsconfig.json / tsconfig.node.json Strict TS compiler settings.
.gitignore Ignores env, logs, tmp, build, cache, node_modules, .venv.

3. Runtime Architecture

Current: backend and frontend are separate processes. There is no WebSocket implementation in the repository; the UI uses REST fetch and polling.

Runtime entrypoints:

Process Command Entrypoint
Backend API python -m world_simulator run --config config/game.modal.local.json src/world_simulator/__main__.py -> run_server
Backend inspect python -m world_simulator inspect --config ... Prints initial WorldState JSON
Frontend dev npm run dev Vite serving frontend/index.html and frontend/src/main.tsx
Frontend typecheck/build npm.cmd run typecheck, npm.cmd run build tsc -b, Vite build

HTTP/API:

Endpoint Method Current Role
/health GET Backend health, current tick, simulator name, tick status.
/state GET Raw WorldState.to_dict() plus simulation status.
/scene/state GET UI snapshot via to_scene_snapshot; optional ?warmup=1 triggers Modal health warmup.
/tick POST Plans and applies one tick.
/god-command POST Sends freeform command to God Console LLM and applies tool edits.
flowchart TD
    User[Player] --> UI[React/Vite UI]
    UI --> Scene[React Three Fiber Scene]
    UI --> GodPanel[GodConsolePanel]
    UI --> Controls[SimulationControls]
    GodPanel -->|POST /god-command| API[ThreadingHTTPServer]
    Controls -->|POST /tick| API
    UI -->|GET /scene/state polling| API
    API --> Lock[World Lock and tick/god status]
    Lock --> Sim[plan_world_tick/apply_tick_plan]
    Sim --> Connector[WorldSimulator connector]
    Connector --> NPCModel[OpenAI-compatible NPC model]
    API --> GodConsole[GodConsole]
    GodConsole --> GodModel[OpenAI-compatible God Console model]
    Sim --> World[Canonical in-memory WorldState]
    GodConsole --> World
    World --> Snapshot[to_scene_snapshot]
    Snapshot --> UI

Config loading:

src/world_simulator/config.py loads JSON configs and optional .env files from cwd/config parent directories. Env variables override configured base_url/api_url/model when *_env fields are present.

Logging:

Current: backend uses print in server.py and openai_compatible.py; BaseHTTPRequestHandler.log_message prints requests. No structured logging subsystem exists. logs/ contains local ignored artifacts.

4. Current Game Loop

Current: a tick is one explicit backend update triggered by POST /tick. There is no autonomous backend timer loop. Frontend auto-ticking is implemented by frontend/src/hooks/useWorldSimulation.ts with a window.setTimeout(..., 500). SimulationConfig.tick_ms is parsed but not used by backend tick scheduling.

Tick flow:

  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`
intention str UI-facing current state/action label.
memory_summary `str None`
memory list[MemoryEntry] Latest meaningful observations.

Supported actions:

Action Current Source Current Execution
walk LLM tool or deterministic connector Move up to one block (4.0 units), snapped/clamped to terrain.
talk LLM tool Requires living target and distance <= TALK_RADIUS (12.0 units); records memories.
attack LLM tool Requires living target and distance <= ATTACK_RADIUS (4.0 units); server computes deterministic damage.
idle NpcDirective action kind Sets intention to idle; not exposed as current LLM tool.

NPC decision flow:

  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.

@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:

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

{
  "id": "event_001",
  "raw_text": "Ada wants to kill everyone",
  "type": "hostile_intent",
  "severity": 9,
  "urgency": 95,
  "source": "god_console",
  "targets": ["all_citizens"],
  "primary_actor_id": "npc-001",
  "primary_actor_name": "Ada",
  "duration_ticks": 20,
  "status": "active"
}

CitizenContext

{
  "citizen_id": "npc-001",
  "citizen_name": "Ada",
  "tick": 75,
  "location": { "x": 10, "z": 4 },
  "health": 100,
  "attack_damage": 12,
  "personality": ["aggressive", "impulsive"],
  "god_directive": "Hunt every visible NPC until nobody remains.",
  "current_goal": "harm_other_citizens",
  "nearby_citizens": [],
  "visible_threats": [],
  "active_events": [],
  "recent_observations": [],
  "allowed_actions": ["walk", "wait", "shout", "search_target"]
}

CitizenAction

{
  "citizen_id": "npc-001",
  "action": "walk",
  "target_id": "npc-002",
  "target_position": { "x": 14, "z": 4 },
  "intent": "approach_target_for_attack",
  "speech": "You cannot hide forever.",
  "confidence": 0.82
}

ValidationResult

{
  "valid": true,
  "reason": "Target is not in melee range, action converted to move_to_target.",
  "original_action": "attack",
  "resolved_action": "move_to_target",
  "resolved_engine_action": "walk"
}

ResolutionResult

{
  "tick": 76,
  "actor_id": "npc-001",
  "action": "move_to_target",
  "engine_action": "walk",
  "target_id": "npc-002",
  "success": true,
  "world_changes": {
    "npc-001.position": { "x": 14, "y": 0, "z": 4 }
  },
  "timeline_entry": "Ada moves toward Boris with hostile intent."
}

21. Mermaid Diagrams

Current runtime architecture

flowchart TD
    Player[Player] --> Frontend[React/Vite Frontend]
    Frontend -->|GET /scene/state| SnapshotAPI[Backend HTTP API]
    Frontend -->|POST /tick| TickAPI[POST /tick]
    Frontend -->|POST /god-command| GodAPI[POST /god-command]
    TickAPI --> TickLock[Tick/God Locks]
    GodAPI --> TickLock
    TickLock --> Planner[plan_world_tick]
    Planner --> Connector{WorldSimulator}
    Connector -->|deterministic| Det[DeterministicWorldSimulator]
    Connector -->|openai_compatible| NPCModel[NPC LLM per living NPC]
    NPCModel --> TickPlan[TickPlan/NpcDirective]
    Det --> TickPlan
    TickPlan --> Apply[apply_tick_plan]
    GodAPI --> GodConsole[GodConsole.propose]
    GodConsole --> GodModel[God Console LLM]
    GodModel --> GodEdits[GodCommandPlan/GodEdit]
    GodEdits --> ApplyGod[apply_god_command_plan]
    Apply --> World[In-memory WorldState]
    ApplyGod --> World
    World --> Snapshot[to_scene_snapshot]
    Snapshot --> Frontend

Target LLM decision pipeline

flowchart TD
    GodText[God Console Text] --> Orchestrator[World Orchestrator Model]
    Orchestrator --> Directive[DirectiveEvent]
    Directive --> EventStore[Canonical Active Events]
    EventStore --> ContextBuilder[Citizen Context Builder]
    World[Canonical WorldState] --> ContextBuilder
    ContextBuilder --> Context[CitizenContext]
    Context --> CitizenModel[Citizen Model]
    CitizenModel --> Action[CitizenAction JSON]
    Action --> Validator[Action Validator]
    Validator -->|valid or converted| Resolver[Action Resolver]
    Validator -->|invalid unrecoverable| Fallback[Safe Fallback Action]
    Fallback --> Resolver
    Resolver --> World
    Resolver --> Memory[Memory/Timeline/Debug Trace]

Target tick loop with harness

sequenceDiagram
    participant UI as Frontend
    participant API as Backend API
    participant H as Tick Harness
    participant W as WorldState
    participant O as Orchestrator/Event Store
    participant C as Citizen Model
    participant V as Validator
    participant R as Resolver

    UI->>API: POST /tick
    API->>H: start next tick
    H->>W: read canonical snapshot
    H->>O: get active events and tick priorities
    loop each living NPC
        H->>H: build compact CitizenContext
        H->>C: request one action
        C-->>H: CitizenAction
        H->>V: validate range/visibility/threat rules
        V-->>H: ValidationResult
    end
    H->>R: resolve validated actions together
    R->>W: apply world changes
    R-->>H: ResolutionResult + debug trace
    H-->>API: updated state
    API-->>UI: JSON response

22. Next Agent Instructions

Read this file first, especially sections 5, 8, 9, 14, 17, 18, 19, and 20. Then read these source files in order:

  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:

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