Skip to content

04 — Offline Progress and Persistence

Goal

Understand how Crafting jobs can be saved, restored, and advanced using real-world elapsed time.

This scene teaches the offline progress lifecycle:

Inspect live jobs → Save snapshots → Restore later → Let CraftingService decide the outcome


What This Scene Demonstrates

This scene shows how CraftingService can capture and restore active crafting jobs.

You’ll see how to:

  • inspect live jobs for an owner
  • save active jobs to JSON using SaveActiveJobs()
  • inspect the saved snapshot payload
  • restore jobs using RestoreJobs()
  • simulate offline time by aging acceptedAtUtc
  • observe whether restored jobs resume, complete, or fail

The key lesson is:

Offline progress is not a magic save system.

CraftingService provides job snapshot and restore behaviour.

Your game still owns the actual persistence layer.


What To Look For

The panel is organised around three tabs:

  • Setup shows the service, owner, saved JSON state, and offline configuration ownership.
  • Jobs & Save shows active jobs, lets you save snapshots, clear active jobs, and configure simulated offline time.
  • Restore & JSON shows the raw snapshot payload and restore controls.

The important flow is:

  1. Start one or more real crafting jobs.
  2. Save active jobs to JSON.
  3. Store or copy that JSON.
  4. Restore the JSON later.
  5. Let CraftingService decide whether each job resumes, completes, or fails.

Persistence Responsibility

RevFramework does not provide a full save system.

The Crafting system exposes snapshot APIs:

  • SaveActiveJobs(...)
  • RestoreJobs(...)

Your game is responsible for:

  • save slots
  • files
  • cloud storage
  • profiles
  • platform persistence
  • stable owner IDs
  • stable recipe IDs
  • resolving those IDs on restore
  • deciding when save and restore happen

This scene demonstrates the snapshot pattern only.

It is not a production save-slot, cloud-save, profile, or persistence framework.


Offline Progress Model

Offline progress depends on the current CraftingService offline configuration at restore time.

At a high level:

  • Offline Progress OFF — restore behaves more like saved remaining-time restoration.
  • Offline Progress ON — restore can compare saved acceptedAtUtc with the current UTC wall clock.
  • If enough time has elapsed, a job may complete immediately during restore.
  • Completion and delivery can still fail if normal delivery rules fail.

The same snapshot can behave differently if the service settings change before restore.

Offline settings live on CraftingService, not on this teaching panel.


Simulated Offline Time

The panel includes an Advance Seconds field and a Simulate Offline + Restore action.

This is a teaching shortcut.

It simulates time away by shifting acceptedAtUtc backward before restore.

That lets you test offline completion without actually closing the application and waiting.

Important:

  • this is not a real app lifecycle simulation
  • it only changes visible behaviour when offline progress is enabled on the service
  • restore still flows through normal service logic
  • restore-time completion can still fail

Sample Scope

This is a demo scene, not a production persistence system.

It focuses on:

  • active job inspection
  • snapshot JSON
  • owner/recipe ID mapping callbacks
  • restore behaviour
  • simulated offline time
  • service-owned offline settings
  • delivery success or failure after restore

It does not focus on:

  • production save slots
  • cloud saves
  • account profiles
  • cross-device sync
  • database persistence
  • multiplayer reconciliation
  • custom platform storage

Those are your game’s responsibility.


How To Use

  1. Enter Play Mode.
  2. Seed the required items/currency using the sample panels if needed.
  3. Start one or more crafting jobs, for example through the Quickstart panel.
  4. Open or view the CraftingOfflineProgressPanel.
  5. Use the Setup tab to confirm the service and owner bindings.
  6. Use Jobs & Save to inspect active jobs.
  7. Click Save Active to capture the active jobs into JSON.
  8. Use the snapshot summary to inspect what was saved.
  9. Use Restore & JSON to inspect, copy, paste, or edit the raw payload.
  10. Click Restore to restore the current JSON as-is.
  11. Click Simulate Offline + Restore to age the snapshot first, then restore.
  12. Watch whether restored jobs resume, complete immediately, or fail through normal service behaviour.

Failure Behaviour

Failures are intentional teaching signals in this scene.

Common examples:

  • no active jobs → there is nothing to save
  • empty JSON → restore cannot run
  • invalid JSON → payload cannot be parsed
  • missing job list → payload is not a valid snapshot wrapper
  • delivery failure → restored completion still failed normal service delivery rules
  • no output space → completed job cannot deliver its outputs

The important rule is:

Restore is not a bypass.

Restored jobs still go through CraftingService logic.

They can resume, complete, or fail depending on the current service state and configured rules.


What Restore Does Not Tell You

The list above is what the panel can actually show you. Two more things happen during a restore, and neither of them is a failure signal at all — which is precisely the problem.

