Distributing it

Persisting players

Who a player is, and what is kept for them between visits.

Who a player is comes from the accounts addon, and what is kept for them from persist, which loads records into a truth as rows and writes them back as they change.

The manager scene

  1. In games/<game>/<game>.scene.json, give world.accounts: name { min, max, pattern }, secret { min, max }, scrypt { N, r, p, keylen, salt }, token { bytes, ttl } and limit { connection, name, window }. Every child scene reads the same rules, so a login screen can say a name is too short before it asks.
  2. Give world.persist: kinds, each kind's { key, load }; every, the fewest steps between two writes of one row; file, the store's name; and busy, the milliseconds a write on node waits for another. key is uid. load is boot for records loaded when the scene starts, or claim for records loaded when a rule asks.
  3. Add the persist prefab, { "claims": {} }, and add the login scene to scenemanager.scenes as its entry.
  4. Name file after the game: two games on one origin with one name share one database. On node "file": "<game>.db" lands at the repository root with <game>.db-wal and <game>.db-shm beside it; add all three to .gitignore.

The login screen

  1. Make the entry scene's login kind: <login>.scene.json, <login>.sim.ts, and the kind's login.sim.ts and login.client.ts, with its words in its definition in assets/<game>.json, drawn on a ui panel.
  2. login.client.ts asks relogin(link) once with the kept token. With no token, or one refused, it shows a form that calls register(link, name, secret) or login(link, name, secret).
  3. Each ask returns { account } or { why, wait? }. Give the screen words for each why: wrong, taken, name, secret, token, slow (wait is the milliseconds until one more ask), busy, signed, down, elsewhere and closed.
  4. login.sim.ts sends the seat on with the scenemanager's go, to the scene its definition names as next.
  5. When the account logs in on another connection, this one is told elsewhere and logged out. Every scene the seat can reach shows that and offers to log in again; it asks only when the player says so.

A connection is seated on the entry scene and sent its packets from the first step, logged in or not. Anything else it sends before it logs in is dropped. In index.html every connection is logged in at once as local, and its records are kept in IndexedDB.

The scene that keeps records

  1. Add the actor { "is": "persist" } to the scene file. Its sim imports the kind from #plugins/persist/persist.sim.ts and names it with each record kind.
  2. Give the player prefab a field that says it has asked, and every field of the record it plays with (a level, gold, an inventory) at the values a new account starts with.
  3. The player row's before calls claim(w, kind, match) and marks that it asked. On a later step it finds the record with w.all(kind) and copies its fields onto itself, or spawns the record from its own fields when there is none.
  4. From then the player row plays on its own fields. Each change to a kept field is written back through an act of the record's kind that lists no open, so no seat can send it.
  5. List every field only the owner may see in the scene's security block under me, on the record and on the player row.

When records are written

RowWritten
Changed since its last writeAt most once every every steps
Spawned by this truthIn the step it was spawned
Loaded, then changedAt once
urgent: true in its dataThat step, and the flag is deleted
EndedA last time, or dropped when its data has forget: true
Any changed row after a seat closedThe next step

One step's writes go in one transaction. A rule sets urgent on every row a transfer touches. Records name each other and accounts by uid, never by row id or name.

The store and local keep nothing for a player: the store holds page-only values such as volume and rebound keys, and starts again on a reload.

Checking it

  1. games/<game>/<game>.sim.test.ts boots the manager over sqlite in a temporary file and checks that a logged-in seat moves on, that its player row holds the record's fields, that a visit is on disk and counts again on the next visit, and that a seat sending the write-back act changes nothing.
  2. games/<game>/<game>.server.test.ts runs the server twice with a stop between, and checks that a reloaded page logs back in with its token and the count goes on after the restart.
  3. Play index.html and see the count go up by one on each reload. Play client.html against the server: register, reload, restart the server, and log the account in from a second window to see the first told it was logged in elsewhere.