Connection and sync — never pretend to be online
repo: land-of-lor (game/world client + Area server, game/director) and this workspace (board, deliverables, site/play) · depends on: world-map-and-portalsground-types · updated 2026-08-19
What this is
Kris played v10 and v11 on the phone (APK) and in a browser, and the two views of the Village diverged: the other body stopped moving in one client and never appeared in the other, and the APK walked straight through portal rings — which are entirely server-driven. The server had the APK’s character barely moved. The transport was fine (a native headless client from the laptop, same TLS + Caddy path, gets every move acked, portal hop included). What was wrong was how the client behaves when its channel dies — and, in Kris’s words, the deeper thing: we do want the game to be able to be played offline, that is important, but we don’t ever want to fake that we are online when we are not — which is kind of what the current behaviour looks like.
This epic is the answer to both halves. It comes out of a full review of the client, the Area
server, the director and the platform (three passes, 2026-08-18) whose findings are the task
list; the plan that produced it is summarised in tasks/001 and each task’s own file.
The diagnosis, in short
game/world/client/client_main.gd: expecting_game_close is a sticky file-global, armed on
every reconnect and portal hop and only ever consumed by a disconnect on a peer that has already
been replaced and is never polled again — so the next real drop is swallowed with no rejoin, no
reset, no message. The client keeps predicting into the void, walks through portals, sees nobody.
Any portal arrival arms it; a phone’s radio hiccup or background pause springs it. The 016
liveness watchdog then made it near-deterministic: its receive timestamp is never seeded on
connect, so on any real network it kills a healthy connection first, forcing the reconnect that
arms the flag. Every automated test runs on localhost, where the first packet lands in the same
frame and hides both.
Around that root cause the review found the same species everywhere: silent no-op sends and
misleading logs; kick reasons (TAKEN_OVER, BAD_GRANT) sent and closed in one tick, which this
Godot client cannot read (the very race 012 fixed for portal_result); intents overwritten
per tick with no ack or reject; a peer left unbound answering NOT_HELLO forever with nothing
prompting a re-hello; a portal transfer with no timeout; a snapshot pipeline that wedges
silently; an Area process that exits without a word to its players; and — on the director — an
Area that can be shut down under a live player because presence is counted on the director
socket, not the game one (the 016 heartbeat made that more likely, not less). Fifteen
constructible failure states, most with no self-heal, all indistinguishable in production logs
from “the phone’s network died”.
Principles
- Truth over motion. The avatar moves only on a channel that is demonstrably alive — recent frames and recent acks. A stalled channel freezes the avatar and says so.
- One owner of connection state per channel, an explicit, tested state machine, no sticky globals; every transition logged with a reason.
- Silence and non-acknowledgement are both signals, on both sides.
- Every intent gets an answer; every kick carries its reason and arrives before the close.
- The Area process is the authority on who is present. An Area shuts down when its game peers are gone, never because a director socket died.
- Every failure a player can hit has a sentence on screen, shown by code that doesn’t hide it in the same breath.
- Areas declare their connectivity —
single_player/multiplayer/either(defaultmultiplayer; the homeeither) — and the client honours it: no play in amultiplayerArea without a live channel; the home playable offline (walk-only, for now), its doors tomultiplayerAreas closed with a bump. - Faults are reproducible on the laptop before they’re chased on the phone; and the phone reports its own diagnostics.
Decisions (with Kris, 2026-08-18)
- Offline home is walk-only — nothing persisted or synced; offline persistence is a later epic once there is something to persist.
- A
multiplayerArea may run on a stalled connection ~15 s (avatar frozen from the first second, reconnects running) before play stops with an error until the connection is back. - Values:
single_player,multiplayer,either.
Non-goals
- Offline persistence or merging offline changes.
- Anti-cheat beyond what server authority already gives.
- Interest management (radius) — it stays off; the review notes it would need the same truth.
Relevant docs
epics/world-map-and-portals/tasks/010–016— the seven earlier bugs of this class.land-of-lor/game/README.md— “Known limits”, the deploy sections.land-of-lor/docs/handoff/connection-and-sync.md— written by 008 with the full diagnosis.
Status
1 ready1 backlog6 done
Next: 006 — local play, the home when there is no realm — is ready (005 signed off, so its blocker is gone); 008 last.
Deliverables (1)
- #11 — Honest connections — the game never pretends to be online, and your home works without the realm (2026-08-18, promised)
Tasks (8)
ready (1)
-
connection-and-sync/006— Local play — your home when there is no realm (priority 1, estimate L , blocked byconnection-and-sync/005)
backlog (1)
-
connection-and-sync/008— Ship, verify, record (priority 1, estimate S , blocked byconnection-and-sync/003,connection-and-sync/004,connection-and-sync/006,connection-and-sync/007, delivers #11)
done (6)
-
connection-and-sync/001— Stop the bleeding — the sticky close flag, the unseeded watchdog, readable kicks, and no Area shut down under a player (priority 1, estimate S ) -
connection-and-sync/002— The client connection state machine — freeze rather than fake, reconnect visibly, give up honestly (priority 1, estimate L ) -
connection-and-sync/003— Server truth — every intent answered, every kick delivered, no Area shut down under a player (priority 1, estimate L ) -
connection-and-sync/005— Area connectivity — single_player, multiplayer, either (priority 1, estimate M ) -
connection-and-sync/004— Observability — the connection indicator, and the phone that reports its own diagnostics (priority 2, estimate M ) -
connection-and-sync/007— The fault-injection harness and the soak (priority 2, estimate M )
Ask an agent about this epic
Paste your AI Task Runner API key once. It stays in this browser and is sent only to kris.ai-task-runner.com.
Also available as raw markdown.