Files
whale-town-end-v2/src/business/world_npc
xiangwang 5bb9e76266 fix: route world NPCs around the west plaza street lamp
Move the western transit point left to clear the lamp with the largest NPC footprint. Document source-based frontend collision verification and allow Jest to run from a clean checkout without the optional local test directory.
2026-09-18 01:53:28 +08:00
..

AI Town world NPC runtime

The runtime separates agent decisions from deterministic game execution:

  1. WorldNpcPlanner creates a daily goal and time-boxed semantic activities.
  2. world_npc.world.ts owns valid locations and traversable edges.
  3. WorldNpcService turns the selected activity into walk, transition, and perform actions.
  4. The WebSocket gateway broadcasts versioned actions and authoritative snapshots.
  5. Godot interpolates walk, renders perform as stationary work/talk, and changes maps on transition snapshots.

The planning model never sees world coordinates, route nodes, map IDs, or internal location IDs. It selects an exact Chinese locationName from the server-provided semantic location catalog; the server resolves that name to its internal locationId before validation and execution. If the model is unavailable or returns invalid JSON, the runtime uses the complete deterministic daily plan.

Daily planning receives the NPC's long-term character definition and server-maintained memory in its system context. That memory contains the previous daily plan, recent NPC encounters, anonymized resident-need summaries, and current resident signals. Memory is reference data rather than executable instructions, and public plans must not quote or identify a resident's private memory.

Resident conversations use an independent session for each NPC and resident. The dialogue model receives stable NPC instructions (identity, personality, daily goal, current activity, and that resident's long-term summary) as one system message, followed by the session's normal user/assistant turns. A meaningful interaction may ask the planner to revise only the activities after the current activity. The current activity and active route stay locked, so replanning cannot interrupt work or teleport the NPC. Route geometry always remains server-authoritative.

Registered agents

NPC Role Home Godot visual
鲸小研 科研观察员与知识分享者 广场海边研究点 independent 8x4 footless whale sheet
范鲸晶 镇长与居民事务协调者 公会接待处(固定:-199,-515 town mayor sheet
虾小满 码头向导与水路消息员 码头向导岗(固定:-825,437 dock crayfish sheet

Whale researcher and Niulai can route through whale_port, work_zone, and whale_cafe. The mayor and dock guide are stationary post NPCs: their daily activities and dialogue can change, but the server always keeps them at their original square positions and emits only an idle/perform state. Every map has a YSortWorld/Characters/Npcs runtime root; static copies of these agents must not be placed in scenes.

Planner configuration

WORLD_NPC_PLANNER_URL=https://your-openai-compatible-api/v1
WORLD_NPC_PLANNER_API_KEY=...
WORLD_NPC_PLANNER_MODEL=your-model
WORLD_NPC_DIALOGUE_MODEL=your-model
WORLD_NPC_STATE_PATH=data/world-npc-state.json
WORLD_NPC_REPLAN_COOLDOWN_MS=300000
WORLD_NPC_SOCIAL_ENABLED=on
WORLD_NPC_SOCIAL_COOLDOWN_MS=30000
WORLD_NPC_TIME_SCALE=1
WORLD_NPC_START_TIME=

Without all three planner variables, WhaleTown runs the deterministic fallback schedule. Set WORLD_NPC_PERSISTENCE=off only for isolated tests.

WORLD_NPC_TIME_SCALE and WORLD_NPC_START_TIME are development aids. Production should normally use scale 1 and no start override.

Runtime protocol

  • npc_snapshot: authoritative NPCs on the player's current map, including daily goal, current activity, plan source, position, and active action.
  • npc_action_started / npc_action_completed: versioned walk, transition, or perform lifecycle events.
  • npc_interact: authenticated player interaction; the server validates map membership and a maximum 150-pixel distance.
  • npc_spoke: public in-world response, with a target user so only that user's conversation panel records it.
  • npc_conversation: ordered autonomous dialogue between co-located NPCs; Godot renders the lines as sequential world bubbles without adding them to a player's conversation panel.
  • npc_interaction_error: authentication, distance, transition, throttling, or validation failure.

Operational state is available from GET /chat/world-npcs/status. Non-production time travel is available from POST /chat/world-npcs/test-time only when WORLD_NPC_TEST_CONTROLS=enabled and x-world-npc-test-token matches WORLD_NPC_TEST_CONTROL_TOKEN. Production code rejects clock overrides regardless of these values.

The status response explicitly reports whether planning and dialogue models are configured, whether autonomous NPC social behavior is enabled, and how many plan or conversation jobs are currently pending. An NPC can participate in only one generated encounter at a time; per-NPC social cooldown prevents overlapping conversation bubbles.

Status output exposes encounter metadata but never resident memory text. Long-term resident context is injected only for the matching npcId + userId pair; short-term turns are sent to the dialogue API as ordinary multi-turn chat messages rather than a JSON blob inside one user message.

Verification

npm run test:world-npc
npm run build

Godot verification from whale-town-front-v2:

/Applications/Godot.app/Contents/MacOS/Godot --headless --path . --editor --quit
/Applications/Godot.app/Contents/MacOS/Godot --headless --path . --scene tools/square_npc_test.tscn
/Applications/Godot.app/Contents/MacOS/Godot --headless --path . --script tools/smoke_ai_town_maps.gd

Check the backend's current route graph against the frontend's real collision shapes from whale-town-front-v2 (both repositories and backend dependencies are required):

sh scripts/check_world_npc_navigation.sh ../whale-town-end-v2

The script exports directly from TypeScript, then checks NPC standing positions and walk segments using the largest NPC footprint. Set GODOT_BIN when Godot is installed outside /Applications/Godot.app/Contents/MacOS/Godot.