← all epics

Connection and sync — never pretend to be online

client

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

Where this stands (2026-10-24)

Offline play is a different thing now (Kris, 2026-10-23): when the realm cannot be reached the game goes, after seven seconds, to the offline runner — Chrome’s offline dinosaur game with Spellgrove’s own models (mini-games epic, issue #39, deliverable #37). So the Area “Connection” property — single_player / multiplayer / either, “Needs a connection / On your own / Either”, 005 — went completely the same day (issue #38): nothing had ever acted on it. The column is dropped, the two load-world rules and the editor’s control are gone. Local play — the walk-only home with no realm (was 006) — is withdrawn, and with it deliverable #11’s offline-home line (marked there, dated).

Two things this epic owns changed with it: when an Area’s channel gives up, the client now hangs up the director socket so the session judges the realm (a black-holed network kept it “open” for minutes); and while the runner is up the realm is looked for every 2 s, forgetting every cached address after a failed look (Godot kept the IPv6 address a carrier’s DNS64 had synthesised in the second before Wi-Fi came up, and every look failed against it for good). docs/handoff/offline-runner.md.

Before that (2026-09-13): six of eight pieces built, live and signed off — 001–003 on 2026-08-18, then 004, 005 and 007 on 2026-08-19 after Kris tested APK v17 in airplane mode: the client says it cannot reach the realm and stops, rather than pretending. The ship-verify-record round (was 008) that would flip deliverable #11 from promised to delivered is still held. No open issues. Past task files (to 2026-09-13): workshop/historic-docs/epics/connection-and-sync/tasks/.

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

Decisions (with Kris, 2026-08-18)

Non-goals

Relevant docs

Deliverables (1)

Work on this epic

The granular work is GitHub Issues, one label per epic: epic:connection-and-sync (private repository — sign-in required). The dated Where this stands section above names the open ones. The task files that carried this epic until 2026-09-13 are kept in the repo: task files to 2026-09-13 .

Ask an agent about this epic

    Also available as raw markdown.