M1 — World & character persistence #3

Closed
opened 2026-07-16 23:04:47 +02:00 by marco · 1 comment
Owner

Make the world survive a server restart. Today World state is in-memory only, so a restart wipes every character's position. First step from "movement demo" to "MMO".

Scope

  • Persist each character's last position (and minimal world state) to the DB.
  • Load the saved position on login/spawn instead of a fixed start tile.
  • Periodic async snapshot of dirty state — never inside the tick loop (no-DB-in-tick invariant).
  • Flush on graceful shutdown.
  • Dual-provider EF migrations (SQLite + Postgres) for any new schema.

Open questions

  • Where does character position live: extend the Auth character store, or a GameServer-owned store? Auth and GameServer deploy separately — the choice must respect that.

Definition of Done

Base DoD applies on top (tests green, whole solution builds, zero warnings, multi-platform preserved); the criteria below are the issue-specific delta.

  • A character moves, disconnects, the server is restarted, reconnects, and reappears at its last position — demonstrated across two restart cycles.
  • The tick loop contains no persistence calls (persistence is periodic/async) — shown in code + a timing/log check.
  • Migrations exist for both providers and Database.Migrate() applies cleanly on SQLite and Postgres.
Make the world survive a server restart. Today `World` state is in-memory only, so a restart wipes every character's position. First step from "movement demo" to "MMO". ## Scope - [ ] Persist each character's last position (and minimal world state) to the DB. - [ ] Load the saved position on login/spawn instead of a fixed start tile. - [ ] Periodic **async** snapshot of dirty state — never inside the tick loop (no-DB-in-tick invariant). - [ ] Flush on graceful shutdown. - [ ] Dual-provider EF migrations (SQLite + Postgres) for any new schema. ## Open questions - Where does character position live: extend the Auth character store, or a GameServer-owned store? Auth and GameServer deploy separately — the choice must respect that. ## Definition of Done _Base DoD applies on top (tests green, whole solution builds, zero warnings, multi-platform preserved); the criteria below are the issue-specific delta._ - [ ] A character moves, disconnects, the **server is restarted**, reconnects, and reappears at its last position — demonstrated across two restart cycles. - [ ] The tick loop contains no persistence calls (persistence is periodic/async) — shown in code + a timing/log check. - [ ] Migrations exist for both providers and `Database.Migrate()` applies cleanly on SQLite and Postgres.
Author
Owner

Done and merged to main (commit ffef976).

The GameServer now owns a dual-provider EF store (GameDbContext, SQLite dev / Postgres prod, both migration sets) for player positions. Position is loaded on connect, saved on disconnect and by a periodic PersistenceService (~15s) plus a shutdown flush — all off the tick loop. World.AddPlayer takes a start position; TryGetPosition exposes the current one.

Verified: an integration test (PlayerPosition_PersistsAcrossReconnect) plus a manual smoke test where a character moved, the server was restarted, and re-login placed the character back at its saved tile (not the origin). Whole solution builds zero-warning, 107 tests green, and IsoMmo.Shared (the wire contract) is untouched.

Done and merged to `main` (commit ffef976). The GameServer now owns a dual-provider EF store (`GameDbContext`, SQLite dev / Postgres prod, both migration sets) for player positions. Position is loaded on connect, saved on disconnect and by a periodic `PersistenceService` (~15s) plus a shutdown flush — all off the tick loop. `World.AddPlayer` takes a start position; `TryGetPosition` exposes the current one. Verified: an integration test (`PlayerPosition_PersistsAcrossReconnect`) plus a manual smoke test where a character moved, the server was restarted, and re-login placed the character back at its saved tile (not the origin). Whole solution builds zero-warning, 107 tests green, and `IsoMmo.Shared` (the wire contract) is untouched.
marco closed this issue 2026-07-17 06:55:48 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marco/IsoMmo#3
No description provided.