Skip to content

Echo own monster moves into local state#15

Merged
Julian-adv merged 1 commit into
Julian-adv:masterfrom
leedoe:fix-owned-monster-desync
Jul 23, 2026
Merged

Echo own monster moves into local state#15
Julian-adv merged 1 commit into
Julian-adv:masterfrom
leedoe:fix-owned-monster-desync

Conversation

@leedoe

@leedoe leedoe commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Monsters owned by the agent-client (ambient spawns it hosts AI for) stay frozen at their spawn position in the client's own nearby_monsters map, while their real server-side position wanders off with the AI. Two facts combine into this:

  1. The server deliberately skips echoing MonsterMoved back to the client that moved the monster (skip_player_id in the monster-move visibility diff).
  2. The agent-client never applies its own outgoing MonsterMove commands to local state — tick_all returns commands, they get sent, and nothing writes the new position back.

So the LLM is fed phantom coordinates for every monster this client spawned: it sees "scp939 1.4m away", the chase loop agrees it is already in range (same stale position) and never walks, PlayerAttack goes out, and the server's range gate silently drops it because the monster's real position is elsewhere — no event, no error, and the LLM retries forever. Operator NPCs (guards/merchants with combat behaviors) are affected the same way. Observed live: an agent standing in a spawn field swung at the same monster for 5+ minutes with zero PlayerAttacked events, while another (human) player killed those same monsters normally — their client receives the broadcasts.

The fix mirrors what the server's skip assumes: the owner already knows where its monsters are — so apply each outgoing MonsterMove to nearby_monsters (position, rotation, state) before sending it.

Verified against prod: before, owned monsters moved 0/18 across samples 35s apart (all idle at spawn coordinates, indefinitely); after, 27/27 moved across 90s (up to 47m, with live idle→walk→run state transitions). cargo test -p agent-client 20/20, clippy clean for the change.

Found while watching an agent through the spectator panel proposed in #13 — the map made the frozen-monster pattern obvious.

🤖 Generated with Claude Code

The server deliberately skips broadcasting MonsterMoved back to the
client that moved the monster, and the agent-client never applied its
own MonsterMove commands locally, so every owned monster sat frozen in
nearby_monsters at its spawn position while its real position wandered
off with the AI. The LLM then attacked coordinates the monster left long
ago, and the server's range gate silently dropped every swing.

Apply each outgoing MonsterMove to nearby_monsters before sending it,
which is exactly what the server's skip assumes the owner already does.

Verified on prod: owned monsters moved 0/18 across 35s before, 27/27
across 90s after, with live idle→walk→run transitions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Julian-adv
Julian-adv merged commit 1223d58 into Julian-adv:master Jul 23, 2026
@Julian-adv

Copy link
Copy Markdown
Owner

Thanks for the fix and the excellent diagnosis — merged as-is, and it held up well in review.

Heads-up on a follow-up (fb83211) that builds on it right after the merge:

  • The local echo moved from the orchestrator loop into send_command's MonsterMove arm, so it now applies the post-terrain-snap coordinates and covers any future sender automatically. Your logic is unchanged, just relocated.
  • Review surfaced one gap the echo exposed: the server silently drops moves that exceed the speed budget, and since the fanout skips the owner, a rejected move would have left the optimistic local state desynced until reconnect. The server now echoes the authoritative pose back to the mover on reject, and the client applies the full pose (position/rotation/state) symmetrically for corrections and normal fanout alike.
  • Both paths got regression tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants