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.
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 tosave.backendand returns its request id. Only the operationsread,commitandcommitBatchare 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;nullwhile 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 withtake.
Character commands #
character(command) takes an object with an op (unknown fields are refused):
op | Arguments | Result |
|---|---|---|
configure | config with startZone, levelThresholds, rewards | null; set before binding profiles, a different config afterwards is refused |
roster | actors (up to 40 distinct ids) | null; the campaign actors profiles can be bound to |
bind | account, character, name | { actor, zone }; one character per account, a repeated bind returns the existing one |
hydrate | account, character, revision, data | null; loads a document read from the backend |
zone | character, zone | null; moves the character, marking it unsaved |
reward | character, receipt: [instance, generation, boss, actor] | { granted, experience, level, inventory }; granted is false for a receipt already seen |
award | legacy form with experience, item, count | same as reward |
view | character | name, zone, experience, level, inventory, revision, saving |
actor | actor | the character id bound to that actor, or null |
list | none | every { account, character, actor } |
pending | none | profiles not yet persisted: account, character, expectedRevision, data |
ack | revisions (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 #
submitfails with "only read/commit/commitBatch may be submitted": other operations (save, load, list) are not allowed.submitfails with "consume completed results before submitting more": 64 requests are pending or untaken; calltake.submitfails right away: nosave.backendis installed in the host.takereturnsnull: the backend has not answered yet, or the answer was already taken.rewardreturnsgranted: 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.configurefails with "progression configuration mismatch": profiles already exist; changing the progression needs an explicit save migration.bindfails 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.