← Connection and sync — never pretend to be online · all tasks
Server truth — every intent answered, every kick delivered, no Area shut down under a player
connection-and-sync/003 · created 2026-08-18 · updated 2026-08-18
What to do
Area server (game/world/area_server.gd):
- Intents are processed in order within the movement budget, or the overwritten ones are
rejected with
SUPERSEDED— the client can reconcile every seq. (Today the per-tick Dictionary keeps only the last one and says nothing about the rest.) _send_raw_to_peerfailure ⇒ log and drop the peer (its body stays).- A peer left unbound (portal step 1,
_drop_other_entities_for_account, takeover) is told —error {REHELLO}/PORTAL_IN_PROGRESS— instead of answeringNOT_HELLOforever. - Pre-hello peers receive nothing but
hello_ack/ errors (closes the unauthenticated feed). pending_transfersget a timeout: restore the entity atfrom_cell(010’s restore path) and sendportal_blocked {TIMEOUT}.- The snapshot ack wedge (
pending_ack_seq): retry on a timer; never block persistence forever on one seq. area_closing {reason}to every peer, then a grace, before anyquit()(shutdown, retry exhaustion, control error) — and the control-errorquit(1)guarded to the stale-spawnToken case only.area_snapshot(or a periodicarea_presence) carriesconnectedPeers.
Director (game/director/src/areaInstance.ts, server.ts):
shutdownInstanceonly when the world reports zero connected peers; the refcount becomes advisory (001’s grace stays as belt-and-braces); portal arrival is covered by that rule.- A
closehandler and a heartbeat on the control listener →instance.registered = false,sendToAreareturns false and callers answer the client at once (GRANT_FAILEDnow, not after a 5 s stall). - Every portal-transfer failure branch replies to the origin (
portal_transfer_rejected {reason}), the exception catch included. - Join-grant TTL 30 s → 90 s; the
completedTransferscache bounded and expiring.
Definition of done
area-server.test.ts: three intents in one tick → three answers; put_packet failure drops
the peer and keeps the body; an unbound peer is told; TAKEN_OVER reaches a Godot client
before the close. Director tests: peer-count-based shutdown; portal-arrival presence;
resume_portal idempotence; control-close handling; every portal failure branch replies;
area_closing reaches peers. All existing suites and verifiers green.
Where the work lands
land-of-lor/game/world/area_server.gd, game/director/src/, game/director/test/.
What was done (2026-08-18, laptop session)
area_server.gd: per-entity intent queue (one applied per tick,TOO_FASTpast eight,ENTITY_GONE,PORTAL_IN_PROGRESSfor moves queued behind a door or sent mid-transfer);REHELLOto a peer unbound by_drop_other_entities_for_account; broadcast only to bound peers;_sweep_stale_transfers(15 s → restore +portal_blocked {TIMEOUT}); snapshot resend after two intervals unacked;area_closing+ 600 ms grace before anyquit(), control-error quit guarded toINVALID_REGISTRATION; presence (connectedPeers,boundAccounts) on every hello / disconnect / 5 s and on snapshots.- Director:
AreaPresenceschemas;applyPresence;leavekeeps an Area up while the world reports someone else bound and shuts it down on the first empty report; drop-grace cancelled by presence;controlSocketClosed;sendToArea→ bool, grant registration fails fast; every transfer failure repliesINTERNAL; grants 90 s; the completed-transfer cache bounded (500) and expiring (1 h). - Tests: three intents in one tick → three answers +
TOO_FAST; presence keeps an Area up after the director socket drops and shuts it down when the game socket goes;area_closingobserved before a graceful exit; all suites green. - Not done: a test for the
REHELLOunbind path (needs a duplicate-body arrival);put_packetfailure → drop peer (kept at log-first — dropping on a transient full buffer seemed worse than a logged one; revisit in 007’s soak).
View source in repository · also available as raw markdown.