Two entries a reader would reasonably expect in that list are deliberately absent from it: an unresolved owner ID and an unresolved recipe ID. Neither produces a signal of any kind. They are the silent cases, and they are the first thing below.

A job it cannot identify is dropped in silence

RestoreJobs skips a snapshot whose owner ID or recipe ID will not resolve. Not with a warning — with a bare continue, and nothing in the Console. It then returns void, so there is no count, no result, and no report either.

An unresolved owner or recipe therefore does not make the restore fail. The call succeeds, the panel reports success, and the job is simply gone — along with the inputs and currency it consumed when it was accepted, spent long before the save was written and not refunded.

The panel says so rather than implying a signal exists, because through the public API there genuinely isn't one. Comparing the Saved Snapshot Summary against Live Jobs is the closest you can get, and even that is not a drop count: a job missing from the live list may have been dropped, or may have completed during offline reconcile. Both are normal outcomes of the same call.

If your game needs to know, resolve the IDs yourself before calling and compare the counts. That is what CraftingSaveParticipant does, and it is why a restore through the save coordinator can report the loss as a failed section while this panel cannot.

RestoreJobs does warn for two other skips — an unsupported schema version, and an already-applied completion — but those warnings are compiled out of release builds.

A completion it has already applied is skipped

CraftingService remembers which completion ids it has applied, so replaying the same snapshots does not deliver twice. RestoreJobs skips any snapshot whose completionTxnId is already in that record.

That record is in memory, and a load does not rewind it. So:

  1. Start a craft.
  2. Save the snapshot while it is still running.
  3. Let it finish live — the completion id is now recorded.
  4. Restore the snapshot you took in step 2.

The job is skipped as already-applied, while everything a real load rewinds around it — inventory, currency — has gone back to before it finished. The player loses the inputs, the outputs and the job. Quickloading mid-craft is not an edge case.

clearExisting: true does not help. That clears the job list; the completion record is a separate thing.

This skip is the one that does log — Restore skipped already-applied completion — but only in the editor and development builds, and the line says a completion was skipped, not that a player lost a craft.

Call ClearAppliedCompletions() before a full-world load. CraftingSaveParticipant makes that call for you before every restore. This scene drives RestoreJobs directly, which is exactly the case CraftingService documents as the caller's own responsibility — so here it is yours. The panel deliberately does not make the call, so the skip above is reproducible rather than only described.


Clear Active Behaviour

The Clear Active button is a cleanup helper.

It calls CancelJob(..., refund: true) for active jobs owned by the current owner.

Refund behaviour still depends on the service and adapters.

This is not a production save-system rule.


Authority Note

This scene may include a local sample authority setup configured to allow crafting.

That means:

  • crafting behaves permissively in this sample
  • authority is not the focus of this scene
  • this is not a multiplayer or server-authority implementation

To enforce real authority:

  • provide an ICraftingAuthority implementation
  • wire it into CraftingService
  • do not rely on simply adding a component to the scene

Networking Reminder

RevFramework does not implement:

  • networking
  • replication
  • prediction
  • rollback
  • reconciliation

These samples demonstrate local behaviour and persistence patterns only.

Cross-device sync and multiplayer behaviour are your responsibility.


Behind The Scenes

The panel uses public CraftingService APIs only, including:

  • SaveActiveJobs(...)
  • RestoreJobs(...)
  • GetJobs(...)
  • CancelJob(...)

Snapshot save/restore uses public mapping callbacks:

  • owner ID selector
  • recipe ID selector
  • owner resolver
  • recipe resolver

The panel does not:

  • reflect into private fields
  • mutate private offline configuration
  • bypass delivery rules
  • pretend to be a production save system

Restore-time completion still flows through normal service logic.


Recipe Type Used In This Scene

This scene may use RecipeCore assets directly to keep snapshot/restore behaviour explicit.

RecipeCore is the runtime recipe format used by CraftingService.

Wrapper assets are authoring conveniences that resolve into RecipeCore at runtime.

Using RecipeCore directly in this scene keeps the persistence example easier to inspect and debug.

Important:

  • this is not a requirement for using persistence
  • wrapper-based recipes can still be used if they resolve correctly in your project
  • your own project should use stable recipe IDs suitable for your save system

Key Takeaway

Offline progress is based on saved job snapshots and real elapsed time.

When a player returns, restored jobs may:

  • resume
  • complete immediately
  • fail during restore-time delivery

The Crafting system provides the job snapshot and restore API.

Your game owns persistence, storage, stable IDs, and restore timing.

That separation is the point of this scene.


Panel: CraftingOfflineProgressPanel Scene Path: Assets/RevFramework/Samples/Systems/Crafting/Scenes/04_Offline_Progress_Persistence/