Online saves

Submit authoritative read and commit requests through save.backend, take their answers once, and keep typed character profiles (bind, reward once, level) in the game save.

Source: module-kits:kits/save_online/usage/en.mdView Markdown source

Also read offline — CLI: grip-forge docs save-onlineMCP tool: gripforge_docs { "topic": "save-online" }

Online character persistence (save.online) #

What it is for #

save.online is a thin adapter between a game and an authoritative save host. The host side (save.backend) owns the credentials and the HTTP calls; this kit queues the requests a game submits, polls the backend every update, and hands each answer back exactly once. It also keeps typed character profiles (one per account, bound to a campaign actor, with experience, inventory and a zone) and makes sure a boss reward is granted only once per receipt. It has no display module and no rules of its own about what a character is worth: the starting zone, the level thresholds and the rewards are your configuration.

Turn it on #

gripforge_gamekit_install { project_id: "gkp_…", id: "save.online" }

The kit requires game.engine and provides the exclusive service save.online. A save.backend service must be present in the host that runs the game; without it a submit fails at once and nothing stays pending. The kit has no settings of its own: progression is configured through the configure command below. Do not put credentials in any configuration.

First example #

<!-- test: kits/save_online/tests/save_online.rs::a_submitted_request_stays_pending_until_the_backend_answers_and_is_taken_once --> Submit a read, wait for the backend to answer, take the answer once:

id = save.online.submit({ "op": "read", "account": "a", "character": "c" })   // a request id
save.online.pending()      // [id] until the backend answers
save.online.take(id)       // null while pending
// ... after the backend answers, the event save.online.completed is emitted
save.online.take(id)       // { "ok": true, "value": { ... } }
save.online.take(id)       // null: the answer was consumed

Services #

  • save.online (service), methods:
    • submit(command): sends a command to save.backend and returns its request id. Only the operations read, commit and commitBatch are accepted. At most 64 requests may be pending or waiting to be taken; take results before submitting more.
    • take(request): returns the completed answer once and forgets it; null while the request is pending or unknown. A backend failure completes the request as { "ok": false, "error": "..." }.
    • pending(): the ids still waiting for the backend.
    • character(command): typed profile operations, see below.

Events #

  • save.online.completed (event): emitted once when the backend answers a submitted request. Payload { request, result }; the result is the backend's own document, to be read with take.

Character commands #

character(command) takes an object with an op (unknown fields are refused):

opArgumentsResult
configureconfig with startZone, levelThresholds, rewardsnull; set before binding profiles, a different config afterwards is refused
rosteractors (up to 40 distinct ids)null; the campaign actors profiles can be bound to
bindaccount, character, name{ actor, zone }; one character per account, a repeated bind returns the existing one
hydrateaccount, character, revision, datanull; loads a document read from the backend
zonecharacter, zonenull; moves the character, marking it unsaved
rewardcharacter, receipt: [instance, generation, boss, actor]{ granted, experience, level, inventory }; granted is false for a receipt already seen
awardlegacy form with experience, item, countsame as reward
viewcharactername, zone, experience, level, inventory, revision, saving
actoractorthe character id bound to that actor, or null
listnoneevery { account, character, actor }
pendingnoneprofiles not yet persisted: account, character, expectedRevision, data
ackrevisions (character to new revision)null; marks profiles persisted when each revision is exactly the next one

Configuration limits: levelThresholds is 1 to 100 strictly increasing numbers starting at 0 (default 0, 100, 200, ...), startZone defaults to crypt-1, rewards maps a zone to a boss to { experience, items } (up to 64 zones, 32 bosses per zone, 128 items per reward).

The usual loop: pending gives the documents to persist, you send them with submit as a commit whose expectedRevision is the one returned, and when the answer is a success you call ack with the new revisions.

Collections #

  • online_worlds: authoritative online world documents (characters, zones and rewards) kept through the backend.

Save and rollback #

The roster, the configuration and the profiles are saved with the game, and a reload keeps rewards from being granted twice. Pending requests, completed answers and credentials are never saved: after a reload pending() is empty. A game without a roster saves nothing from this kit.

When it does not do what you expect #

  • submit fails with "only read/commit/commitBatch may be submitted": other operations (save, load, list) are not allowed.
  • submit fails with "consume completed results before submitting more": 64 requests are pending or untaken; call take.
  • submit fails right away: no save.backend is installed in the host.
  • take returns null: the backend has not answered yet, or the answer was already taken.
  • reward returns granted: false: that receipt was already applied. An error "receipt recipient mismatch" means the actor in the receipt is not the one bound to the character.
  • configure fails with "progression configuration mismatch": profiles already exist; changing the progression needs an explicit save migration.
  • bind fails with "persistent campaign roster full": every actor of the roster already has a character.

Limits #

  • It does not speak HTTP and holds no credentials; the backend does.
  • No gameplay rollback of network state, and no display.
  • Only read and commit style requests can be submitted.

Versions #

Kit version 1.0.0, the first delivery of the online character profiles. It relies on game.engine 1.x.