Story campaign
story-campaign · an adoption of State persistence scope, version 2
Use in your package
The first button opens the authoring tool with these answers and numbers already filled in. You still add the rules and tuning of your own game.
What is in the zip file
The zip file holds the adoption and its acceptance tests. In the authoring tool, choose Add contract and pick this zip file. If you edit your package outside the authoring tool, unpack the zip file in your package folder. The files of the zip file go into contracts/.
The contract that this adoption uses
This contract covers which state is kept after the end of a run or session, a quit, a crash, death, the deletion of saved data, and a move to another device. It also covers local saves, manual slots, cloud copies, server profiles, backups, conflicts, and version changes. This contract does not cover time that passes while the game is closed, replay guarantees, file formats, or the shape of saved numbers. The Suspension and catch-up contract covers time while the game is closed. The Replay scope contract covers replay guarantees.
Questions
The supplied answer is marked on each question. Pick other answers to see what changes. Nothing is saved here: the zip file and the authoring tool use the supplied answers.
By default, what happens when saved state cannot be read?
- Asked when
- the copy kinds list has at least one row.
- If not asked
- No saved copy can be opened later, so unreadable saved state never reaches a load attempt.
Why this is asked
Lost progress is the most visible failure of a save system. This answer is the default answer. A save kind can choose another answer in its own row.
By default, what happens when an older game build wrote the copy?
- Asked when
- the copy kinds list has at least one row.
- If not asked
- No loadable copy exists, so an older build has no saved state to open.
Why this is asked
An update can make a player's progress impossible to load, or change it permanently. Choose how the current build protects that progress.
By default, what happens when a newer game build wrote the copy?
- Asked when
- the copy kinds list has at least one row.
- If not asked
- No loadable copy exists, so a newer build has no saved state to open.
Why this is asked
Going back to an older build can erase newer content. Choose whether the player may take that risk.
Numbersno numbers
This contract has no numbers to set.
Rulesno rules
A rule is a check between the numbers. Validation reports a rule that fails.
This contract has no rules between its numbers.
Lists5 lists
Each list holds the rows this adoption supplies. A list can be empty.
State families
state-families
| Id | State declared in | Lifetime | Scope starts in | Scope ends in | Lifetime event declared in | Saved in | After normal quit | After crash | After death | After slot delete | After local profile delete | On another device |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
campaign-progress | campaign.progress-fields | slot | — | save-data.delete-campaign-slot | — | campaign-slots | returns-last-commit | returns-last-commit | keeps-value | clears-value | no-profile | does-not-return |
dungeon-attempt | dungeon.attempt-fields | run | dungeon.enter | dungeon.victory-death-or-abandonment | — | campaign-slots | returns-last-commit | returns-last-commit | clears-value | clears-value | no-profile | does-not-return |
session-map-hints | map.session-hint-fields | session | map.open-campaign | map.leave-campaign | — | settings-file | returns-last-commit | returns-last-commit | keeps-value | no-slot | no-profile | does-not-return |
unlocked-cosmetics | cosmetics.unlock-fields | profile | — | profile.delete-or-reset | — | campaign-slots | returns-last-commit | returns-last-commit | keeps-value | keeps-value | clears-value | does-not-return |
Copy kinds
copy-kinds
| Id | Contains declared in | Write trigger | Write rule declared in | Residence | Layout | Storage rule declared in | Unreadable copy | Older build copy | Newer build copy | Mode gated | Mode rule declared in | Removed at | Removal rule declared in |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
campaign-slots | save-data.campaign-contents | checkpoint | save-data.campaign-checkpoints | local-device | manual-slots | save-data.campaign-slot-layout | warn-and-try | warn-and-load | use-game-default | — | — | — | — |
settings-file | settings.session-hint-contents | timer | settings.timed-write | local-device | one-current | settings.current-file | start-fresh | use-game-default | warn-and-load | — | — | — | — |
Backup copies
backup-copies
| Id | Copy kind | Backup rule declared in | Restore rule declared in |
|---|---|---|---|
previous-campaign-checkpoint | campaign-slots | save-data.previous-checkpoint-policy | menus.restore-previous-checkpoint |
Copy conflicts
copy-conflicts
This adoption declares no copy conflicts.
Earlier point returns
earlier-point-returns
| Id | Copy kind | After load | Return rule declared in | Mode gated | Mode rule declared in |
|---|---|---|---|---|---|
dungeon-retry | campaign-slots | resume-is-used-once | dungeon.retry | — | — |
Test inputsscope and seeds
Some tests need a scope or seeds from the adoption before they can run.
This adoption supplies no test inputs. Every test uses its default inputs.
Acceptance tests88 tests apply
The contract comes with 85 tests. A test that runs once per row is counted once for each row. Tests that do not apply to these answers are still listed, with the reason.
run-scope-uses-cited-events · dungeon-attempt
dungeon-attempt begins and ends its run only at the cited events
Applies to the dungeon-attempt row.
The run for dungeon-attempt begins at dungeon.enter and ends at dungeon.victory-death-or-abandonment. Each save, automatic write, timer tick, load, close, or relaunch the adoption can construct does not create either boundary. For an event that the adoption cannot construct, this test checks nothing. The test names the addresses and restates nothing from them.
Test steps and diagnostics
- Given
a distinguishable
dungeon-attemptvalue before, during, and after its run- When
- play crosses the attempt start at
dungeon.enter, the run ending atdungeon.victory-death-or-abandonment, and each save, automatic write, timer tick, load, process close, and relaunch to the same context that the adoption can construct
- play crosses the attempt start at
- Then
- the run identity begins only at
dungeon.enterand ends only atdungeon.victory-death-or-abandonment - each constructed save, automatic write, timer tick, load, close, and relaunch does not by itself begin or end the run; for an event that the adoption cannot construct, this test checks nothing
- the run identity begins only at
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-run-identity
session-scope-uses-cited-events · session-map-hints
session-map-hints keeps one session through returns to the same context
Applies to the session-map-hints row.
The session for session-map-hints begins at map.open-campaign, continues through each constructible save, automatic write, timer tick, load, pause, background interval, closure, relaunch to the same context, and another family's run boundary, and ends at map.leave-campaign. For an event or a run boundary that the adoption cannot construct, this test checks nothing. The family's value still needs the declared commit and boundary result to return. The test names the addresses and restates nothing from them.
Test steps and diagnostics
- Given
a distinguishable
session-map-hintsvalue in the session that starts atmap.open-campaign- When
- play pauses, enters the background, crosses each constructed save, automatic write, timer tick, and load, closes, relaunches to the same context, and finally crosses
map.leave-campaign - where the adoption declares a state family with the lifetime run, play also crosses the run boundary of that family
- play pauses, enters the background, crosses each constructed save, automatic write, timer tick, and load, closes, relaunches to the same context, and finally crosses
- Then
- pause, background, closure, and relaunch to the same context retain one session identity
- each constructed save, automatic write, timer tick, load, close, and relaunch does not by itself begin or end the session; for an event that the adoption cannot construct, this test checks nothing
- crossing a run boundary declared by another family does not by itself end this session; where the adoption declares no state family with the lifetime run, this test checks nothing about a run boundary
- the session ends only at
map.leave-campaign; whether its earlier value returns still follows its commit and boundary results
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-session-identity
copy-carries-complete-identity · campaign-slots
campaign-slots copies carry their complete identity and their complete state-family set
Applies to the campaign-slots row.
A copy of save kind campaign-slots is one coherent accepted commit with save-kind, copy, slot when present, build, and commit identities and the complete family set at save-data.campaign-contents. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
one accepted commit of the save kind named
campaign-slots- When
- the copy is recorded and later considered as a candidate
- Then
- the copy record carries save-kind id, copy identity, any slot identity, build identity, commit identity, and the complete state-family set named at
save-data.campaign-contents - the commit record identifies one coherent accepted write rather than a collection of separately accepted fields
- the copy record carries save-kind id, copy identity, any slot identity, build identity, commit identity, and the complete state-family set named at
- Diagnostics
story-campaign-commit-logstory-campaign-candidate-set
copy-carries-complete-identity · settings-file
settings-file copies carry their complete identity and their complete state-family set
Applies to the settings-file row.
A copy of save kind settings-file is one coherent accepted commit with save-kind, copy, slot when present, build, and commit identities and the complete family set at settings.session-hint-contents. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
one accepted commit of the save kind named
settings-file- When
- the copy is recorded and later considered as a candidate
- Then
- the copy record carries save-kind id, copy identity, any slot identity, build identity, commit identity, and the complete state-family set named at
settings.session-hint-contents - the commit record identifies one coherent accepted write rather than a collection of separately accepted fields
- the copy record carries save-kind id, copy identity, any slot identity, build identity, commit identity, and the complete state-family set named at
- Diagnostics
story-campaign-commit-logstory-campaign-candidate-set
conflict-has-two-unreplaced-accepted-copies
Row.id is a conflict only for two accepted unreplaced copies
Does not apply to story-campaign: copy-conflicts has no rows.
state-family-follows-maximum-lifetime · campaign-progress
campaign-progress follows its declared maximum lifetime
Applies to the campaign-progress row.
campaign-progress has a maximum lifetime of one player-selected slot. Each boundary before the end of that lifetime follows the row's result, and the trace distinguishes a restored value from an equal value derived later. The family definition is at campaign.progress-fields; the test names the address and restates nothing from it.
Test steps and diagnostics
- Given
campaign-progresschanges from an earlier value to a distinguishable value- When
- the value is read immediately before and after every declared persistence boundary that
story-campaigncan construct
- the value is read immediately before and after every declared persistence boundary that
- Then
- the earlier value affects play for no longer than one player-selected slot
- a boundary before the end of that lifetime clears it exactly when
campaign-progress's selected boundary result says so - the trace identifies restoration of the earlier value separately from a later derivation of an equal value
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-source
state-family-follows-maximum-lifetime · dungeon-attempt
dungeon-attempt follows its declared maximum lifetime
Applies to the dungeon-attempt row.
dungeon-attempt has a maximum lifetime of one run. Each boundary before the end of that lifetime follows the row's result, and the trace distinguishes a restored value from an equal value derived later. The family definition is at dungeon.attempt-fields; the test names the address and restates nothing from it.
Test steps and diagnostics
- Given
dungeon-attemptchanges from an earlier value to a distinguishable value- When
- the value is read immediately before and after every declared persistence boundary that
story-campaigncan construct
- the value is read immediately before and after every declared persistence boundary that
- Then
- the earlier value affects play for no longer than one run
- a boundary before the end of that lifetime clears it exactly when
dungeon-attempt's selected boundary result says so - the trace identifies restoration of the earlier value separately from a later derivation of an equal value
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-source
state-family-follows-maximum-lifetime · session-map-hints
session-map-hints follows its declared maximum lifetime
Applies to the session-map-hints row.
session-map-hints has a maximum lifetime of one session. Each boundary before the end of that lifetime follows the row's result, and the trace distinguishes a restored value from an equal value derived later. The family definition is at map.session-hint-fields; the test names the address and restates nothing from it.
Test steps and diagnostics
- Given
session-map-hintschanges from an earlier value to a distinguishable value- When
- the value is read immediately before and after every declared persistence boundary that
story-campaigncan construct
- the value is read immediately before and after every declared persistence boundary that
- Then
- the earlier value affects play for no longer than one session
- a boundary before the end of that lifetime clears it exactly when
session-map-hints's selected boundary result says so - the trace identifies restoration of the earlier value separately from a later derivation of an equal value
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-source
state-family-follows-maximum-lifetime · unlocked-cosmetics
unlocked-cosmetics follows its declared maximum lifetime
Applies to the unlocked-cosmetics row.
unlocked-cosmetics has a maximum lifetime of one local player profile. Each boundary before the end of that lifetime follows the row's result, and the trace distinguishes a restored value from an equal value derived later. The family definition is at cosmetics.unlock-fields; the test names the address and restates nothing from it.
Test steps and diagnostics
- Given
unlocked-cosmeticschanges from an earlier value to a distinguishable value- When
- the value is read immediately before and after every declared persistence boundary that
story-campaigncan construct
- the value is read immediately before and after every declared persistence boundary that
- Then
- the earlier value affects play for no longer than one local player profile
- a boundary before the end of that lifetime clears it exactly when
unlocked-cosmetics's selected boundary result says so - the trace identifies restoration of the earlier value separately from a later derivation of an equal value
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-source
until-event-value-ends-at-cited-event
Row.id ends at its cited event
Does not apply to story-campaign: no row in state-families matches lifetime until event.
not-kept-value-never-returns
Row.id never returns after its named interruption
Does not apply to story-campaign: no row in state-families matches lifetime not kept.
slot-and-profile-end-at-cited-event · campaign-progress
campaign-progress ends at its cited slot boundary
Applies to the campaign-progress row.
The maximum lifetime of campaign-progress ends at save-data.delete-campaign-slot. An equal value derived later is new state. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
a distinguishable
campaign-progressvalue beforesave-data.delete-campaign-slot- When
- play crosses the cited lifetime ending
- Then
- the earlier value reaches the end of its maximum lifetime at
save-data.delete-campaign-slotand cannot affect later play - the source record distinguishes an equal value derived later as new state
- the earlier value reaches the end of its maximum lifetime at
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-source
slot-and-profile-end-at-cited-event · unlocked-cosmetics
unlocked-cosmetics ends at its cited profile boundary
Applies to the unlocked-cosmetics row.
The maximum lifetime of unlocked-cosmetics ends at profile.delete-or-reset. An equal value derived later is new state. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
a distinguishable
unlocked-cosmeticsvalue beforeprofile.delete-or-reset- When
- play crosses the cited lifetime ending
- Then
- the earlier value reaches the end of its maximum lifetime at
profile.delete-or-resetand cannot affect later play - the source record distinguishes an equal value derived later as new state
- the earlier value reaches the end of its maximum lifetime at
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-source
commit-starts-only-at-selected-trigger · campaign-slots
campaign-slots starts a commit only at its selected trigger
Applies to the campaign-slots row.
A commit for campaign-slots begins only at the cited checkpoint event under save-data.campaign-checkpoints. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
events around the write rule at
save-data.campaign-checkpoints- When
- the events occur before, at, and after the cited checkpoint event
- Then
- one
campaign-slotscommit starts at the cited checkpoint event and no commit starts at the test events before or after it - the commit log records which cited event started it
- one
- Diagnostics
story-campaign-commit-log
commit-starts-only-at-selected-trigger · settings-file
settings-file starts a commit only at its selected trigger
Applies to the settings-file row.
A commit for settings-file begins only at the cited timer interval under settings.timed-write. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
events around the write rule at
settings.timed-write- When
- the events occur before, at, and after the cited timer interval
- Then
- one
settings-filecommit starts at the cited timer interval and no commit starts at the test events before or after it - the commit log records which cited event started it
- one
- Diagnostics
story-campaign-commit-log
commit-loadable-only-after-complete-success · campaign-slots
campaign-slots replaces its last commit only after complete success
Applies to the campaign-slots row.
campaign-slots becomes loadable only when save-data.campaign-checkpoints reports success for the complete family set at save-data.campaign-contents. A failed or partial write leaves the last successful commit in place. The test names both addresses and restates nothing from them.
Test steps and diagnostics
- Given
a last successful
campaign-slotscommit and later complete-success, failed, and partial write attempts undersave-data.campaign-checkpoints- When
- each attempt is followed by a return that requests
campaign-slots
- each attempt is followed by a return that requests
- Then
- only the attempt reported successful for the complete set at
save-data.campaign-contentsbecomes loadable - a failed or partial attempt never replaces the last successful commit
- the commit and load records distinguish the retained last commit from the attempts that did not succeed
- only the attempt reported successful for the complete set at
- Diagnostics
story-campaign-commit-logstory-campaign-load-resultstory-campaign-active-state-source
commit-loadable-only-after-complete-success · settings-file
settings-file replaces its last commit only after complete success
Applies to the settings-file row.
settings-file becomes loadable only when settings.timed-write reports success for the complete family set at settings.session-hint-contents. A failed or partial write leaves the last successful commit in place. The test names both addresses and restates nothing from them.
Test steps and diagnostics
- Given
a last successful
settings-filecommit and later complete-success, failed, and partial write attempts undersettings.timed-write- When
- each attempt is followed by a return that requests
settings-file
- each attempt is followed by a return that requests
- Then
- only the attempt reported successful for the complete set at
settings.session-hint-contentsbecomes loadable - a failed or partial attempt never replaces the last successful commit
- the commit and load records distinguish the retained last commit from the attempts that did not succeed
- only the attempt reported successful for the complete set at
- Diagnostics
story-campaign-commit-logstory-campaign-load-resultstory-campaign-active-state-source
normal-quit-final-commit-follows-write-rule · campaign-slots
campaign-slots makes a final quit commit only when its rule requires one
Applies to the campaign-slots row.
On a normal quit, campaign-slots makes a final commit only when save-data.campaign-checkpoints requires one. Otherwise the earlier successful commit remains the last commit. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
each normal-quit case the adoption can construct for changed
campaign-slotsstate undersave-data.campaign-checkpoints- When
- each normal quit completes
- Then
- where
save-data.campaign-checkpointsrequires a final commit, exactly one starts - where the rule does not require one, no final commit starts and the previous last successful commit remains; for a case that the adoption cannot construct, this test checks nothing
- where
- Diagnostics
story-campaign-commit-log
normal-quit-final-commit-follows-write-rule · settings-file
settings-file makes a final quit commit only when its rule requires one
Applies to the settings-file row.
On a normal quit, settings-file makes a final commit only when settings.timed-write requires one. Otherwise the earlier successful commit remains the last commit. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
each normal-quit case the adoption can construct for changed
settings-filestate undersettings.timed-write- When
- each normal quit completes
- Then
- where
settings.timed-writerequires a final commit, exactly one starts - where the rule does not require one, no final commit starts and the previous last successful commit remains; for a case that the adoption cannot construct, this test checks nothing
- where
- Diagnostics
story-campaign-commit-log
normal-quit-returns-last-commit · campaign-progress
campaign-progress returns its last commit after a normal quit
Applies to the campaign-progress row.
After a normal quit, campaign-progress returns from the latest successful commit of the save kind named by saved-in; its uncommitted change does not return. If the row has no saved-in, this test checks nothing about restoration.
Test steps and diagnostics
- Given
a committed
campaign-progressvalue followed by a distinguishable uncommitted change- When
- the game quits normally and returns through the same context
- Then
- where this family names a save kind at saved-in, the latest successful commit of that kind restores
campaign-progressand the uncommitted change does not return - where this family has no saved-in, this test checks nothing about restoration
- where this family names a save kind at saved-in, the latest successful commit of that kind restores
- Diagnostics
story-campaign-commit-logstory-campaign-load-resultstory-campaign-active-state-source
normal-quit-returns-last-commit · dungeon-attempt
dungeon-attempt returns its last commit after a normal quit
Applies to the dungeon-attempt row.
After a normal quit, dungeon-attempt returns from the latest successful commit of the save kind named by saved-in; its uncommitted change does not return. If the row has no saved-in, this test checks nothing about restoration.
Test steps and diagnostics
- Given
a committed
dungeon-attemptvalue followed by a distinguishable uncommitted change- When
- the game quits normally and returns through the same context
- Then
- where this family names a save kind at saved-in, the latest successful commit of that kind restores
dungeon-attemptand the uncommitted change does not return - where this family has no saved-in, this test checks nothing about restoration
- where this family names a save kind at saved-in, the latest successful commit of that kind restores
- Diagnostics
story-campaign-commit-logstory-campaign-load-resultstory-campaign-active-state-source
normal-quit-returns-last-commit · session-map-hints
session-map-hints returns its last commit after a normal quit
Applies to the session-map-hints row.
After a normal quit, session-map-hints returns from the latest successful commit of the save kind named by saved-in; its uncommitted change does not return. If the row has no saved-in, this test checks nothing about restoration.
Test steps and diagnostics
- Given
a committed
session-map-hintsvalue followed by a distinguishable uncommitted change- When
- the game quits normally and returns through the same context
- Then
- where this family names a save kind at saved-in, the latest successful commit of that kind restores
session-map-hintsand the uncommitted change does not return - where this family has no saved-in, this test checks nothing about restoration
- where this family names a save kind at saved-in, the latest successful commit of that kind restores
- Diagnostics
story-campaign-commit-logstory-campaign-load-resultstory-campaign-active-state-source
normal-quit-returns-last-commit · unlocked-cosmetics
unlocked-cosmetics returns its last commit after a normal quit
Applies to the unlocked-cosmetics row.
After a normal quit, unlocked-cosmetics returns from the latest successful commit of the save kind named by saved-in; its uncommitted change does not return. If the row has no saved-in, this test checks nothing about restoration.
Test steps and diagnostics
- Given
a committed
unlocked-cosmeticsvalue followed by a distinguishable uncommitted change- When
- the game quits normally and returns through the same context
- Then
- where this family names a save kind at saved-in, the latest successful commit of that kind restores
unlocked-cosmeticsand the uncommitted change does not return - where this family has no saved-in, this test checks nothing about restoration
- where this family names a save kind at saved-in, the latest successful commit of that kind restores
- Diagnostics
story-campaign-commit-logstory-campaign-load-resultstory-campaign-active-state-source
normal-quit-does-not-return
Row.id does not return after a normal quit
Does not apply to story-campaign: no row in state-families matches after-normal-quit does not return.
crash-returns-last-commit · campaign-progress
campaign-progress returns its last completed commit after a crash
Applies to the campaign-progress row.
After a crash, no unfinished commit succeeds. campaign-progress returns from the last successful commit of the save kind named by saved-in, without later changes. If the row has no saved-in, this test checks nothing about restoration.
Test steps and diagnostics
- Given
a committed
campaign-progressvalue, then a distinguishable change and an unfinished commit- When
- the game crashes and returns through the same context
- Then
- the unfinished commit does not succeed
- where this family names a save kind at saved-in, the last commit completed before the crash restores
campaign-progressand the later change does not return - where this family has no saved-in, this test checks nothing about restoration
- Diagnostics
story-campaign-commit-logstory-campaign-load-resultstory-campaign-active-state-source
crash-returns-last-commit · dungeon-attempt
dungeon-attempt returns its last completed commit after a crash
Applies to the dungeon-attempt row.
After a crash, no unfinished commit succeeds. dungeon-attempt returns from the last successful commit of the save kind named by saved-in, without later changes. If the row has no saved-in, this test checks nothing about restoration.
Test steps and diagnostics
- Given
a committed
dungeon-attemptvalue, then a distinguishable change and an unfinished commit- When
- the game crashes and returns through the same context
- Then
- the unfinished commit does not succeed
- where this family names a save kind at saved-in, the last commit completed before the crash restores
dungeon-attemptand the later change does not return - where this family has no saved-in, this test checks nothing about restoration
- Diagnostics
story-campaign-commit-logstory-campaign-load-resultstory-campaign-active-state-source
crash-returns-last-commit · session-map-hints
session-map-hints returns its last completed commit after a crash
Applies to the session-map-hints row.
After a crash, no unfinished commit succeeds. session-map-hints returns from the last successful commit of the save kind named by saved-in, without later changes. If the row has no saved-in, this test checks nothing about restoration.
Test steps and diagnostics
- Given
a committed
session-map-hintsvalue, then a distinguishable change and an unfinished commit- When
- the game crashes and returns through the same context
- Then
- the unfinished commit does not succeed
- where this family names a save kind at saved-in, the last commit completed before the crash restores
session-map-hintsand the later change does not return - where this family has no saved-in, this test checks nothing about restoration
- Diagnostics
story-campaign-commit-logstory-campaign-load-resultstory-campaign-active-state-source
crash-returns-last-commit · unlocked-cosmetics
unlocked-cosmetics returns its last completed commit after a crash
Applies to the unlocked-cosmetics row.
After a crash, no unfinished commit succeeds. unlocked-cosmetics returns from the last successful commit of the save kind named by saved-in, without later changes. If the row has no saved-in, this test checks nothing about restoration.
Test steps and diagnostics
- Given
a committed
unlocked-cosmeticsvalue, then a distinguishable change and an unfinished commit- When
- the game crashes and returns through the same context
- Then
- the unfinished commit does not succeed
- where this family names a save kind at saved-in, the last commit completed before the crash restores
unlocked-cosmeticsand the later change does not return - where this family has no saved-in, this test checks nothing about restoration
- Diagnostics
story-campaign-commit-logstory-campaign-load-resultstory-campaign-active-state-source
crash-does-not-return
Row.id does not return after a crash
Does not apply to story-campaign: no row in state-families matches after-crash does not return.
timer-and-checkpoint-have-crash-loss-window · campaign-slots
campaign-slots reports its possible crash loss window
Applies to the campaign-slots row.
save-data.campaign-checkpoints states which later changes of campaign-slots a crash can lose. These changes are the possible loss window. The interrupted commit fails, and the report identifies the last successful commit and the lost interval. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
a successful
campaign-slotscommit, later changes inside the possible loss window atsave-data.campaign-checkpoints, and a commit interrupted by a crash- When
- the game returns after the crash
- Then
- the interrupted commit does not succeed
- the settlement report identifies the last successful commit and the changes in the cited possible loss window that did not return
- Diagnostics
story-campaign-commit-logstory-campaign-settlement-report
timer-and-checkpoint-have-crash-loss-window · settings-file
settings-file reports its possible crash loss window
Applies to the settings-file row.
settings.timed-write states which later changes of settings-file a crash can lose. These changes are the possible loss window. The interrupted commit fails, and the report identifies the last successful commit and the lost interval. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
a successful
settings-filecommit, later changes inside the possible loss window atsettings.timed-write, and a commit interrupted by a crash- When
- the game returns after the crash
- Then
- the interrupted commit does not succeed
- the settlement report identifies the last successful commit and the changes in the cited possible loss window that did not return
- Diagnostics
story-campaign-commit-logstory-campaign-settlement-report
layout-selects-requested-copy · campaign-slots
campaign-slots selects its requested copy before activation
Applies to the campaign-slots row.
Before activation, campaign-slots selects the player's named slot with its stable slot identity under save-data.campaign-slot-layout. Where an earlier commit is retained, it is not another layout. Where none is retained, this test checks nothing about earlier commits. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
the player requests
campaign-slotsthrough the selection route atsave-data.campaign-slot-layoutand, where the adoption retains one, an earlier commit is also available- When
- selection completes before any state activation
- Then
campaign-slotsselects the player's named slot with its stable slot identity- where an earlier commit is retained, it is not treated as another layout choice and remains available only through its matching backup and damage routes; where none is retained, this test checks nothing about earlier commits
- Diagnostics
story-campaign-candidate-setstory-campaign-active-state-source
layout-selects-requested-copy · settings-file
settings-file selects its requested copy before activation
Applies to the settings-file row.
Before activation, settings-file selects its current copy and presents no player-selected slot under settings.current-file. Where an earlier commit is retained, it is not another layout. Where none is retained, this test checks nothing about earlier commits. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
the player requests
settings-filethrough the selection route atsettings.current-fileand, where the adoption retains one, an earlier commit is also available- When
- selection completes before any state activation
- Then
settings-fileselects its current copy and presents no player-selected slot- where an earlier commit is retained, it is not treated as another layout choice and remains available only through its matching backup and damage routes; where none is retained, this test checks nothing about earlier commits
- Diagnostics
story-campaign-candidate-setstory-campaign-active-state-source
residence-controls-authoritative-candidates · campaign-slots
campaign-slots obtains candidates from its declared residence
Applies to the campaign-slots row.
campaign-slots is available only on that device unless a transfer cited outside this adoption supplies it under save-data.campaign-slot-layout. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
the candidate sources the adoption can construct for
campaign-slotsas governed atsave-data.campaign-slot-layout- When
- the game builds the candidate set before conflict handling
- Then
campaign-slotsis available only on that device unless a transfer cited outside this adoption supplies it
- Diagnostics
story-campaign-candidate-setstory-campaign-authority-trace
residence-controls-authoritative-candidates · settings-file
settings-file obtains candidates from its declared residence
Applies to the settings-file row.
settings-file is available only on that device unless a transfer cited outside this adoption supplies it under settings.current-file. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
the candidate sources the adoption can construct for
settings-fileas governed atsettings.current-file- When
- the game builds the candidate set before conflict handling
- Then
settings-fileis available only on that device unless a transfer cited outside this adoption supplies it
- Diagnostics
story-campaign-candidate-setstory-campaign-authority-trace
candidate-identity-precedes-settlement · campaign-slots
campaign-slots reads candidate identity before settlement
Applies to the campaign-slots row.
campaign-slots reads candidate identity first. A candidate with an unreadable identity cannot enter conflict handling. When no candidate remains, the effective damage response runs immediately. A readable header with an unreadable body reaches the damage response only after conflict and build comparison.
Test steps and diagnostics
- Given
campaign-slotscandidates with readable identity, unreadable identity, and a readable header with an unreadable gameplay body- When
- the game reads candidate identity before conflict, build, or damage settlement
- Then
- save-kind, slot when present, build, and commit identity are read before gameplay state
- an unreadable identity is not a conflict candidate; if no candidate remains, the effective damage response runs immediately without conflict or build comparison
- a readable header with an unreadable body remains a candidate through conflict and build comparison before reaching damage settlement
- Diagnostics
story-campaign-candidate-setstory-campaign-settlement-tracestory-campaign-damage-report
candidate-identity-precedes-settlement · settings-file
settings-file reads candidate identity before settlement
Applies to the settings-file row.
settings-file reads candidate identity first. A candidate with an unreadable identity cannot enter conflict handling. When no candidate remains, the effective damage response runs immediately. A readable header with an unreadable body reaches the damage response only after conflict and build comparison.
Test steps and diagnostics
- Given
settings-filecandidates with readable identity, unreadable identity, and a readable header with an unreadable gameplay body- When
- the game reads candidate identity before conflict, build, or damage settlement
- Then
- save-kind, slot when present, build, and commit identity are read before gameplay state
- an unreadable identity is not a conflict candidate; if no candidate remains, the effective damage response runs immediately without conflict or build comparison
- a readable header with an unreadable body remains a candidate through conflict and build comparison before reaching damage settlement
- Diagnostics
story-campaign-candidate-setstory-campaign-settlement-tracestory-campaign-damage-report
missing-conflict-row-runs-no-conflict-response · campaign-slots
campaign-slots has no conflict response without a matching row
Applies to the campaign-slots row.
If campaign-slots has no matching conflict row, this contract performs no conflict response for it. If it has one, this test checks nothing.
Test steps and diagnostics
- Given
a candidate set for
campaign-slots- When
- the game looks for a matching copy-conflicts row before build comparison
- Then
- where no matching conflict row exists, no candidate is selected, refused, or replaced by a conflict response from this contract
- where a matching row exists, this test checks nothing
- Diagnostics
story-campaign-candidate-setstory-campaign-settlement-trace
missing-conflict-row-runs-no-conflict-response · settings-file
settings-file has no conflict response without a matching row
Applies to the settings-file row.
If settings-file has no matching conflict row, this contract performs no conflict response for it. If it has one, this test checks nothing.
Test steps and diagnostics
- Given
a candidate set for
settings-file- When
- the game looks for a matching copy-conflicts row before build comparison
- Then
- where no matching conflict row exists, no candidate is selected, refused, or replaced by a conflict response from this contract
- where a matching row exists, this test checks nothing
- Diagnostics
story-campaign-candidate-setstory-campaign-settlement-trace
one-kind-conflict-settles-only-named-kind
Row.id settles only Row.copy kind
Does not apply to story-campaign: copy-conflicts has no rows.
all-synced-conflict-settles-whole-set
Row.id settles every synced save kind together
Does not apply to story-campaign: copy-conflicts has no rows.
conflict-select-by-rule
Row.id applies select-by-rule
Does not apply to story-campaign: copy-conflicts has no rows.
conflict-player-chooses-copy
Row.id applies player-chooses-copy
Does not apply to story-campaign: copy-conflicts has no rows.
conflict-refuse-until-resolved
Row.id applies refuse-until-resolved
Does not apply to story-campaign: copy-conflicts has no rows.
older-build-override-refuse-load
Row.id applies its older-build refuse-load override
Does not apply to story-campaign: no row in copy-kinds matches older-build-copy refuse load.
older-build-default-refuse-load
Row.id applies the default answer refuse-load for an older build
Does not apply to story-campaign: older build default is convert and load, not refuse load.
older-build-override-convert-and-load
Row.id applies its older-build convert-and-load override
Does not apply to story-campaign: no row in copy-kinds matches older-build-copy convert and load.
older-build-default-convert-and-load · settings-file
settings-file applies the default answer convert-and-load for an older build
Applies to the settings-file row.
settings-file uses the default answer for an older build and converts the copy to one coherent current-build copy before successful activation; a failed conversion refuses the load and leaves the source copy unchanged unless its cited rule records another result for the source copy. The observable consequence is stated here; the test restates nothing from the adoption's cited rule.
Test steps and diagnostics
- Given
a selected readable copy of save kind
settings-filewritten by an older build, with no unresolved conflict- When
- one conversion succeeds and a second constructed conversion fails
- Then
- the successful conversion produces one coherent current-build copy before all of its carried families activate together
- the failed conversion refuses activation and leaves the source copy unchanged unless its cited rule records another result for the source copy
- the conversion record distinguishes success, refusal, and what happened to the source copy
- Diagnostics
story-campaign-writer-and-reader-buildsstory-campaign-conversion-resultstory-campaign-load-resultstory-campaign-active-state-source
older-build-override-warn-and-load · campaign-slots
campaign-slots applies its older-build warn-and-load override
Applies to the campaign-slots row.
campaign-slots overrides the default answer for an older build and warns before the attempt; cancellation activates nothing, and acceptance is recorded separately from success. The observable consequence is stated here; the test restates nothing from the adoption's cited rule.
Test steps and diagnostics
- Given
a selected readable copy of save kind
campaign-slotswritten by an older build, with no unresolved conflict- When
- the player first cancels the warning, then accepts a successful attempt, and finally accepts a constructed failed attempt
- Then
- canceling activates no state
- the warning precedes both accepted attempts; the successful attempt activates every carried family together and the failed attempt activates none
- warning acceptance is recorded separately from load success
- Diagnostics
story-campaign-writer-and-reader-buildsstory-campaign-warning-choicestory-campaign-load-resultstory-campaign-active-state-source
older-build-default-warn-and-load
Row.id applies the default answer warn-and-load for an older build
Does not apply to story-campaign: older build default is convert and load, not warn and load.
newer-build-override-refuse-load
Row.id applies its newer-build refuse-load override
Does not apply to story-campaign: no row in copy-kinds matches newer-build-copy refuse load.
newer-build-default-refuse-load · campaign-slots
campaign-slots applies the default answer refuse-load for a newer build
Applies to the campaign-slots row.
campaign-slots uses the default answer for a newer build and refuses the load before any mismatched-build state activates. The observable consequence is stated here; the test restates nothing from the adoption's cited rule.
Test steps and diagnostics
- Given
a selected readable copy of save kind
campaign-slotswritten by a newer build, with no unresolved conflict- When
- the player requests the copy
- Then
- loading stops before any state from the mismatched-build copy activates
- the refusal record distinguishes refusal from an absent or unreadable copy
- Diagnostics
story-campaign-writer-and-reader-buildsstory-campaign-load-resultstory-campaign-active-state-source
newer-build-override-warn-and-load · settings-file
settings-file applies its newer-build warn-and-load override
Applies to the settings-file row.
settings-file overrides the default answer for a newer build and warns before the attempt; cancellation activates nothing, and acceptance is recorded separately from success. The observable consequence is stated here; the test restates nothing from the adoption's cited rule.
Test steps and diagnostics
- Given
a selected readable copy of save kind
settings-filewritten by a newer build, with no unresolved conflict- When
- the player first cancels the warning, then accepts a successful attempt, and finally accepts a constructed failed attempt
- Then
- canceling activates no state
- the warning precedes both accepted attempts; the successful attempt activates every carried family together and the failed attempt activates none
- warning acceptance is recorded separately from load success, and the failed attempt is distinguished from an absent or unreadable copy
- Diagnostics
story-campaign-writer-and-reader-buildsstory-campaign-warning-choicestory-campaign-load-resultstory-campaign-active-state-source
newer-build-default-warn-and-load
Row.id applies the default answer warn-and-load for a newer build
Does not apply to story-campaign: newer build default is refuse load, not warn and load.
unreadable-copy-override-refuse-load
Row.id applies its unreadable-copy refuse-load override
Does not apply to story-campaign: no row in copy-kinds matches unreadable-copy refuse load.
unreadable-copy-default-refuse-load
Row.id applies the default answer refuse-load for an unreadable copy
Does not apply to story-campaign: unreadable copy default is repair readable state, not refuse load.
unreadable-copy-override-start-fresh · settings-file
settings-file applies its unreadable-copy start-fresh override
Applies to the settings-file row.
settings-file overrides the default answer for an unreadable copy and activates the adoption's starting state without silently deleting or overwriting the unreadable copy. The observable consequence is stated here; the test restates nothing from the adoption's cited rule.
Test steps and diagnostics
- Given
a selected copy of save kind
settings-filewhose gameplay body is unreadable after conflict and build settlement- When
- the player requests the unreadable copy
- Then
- the starting state named by the adoption becomes active instead of state from the unreadable copy
- the unreadable copy is not silently deleted or overwritten
- the source and removal records distinguish a new start from a deletion
- Diagnostics
story-campaign-damage-reportstory-campaign-load-resultstory-campaign-active-state-sourcestory-campaign-removal-log
unreadable-copy-default-start-fresh
Row.id applies the default answer start-fresh for an unreadable copy
Does not apply to story-campaign: unreadable copy default is repair readable state, not start fresh.
unreadable-copy-override-restore-earlier-copy
Row.id applies its unreadable-copy restore-earlier-copy override
Does not apply to story-campaign: no row in copy-kinds matches unreadable-copy restore earlier copy.
unreadable-copy-default-restore-earlier-copy
Row.id applies the default answer restore-earlier-copy for an unreadable copy
Does not apply to story-campaign: unreadable copy default is repair readable state, not restore earlier copy.
unreadable-copy-override-repair-readable-state
Row.id applies its unreadable-copy repair-readable-state override
Does not apply to story-campaign: no row in copy-kinds matches unreadable-copy repair readable state.
unreadable-copy-default-repair-readable-state
Row.id applies the default answer repair-readable-state for an unreadable copy
Does not apply to story-campaign: no row in copy-kinds matches unreadable-copy use game default.
unreadable-copy-override-warn-and-try · campaign-slots
campaign-slots applies its unreadable-copy warn-and-try override
Applies to the campaign-slots row.
campaign-slots overrides the default answer for an unreadable copy and waits for an explicit choice; cancellation activates nothing, and acceptance is recorded separately from success. The observable consequence is stated here; the test restates nothing from the adoption's cited rule.
Test steps and diagnostics
- Given
a selected copy of save kind
campaign-slotswhose gameplay body is unreadable after conflict and build settlement- When
- the player first cancels the warning, then accepts a successful attempt, and finally accepts a constructed failed attempt
- Then
- no copy state activates before the explicit choice and cancellation activates nothing
- the successful accepted attempt activates one coherent copy and the failed accepted attempt activates none
- warning acceptance is recorded separately from load success
- Diagnostics
story-campaign-damage-reportstory-campaign-load-resultstory-campaign-active-state-sourcestory-campaign-removal-log
unreadable-copy-default-warn-and-try
Row.id applies the default answer warn-and-try for an unreadable copy
Does not apply to story-campaign: unreadable copy default is repair readable state, not warn and try.
backup-retains-orders-and-restores-earlier-copies · previous-campaign-checkpoint
previous-campaign-checkpoint retains and restores earlier copies
Applies to the previous-campaign-checkpoint row.
previous-campaign-checkpoint retains and orders earlier copies under save-data.previous-checkpoint-policy. Its direct player route at menus.restore-previous-checkpoint opens the newest usable one and reports the lost interval, independently of damage settlement. The test names both addresses and restates nothing from them.
Test steps and diagnostics
- Given
several earlier commits for the save kind named by
campaign-slots, including usable and unusable candidates- When
- the game applies
save-data.previous-checkpoint-policyand the player directly usesmenus.restore-previous-checkpoint
- the game applies
- Then
- earlier commits are retained and ordered only as
save-data.previous-checkpoint-policyand the storage rule of the save kind named at copy-kind provide - the newest usable earlier copy becomes active as one complete copy and the lost interval is reported
- the direct route at
menus.restore-previous-checkpointremains available independently of any damage response
- earlier commits are retained and ordered only as
- Diagnostics
story-campaign-backup-inventorystory-campaign-load-resultstory-campaign-damage-report
readable-compatible-copy-activates-together · campaign-slots
campaign-slots loads a readable compatible conflict-free copy
Applies to the campaign-slots row.
A readable, same-build, conflict-free copy of save kind campaign-slots loads successfully and activates every family at save-data.campaign-contents together from one commit. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
a selected readable copy of save kind
campaign-slotsfrom the running build with no conflict, carrying distinguishable values for every family atsave-data.campaign-contents- When
- the game loads the copy
- Then
- the load is accepted rather than refused
- every carried state family activates together from one commit
- no partial copy is visible at any point
- Diagnostics
story-campaign-load-resultstory-campaign-active-state-sourcestory-campaign-activation-trace
readable-compatible-copy-activates-together · settings-file
settings-file loads a readable compatible conflict-free copy
Applies to the settings-file row.
A readable, same-build, conflict-free copy of save kind settings-file loads successfully and activates every family at settings.session-hint-contents together from one commit. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
a selected readable copy of save kind
settings-filefrom the running build with no conflict, carrying distinguishable values for every family atsettings.session-hint-contents- When
- the game loads the copy
- Then
- the load is accepted rather than refused
- every carried state family activates together from one commit
- no partial copy is visible at any point
- Diagnostics
story-campaign-load-resultstory-campaign-active-state-sourcestory-campaign-activation-trace
load-preserves-families-from-other-kinds · campaign-slots
loading campaign-slots preserves state from other save kinds
Applies to the campaign-slots row.
Loading campaign-slots activates its carried families together and preserves the active value of every family saved in another save kind. If no other save kind exists, this test checks nothing about other save kinds.
Test steps and diagnostics
- Given
active distinguishable state from
campaign-slotsand, where the adoption declares one, another save kind- When
- a readable compatible conflict-free copy of save kind
campaign-slotsloads successfully
- a readable compatible conflict-free copy of save kind
- Then
- families carried by
campaign-slotsactivate together from its selected commit - families saved in another save kind keep that kind's already active value
- where no other save kind exists, this test checks nothing about other save kinds
- families carried by
- Diagnostics
story-campaign-activation-tracestory-campaign-active-state-source
load-preserves-families-from-other-kinds · settings-file
loading settings-file preserves state from other save kinds
Applies to the settings-file row.
Loading settings-file activates its carried families together and preserves the active value of every family saved in another save kind. If no other save kind exists, this test checks nothing about other save kinds.
Test steps and diagnostics
- Given
active distinguishable state from
settings-fileand, where the adoption declares one, another save kind- When
- a readable compatible conflict-free copy of save kind
settings-fileloads successfully
- a readable compatible conflict-free copy of save kind
- Then
- families carried by
settings-fileactivate together from its selected commit - families saved in another save kind keep that kind's already active value
- where no other save kind exists, this test checks nothing about other save kinds
- families carried by
- Diagnostics
story-campaign-activation-tracestory-campaign-active-state-source
family-without-saved-in-is-never-restored · campaign-progress
campaign-progress is never restored when it names no save kind
Applies to the campaign-progress row.
For campaign-progress, when it has no saved-in, no return path restores its earlier value. For campaign-progress, when it names saved-in, only an accepted load of that save kind restores its earlier value; no other return path does.
Test steps and diagnostics
- Given
a distinguishable earlier
campaign-progressvalue and every return path the adoption can construct- When
- the return paths complete
- Then
- where
campaign-progresshas no saved-in, none of the return paths restores its earlier value - where
campaign-progressnames saved-in, its earlier value is restored only by an accepted load of that save kind and by no other return path
- where
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-source
family-without-saved-in-is-never-restored · dungeon-attempt
dungeon-attempt is never restored when it names no save kind
Applies to the dungeon-attempt row.
For dungeon-attempt, when it has no saved-in, no return path restores its earlier value. For dungeon-attempt, when it names saved-in, only an accepted load of that save kind restores its earlier value; no other return path does.
Test steps and diagnostics
- Given
a distinguishable earlier
dungeon-attemptvalue and every return path the adoption can construct- When
- the return paths complete
- Then
- where
dungeon-attempthas no saved-in, none of the return paths restores its earlier value - where
dungeon-attemptnames saved-in, its earlier value is restored only by an accepted load of that save kind and by no other return path
- where
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-source
family-without-saved-in-is-never-restored · session-map-hints
session-map-hints is never restored when it names no save kind
Applies to the session-map-hints row.
For session-map-hints, when it has no saved-in, no return path restores its earlier value. For session-map-hints, when it names saved-in, only an accepted load of that save kind restores its earlier value; no other return path does.
Test steps and diagnostics
- Given
a distinguishable earlier
session-map-hintsvalue and every return path the adoption can construct- When
- the return paths complete
- Then
- where
session-map-hintshas no saved-in, none of the return paths restores its earlier value - where
session-map-hintsnames saved-in, its earlier value is restored only by an accepted load of that save kind and by no other return path
- where
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-source
family-without-saved-in-is-never-restored · unlocked-cosmetics
unlocked-cosmetics is never restored when it names no save kind
Applies to the unlocked-cosmetics row.
For unlocked-cosmetics, when it has no saved-in, no return path restores its earlier value. For unlocked-cosmetics, when it names saved-in, only an accepted load of that save kind restores its earlier value; no other return path does.
Test steps and diagnostics
- Given
a distinguishable earlier
unlocked-cosmeticsvalue and every return path the adoption can construct- When
- the return paths complete
- Then
- where
unlocked-cosmeticshas no saved-in, none of the return paths restores its earlier value - where
unlocked-cosmeticsnames saved-in, its earlier value is restored only by an accepted load of that save kind and by no other return path
- where
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-source
earlier-point-follows-after-load · dungeon-retry
dungeon-retry follows its after-load result
Applies to the dungeon-retry row.
After successful activation, dungeon-retry returns play to dungeon.retry and keeps the run identity when the point is inside a run. Then only the first return goes to the earlier point, and it makes that same return unavailable for a second attempt. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
an accepted copy of the save kind
campaign-slotsand an earlier point atdungeon.retry- When
- activation succeeds and the player attempts the return twice
- Then
- continuous play returns to the cited earlier point only after successful activation
- each return the row's after-load result permits retains the same run identity when the point is inside a run
- only the first return goes to the earlier point, and it makes that same return unavailable for a second attempt
- Diagnostics
story-campaign-load-resultstory-campaign-return-pointstory-campaign-run-identitystory-campaign-save-availability-after-load
missing-earlier-point-row-returns-nowhere · campaign-slots
campaign-slots returns to no earlier point without an earlier-point row
Applies to the campaign-slots row.
A successful campaign-slots load returns continuous play to no earlier point under this contract when no earlier-point row names it. When a row names it, this test checks nothing.
Test steps and diagnostics
- Given
a successful
campaign-slotsload- When
- the game checks for an earlier-point-returns row after activation
- Then
- where no row names
campaign-slots, this contract returns continuous play to no earlier point - where a row names
campaign-slots, this test checks nothing
- where no row names
- Diagnostics
story-campaign-load-resultstory-campaign-return-point
missing-earlier-point-row-returns-nowhere · settings-file
settings-file returns to no earlier point without an earlier-point row
Applies to the settings-file row.
A successful settings-file load returns continuous play to no earlier point under this contract when no earlier-point row names it. When a row names it, this test checks nothing.
Test steps and diagnostics
- Given
a successful
settings-fileload- When
- the game checks for an earlier-point-returns row after activation
- Then
- where no row names
settings-file, this contract returns continuous play to no earlier point - where a row names
settings-file, this test checks nothing
- where no row names
- Diagnostics
story-campaign-load-resultstory-campaign-return-point
earlier-point-mode-gate-controls-existence
Row.id exists only in its named modes
Does not apply to story-campaign: no row in earlier-point-returns matches mode-gated only in named modes.
death-follows-family-result · campaign-progress
campaign-progress follows its death result
Applies to the campaign-progress row.
On death, campaign-progress follows its selected result: the earlier value remains available to later play.
Test steps and diagnostics
- Given
a distinguishable
campaign-progressvalue immediately before death- When
- death settles before any death-triggered copy removal the adoption declares
- Then
- the earlier value remains available to later play
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-source
death-follows-family-result · dungeon-attempt
dungeon-attempt follows its death result
Applies to the dungeon-attempt row.
On death, dungeon-attempt follows its selected result: the earlier value is removed before later play and an equal later value is recorded only as new state.
Test steps and diagnostics
- Given
a distinguishable
dungeon-attemptvalue immediately before death- When
- death settles before any death-triggered copy removal the adoption declares
- Then
- the earlier value is removed before later play and an equal later value is recorded only as new state
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-source
death-follows-family-result · session-map-hints
session-map-hints follows its death result
Applies to the session-map-hints row.
On death, session-map-hints follows its selected result: the earlier value remains available to later play.
Test steps and diagnostics
- Given
a distinguishable
session-map-hintsvalue immediately before death- When
- death settles before any death-triggered copy removal the adoption declares
- Then
- the earlier value remains available to later play
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-source
death-follows-family-result · unlocked-cosmetics
unlocked-cosmetics follows its death result
Applies to the unlocked-cosmetics row.
On death, unlocked-cosmetics follows its selected result: the earlier value remains available to later play.
Test steps and diagnostics
- Given
a distinguishable
unlocked-cosmeticsvalue immediately before death- When
- death settles before any death-triggered copy removal the adoption declares
- Then
- the earlier value remains available to later play
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-source
death-at-run-end-clears-run-family · dungeon-attempt
dungeon-attempt clears when death is its run end
Applies to the dungeon-attempt row.
If death is the run end at dungeon.victory-death-or-abandonment, dungeon-attempt clears even without a save action. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
a distinguishable
dungeon-attemptvalue before a death, where the run ending atdungeon.victory-death-or-abandonmentis death- When
- death occurs without another save action
- Then
- if the cited end at
dungeon.victory-death-or-abandonmentis death, the earlier run-lifetime value clears before later play even without a save action; if the cited end is not death, this test checks nothing
- if the cited end at
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-run-identity
death-at-until-event-clears-family
Row.id clears when death is its cited event
Does not apply to story-campaign: no row in state-families matches lifetime until event.
slot-deletion-follows-family-result · campaign-progress
campaign-progress follows its slot-deletion result
Applies to the campaign-progress row.
On slot deletion, campaign-progress follows its selected result: the earlier value tied to that slot clears before later play.
Test steps and diagnostics
- Given
a distinguishable family value before deletion of its related player-selected slot
- When
- the related slot is deleted
- Then
- the earlier value tied to that slot clears before later play
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-settlement-report
slot-deletion-follows-family-result · dungeon-attempt
dungeon-attempt follows its slot-deletion result
Applies to the dungeon-attempt row.
On slot deletion, dungeon-attempt follows its selected result: the earlier value tied to that slot clears before later play.
Test steps and diagnostics
- Given
a distinguishable family value before deletion of its related player-selected slot
- When
- the related slot is deleted
- Then
- the earlier value tied to that slot clears before later play
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-settlement-report
slot-deletion-follows-family-result · session-map-hints
session-map-hints follows its slot-deletion result
Applies to the session-map-hints row.
On slot deletion, session-map-hints follows its selected result: the settlement records that this family has no related player-selected slot.
Test steps and diagnostics
- Given
a distinguishable family value when it has no related player-selected slot
- When
- a slot-deletion boundary is considered
- Then
- the settlement records that this family has no related player-selected slot
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-settlement-report
slot-deletion-follows-family-result · unlocked-cosmetics
unlocked-cosmetics follows its slot-deletion result
Applies to the unlocked-cosmetics row.
On slot deletion, unlocked-cosmetics follows its selected result: the earlier value remains available after the related slot is deleted.
Test steps and diagnostics
- Given
a distinguishable family value before deletion of its related player-selected slot
- When
- the related slot is deleted
- Then
- the earlier value remains available after the related slot is deleted
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-settlement-report
local-profile-deletion-follows-family-result · campaign-progress
campaign-progress follows its local-profile-deletion result
Applies to the campaign-progress row.
On local-profile deletion, campaign-progress follows its selected result: the settlement records that this family has no related local player profile. Where a related profile exists, child slots are included only as the adoption's cited rule provides. Where no related profile exists, this test checks nothing about child slots. The test restates nothing from the cited rule.
Test steps and diagnostics
- Given
a distinguishable family value when it has no related local player profile
- When
- a local-profile-deletion boundary is considered
- Then
- the settlement records that this family has no related local player profile
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-settlement-reportstory-campaign-removal-log
local-profile-deletion-follows-family-result · dungeon-attempt
dungeon-attempt follows its local-profile-deletion result
Applies to the dungeon-attempt row.
On local-profile deletion, dungeon-attempt follows its selected result: the settlement records that this family has no related local player profile. Where a related profile exists, child slots are included only as the adoption's cited rule provides. Where no related profile exists, this test checks nothing about child slots. The test restates nothing from the cited rule.
Test steps and diagnostics
- Given
a distinguishable family value when it has no related local player profile
- When
- a local-profile-deletion boundary is considered
- Then
- the settlement records that this family has no related local player profile
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-settlement-reportstory-campaign-removal-log
local-profile-deletion-follows-family-result · session-map-hints
session-map-hints follows its local-profile-deletion result
Applies to the session-map-hints row.
On local-profile deletion, session-map-hints follows its selected result: the settlement records that this family has no related local player profile. Where a related profile exists, child slots are included only as the adoption's cited rule provides. Where no related profile exists, this test checks nothing about child slots. The test restates nothing from the cited rule.
Test steps and diagnostics
- Given
a distinguishable family value when it has no related local player profile
- When
- a local-profile-deletion boundary is considered
- Then
- the settlement records that this family has no related local player profile
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-settlement-reportstory-campaign-removal-log
local-profile-deletion-follows-family-result · unlocked-cosmetics
unlocked-cosmetics follows its local-profile-deletion result
Applies to the unlocked-cosmetics row.
On local-profile deletion, unlocked-cosmetics follows its selected result: the earlier value tied to that local profile clears before later play. Where a related profile exists, child slots are included only as the adoption's cited rule provides. Where no related profile exists, this test checks nothing about child slots. The test restates nothing from the cited rule.
Test steps and diagnostics
- Given
a distinguishable family value before deletion of its related local profile
- When
- the related local profile is deleted with every child slot named by the adoption's cited rule
- Then
- the earlier value tied to that local profile clears before later play
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-settlement-reportstory-campaign-removal-log
device-lifetime-ends-at-device-data-deletion
Row.id ends when device data is deleted
Does not apply to story-campaign: no row in state-families matches lifetime device.
account-lifetime-ends-at-account-deletion
Row.id ends when its account is deleted or reset
Does not apply to story-campaign: no row in state-families matches lifetime account.
another-device-returns-family
Row.id returns on another eligible device
Does not apply to story-campaign: no row in state-families matches on-another-device returns.
another-device-does-not-return-family · campaign-progress
campaign-progress does not return on another device
Applies to the campaign-progress row.
On another eligible device, the earlier campaign-progress value does not return. Its new-device value follows campaign.progress-fields. The device change does not delete any source-device copy; if none exists, this test checks nothing about a source-device copy. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
a distinguishable
campaign-progressvalue on one device before the same eligible player continues on another- When
- the new device settles available state
- Then
- the earlier value does not activate and the family starts from the new-device value governed at
campaign.progress-fields - any source-device copy is not deleted by the device change; where none exists, this test checks nothing about a source-device copy
- the earlier value does not activate and the family starts from the new-device value governed at
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-sourcestory-campaign-removal-log
another-device-does-not-return-family · dungeon-attempt
dungeon-attempt does not return on another device
Applies to the dungeon-attempt row.
On another eligible device, the earlier dungeon-attempt value does not return. Its new-device value follows dungeon.attempt-fields. The device change does not delete any source-device copy; if none exists, this test checks nothing about a source-device copy. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
a distinguishable
dungeon-attemptvalue on one device before the same eligible player continues on another- When
- the new device settles available state
- Then
- the earlier value does not activate and the family starts from the new-device value governed at
dungeon.attempt-fields - any source-device copy is not deleted by the device change; where none exists, this test checks nothing about a source-device copy
- the earlier value does not activate and the family starts from the new-device value governed at
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-sourcestory-campaign-removal-log
another-device-does-not-return-family · session-map-hints
session-map-hints does not return on another device
Applies to the session-map-hints row.
On another eligible device, the earlier session-map-hints value does not return. Its new-device value follows map.session-hint-fields. The device change does not delete any source-device copy; if none exists, this test checks nothing about a source-device copy. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
a distinguishable
session-map-hintsvalue on one device before the same eligible player continues on another- When
- the new device settles available state
- Then
- the earlier value does not activate and the family starts from the new-device value governed at
map.session-hint-fields - any source-device copy is not deleted by the device change; where none exists, this test checks nothing about a source-device copy
- the earlier value does not activate and the family starts from the new-device value governed at
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-sourcestory-campaign-removal-log
another-device-does-not-return-family · unlocked-cosmetics
unlocked-cosmetics does not return on another device
Applies to the unlocked-cosmetics row.
On another eligible device, the earlier unlocked-cosmetics value does not return. Its new-device value follows cosmetics.unlock-fields. The device change does not delete any source-device copy; if none exists, this test checks nothing about a source-device copy. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
a distinguishable
unlocked-cosmeticsvalue on one device before the same eligible player continues on another- When
- the new device settles available state
- Then
- the earlier value does not activate and the family starts from the new-device value governed at
cosmetics.unlock-fields - any source-device copy is not deleted by the device change; where none exists, this test checks nothing about a source-device copy
- the earlier value does not activate and the family starts from the new-device value governed at
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-sourcestory-campaign-removal-log
removed-copy-disappears-at-selected-event
Row.id disappears at its removal event
Does not apply to story-campaign: no row in copy-kinds matches removed-at normal quit or crash or death or slot delete or local profile delete or device data delete or account delete.
copy-kind-mode-gate-controls-existence
Row.id exists only in its named modes
Does not apply to story-campaign: no row in copy-kinds matches mode-gated only in named modes.
cleared-or-ended-value-never-affects-later-play
ended and cleared values stay absent
Applies to story-campaign.
After a lifetime ends or a boundary result clears a value, that earlier value never affects later play. An equal value derived later is new state. If no such case can be constructed, this test checks nothing.
Test steps and diagnostics
- Given
every event the
story-campaignadoption can construct where a lifetime ends or a boundary result clears an earlier value- When
- later play reaches points where an equal value can be derived
- Then
- the earlier value never affects later play after the end or clearing event
- any equal value derived later is recorded as new state rather than restoration
- where the adoption can construct no such event, this test checks nothing
- Diagnostics
story-campaign-state-boundary-tracestory-campaign-active-state-source
settlement-report-is-complete
every settlement report records its complete result
Applies to story-campaign.
Every settlement report carries the mechanism's full identity, boundary, commit, selection, response, result, and loss fields. Conflict and damage reports add their required facts and never claim an unattempted success. For a case that the adoption cannot construct, this test checks nothing.
Test steps and diagnostics
- Given
each persistence settlement the
story-campaignadoption can construct- When
- the settlement completes or refuses
- Then
- the report identifies state-family id, save-kind id or its absence, event, scope before and after, last successful commit when read, selected copy and build when loaded, override or default answer used, state result, and any lost interval
- a conflict report also identifies both candidate commits and the selected response
- a damage report identifies refusal, starting state, earlier-copy recovery, repaired parts, or warning choice and never claims an unattempted load succeeded
- where a conflict, damage case, load, or answered question does not arise, this test checks nothing about it
- Diagnostics
story-campaign-settlement-reportstory-campaign-conflict-reportstory-campaign-damage-report
state-family-citation-states-its-duty · campaign-progress
declaration check: campaign-progress's state citation states its duty
Applies to the campaign-progress row.
This declaration check reads the adoption and cited prose, not the running game. campaign.progress-fields must name every field in campaign-progress and every point where it can affect later play. For a family that answers does-not-return after a quit, a crash, or on another device, it must also name the value the family starts from; otherwise this test checks nothing about a starting value. When the cited prose has another meaning, the adoption is invalid, and this test fails. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
the
story-campaignadoption and the prose atcampaign.progress-fields, not the running game- When
- a reviewer checks the citation's meaning
- Then
campaign.progress-fieldsnames every field in this state family and every point where it can affect later play- where the row answers does-not-return after a quit, a crash, or on another device,
campaign.progress-fieldsalso names the value the family starts from; where it never gives that answer for those cases, this test checks nothing about a starting value - when the cited prose has another meaning, the adoption is invalid, and this test fails
- Diagnostics
story-campaign-declaration-record
state-family-citation-states-its-duty · dungeon-attempt
declaration check: dungeon-attempt's state citation states its duty
Applies to the dungeon-attempt row.
This declaration check reads the adoption and cited prose, not the running game. dungeon.attempt-fields must name every field in dungeon-attempt and every point where it can affect later play. For a family that answers does-not-return after a quit, a crash, or on another device, it must also name the value the family starts from; otherwise this test checks nothing about a starting value. When the cited prose has another meaning, the adoption is invalid, and this test fails. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
the
story-campaignadoption and the prose atdungeon.attempt-fields, not the running game- When
- a reviewer checks the citation's meaning
- Then
dungeon.attempt-fieldsnames every field in this state family and every point where it can affect later play- where the row answers does-not-return after a quit, a crash, or on another device,
dungeon.attempt-fieldsalso names the value the family starts from; where it never gives that answer for those cases, this test checks nothing about a starting value - when the cited prose has another meaning, the adoption is invalid, and this test fails
- Diagnostics
story-campaign-declaration-record
state-family-citation-states-its-duty · session-map-hints
declaration check: session-map-hints's state citation states its duty
Applies to the session-map-hints row.
This declaration check reads the adoption and cited prose, not the running game. map.session-hint-fields must name every field in session-map-hints and every point where it can affect later play. For a family that answers does-not-return after a quit, a crash, or on another device, it must also name the value the family starts from; otherwise this test checks nothing about a starting value. When the cited prose has another meaning, the adoption is invalid, and this test fails. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
the
story-campaignadoption and the prose atmap.session-hint-fields, not the running game- When
- a reviewer checks the citation's meaning
- Then
map.session-hint-fieldsnames every field in this state family and every point where it can affect later play- where the row answers does-not-return after a quit, a crash, or on another device,
map.session-hint-fieldsalso names the value the family starts from; where it never gives that answer for those cases, this test checks nothing about a starting value - when the cited prose has another meaning, the adoption is invalid, and this test fails
- Diagnostics
story-campaign-declaration-record
state-family-citation-states-its-duty · unlocked-cosmetics
declaration check: unlocked-cosmetics's state citation states its duty
Applies to the unlocked-cosmetics row.
This declaration check reads the adoption and cited prose, not the running game. cosmetics.unlock-fields must name every field in unlocked-cosmetics and every point where it can affect later play. For a family that answers does-not-return after a quit, a crash, or on another device, it must also name the value the family starts from; otherwise this test checks nothing about a starting value. When the cited prose has another meaning, the adoption is invalid, and this test fails. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
the
story-campaignadoption and the prose atcosmetics.unlock-fields, not the running game- When
- a reviewer checks the citation's meaning
- Then
cosmetics.unlock-fieldsnames every field in this state family and every point where it can affect later play- where the row answers does-not-return after a quit, a crash, or on another device,
cosmetics.unlock-fieldsalso names the value the family starts from; where it never gives that answer for those cases, this test checks nothing about a starting value - when the cited prose has another meaning, the adoption is invalid, and this test fails
- Diagnostics
story-campaign-declaration-record
saved-in-joins-a-copy-kind · campaign-progress
declaration check: campaign-progress names an existing save kind when saved
Applies to the campaign-progress row.
This declaration check reads the adoption, not the running game. When campaign-progress has saved-in, it must name exactly one save kind in this adoption. When it names anything else, the adoption is invalid, and this test fails. With no saved-in, this test checks nothing. It says nothing about a family held in two places at once; one family names one restoring save kind.
Test steps and diagnostics
- Given
the
story-campaignadoption document, not the running game- When
- a reviewer reads
campaign-progress's saved-in field
- a reviewer reads
- Then
- where saved-in is present, it names exactly one copy-kinds row in this adoption; when it names anything else, the adoption is invalid, and this test fails
- where saved-in is absent, this test checks nothing
- Diagnostics
story-campaign-declaration-record
saved-in-joins-a-copy-kind · dungeon-attempt
declaration check: dungeon-attempt names an existing save kind when saved
Applies to the dungeon-attempt row.
This declaration check reads the adoption, not the running game. When dungeon-attempt has saved-in, it must name exactly one save kind in this adoption. When it names anything else, the adoption is invalid, and this test fails. With no saved-in, this test checks nothing. It says nothing about a family held in two places at once; one family names one restoring save kind.
Test steps and diagnostics
- Given
the
story-campaignadoption document, not the running game- When
- a reviewer reads
dungeon-attempt's saved-in field
- a reviewer reads
- Then
- where saved-in is present, it names exactly one copy-kinds row in this adoption; when it names anything else, the adoption is invalid, and this test fails
- where saved-in is absent, this test checks nothing
- Diagnostics
story-campaign-declaration-record
saved-in-joins-a-copy-kind · session-map-hints
declaration check: session-map-hints names an existing save kind when saved
Applies to the session-map-hints row.
This declaration check reads the adoption, not the running game. When session-map-hints has saved-in, it must name exactly one save kind in this adoption. When it names anything else, the adoption is invalid, and this test fails. With no saved-in, this test checks nothing. It says nothing about a family held in two places at once; one family names one restoring save kind.
Test steps and diagnostics
- Given
the
story-campaignadoption document, not the running game- When
- a reviewer reads
session-map-hints's saved-in field
- a reviewer reads
- Then
- where saved-in is present, it names exactly one copy-kinds row in this adoption; when it names anything else, the adoption is invalid, and this test fails
- where saved-in is absent, this test checks nothing
- Diagnostics
story-campaign-declaration-record
saved-in-joins-a-copy-kind · unlocked-cosmetics
declaration check: unlocked-cosmetics names an existing save kind when saved
Applies to the unlocked-cosmetics row.
This declaration check reads the adoption, not the running game. When unlocked-cosmetics has saved-in, it must name exactly one save kind in this adoption. When it names anything else, the adoption is invalid, and this test fails. With no saved-in, this test checks nothing. It says nothing about a family held in two places at once; one family names one restoring save kind.
Test steps and diagnostics
- Given
the
story-campaignadoption document, not the running game- When
- a reviewer reads
unlocked-cosmetics's saved-in field
- a reviewer reads
- Then
- where saved-in is present, it names exactly one copy-kinds row in this adoption; when it names anything else, the adoption is invalid, and this test fails
- where saved-in is absent, this test checks nothing
- Diagnostics
story-campaign-declaration-record
backup-copy-kind-joins · previous-campaign-checkpoint
declaration check: previous-campaign-checkpoint names an existing save kind
Applies to the previous-campaign-checkpoint row.
This declaration check reads the adoption, not the running game. campaign-slots must name exactly one save kind in this adoption. When it names anything else, the adoption is invalid, and this test fails.
Test steps and diagnostics
- Given
the
story-campaignadoption document, not the running game- When
- a reviewer compares
campaign-slotswith copy-kinds ids
- a reviewer compares
- Then
campaign-slotsnames exactly one copy-kinds row in this adoption; when it names anything else, the adoption is invalid, and this test fails
- Diagnostics
story-campaign-declaration-record
conflict-copy-kind-joins
declaration check: Row.id names an existing shared save kind
Does not apply to story-campaign: copy-conflicts has no rows.
earlier-point-copy-kind-joins · dungeon-retry
declaration check: dungeon-retry names an existing save kind
Applies to the dungeon-retry row.
This declaration check reads the adoption, not the running game. campaign-slots must name exactly one save kind in this adoption. When it names anything else, the adoption is invalid, and this test fails.
Test steps and diagnostics
- Given
the
story-campaignadoption document, not the running game- When
- a reviewer compares
campaign-slotswith copy-kinds ids
- a reviewer compares
- Then
campaign-slotsnames exactly one copy-kinds row in this adoption; when it names anything else, the adoption is invalid, and this test fails
- Diagnostics
story-campaign-declaration-record
account-family-names-account-ending
declaration check: Row.id names account deletion or reset as its end
Does not apply to story-campaign: no row in state-families matches lifetime account.
restore-earlier-override-has-backup
declaration check: Row.id's restore-earlier-copy override has a backup row
Does not apply to story-campaign: no row in copy-kinds matches unreadable-copy restore earlier copy.
restore-earlier-default-has-backup
declaration check: the default answer restore-earlier-copy for Row.id has a backup row
Does not apply to story-campaign: unreadable copy default is repair readable state, not restore earlier copy.
returning-family-uses-shared-authority
declaration check: Row.id returns from a cloud-synced or server-authoritative save kind
Does not apply to story-campaign: no row in state-families matches on-another-device returns.
one-conflict-row-per-scope
declaration check: each conflict scope has at most one row
Applies to story-campaign.
This declaration check reads the adoption, not the running game. Each represented conflict scope has at most one row. When a scope has more than one row, the adoption is invalid, and this test fails. With no conflict rows, this test checks nothing.
Test steps and diagnostics
- Given
the
story-campaignadoption document, not the running game- When
- a reviewer groups copy-conflicts rows by a this-copy-kind name or the all-synced-kinds scope
- Then
- no declared scope has more than one conflict row; when a scope has more than one row, the adoption is invalid, and this test fails; with no conflict rows, this test checks nothing
- Diagnostics
story-campaign-declaration-record
one-earlier-point-row-per-copy-kind
declaration check: each save kind has at most one earlier-point row
Applies to story-campaign.
This declaration check reads the adoption, not the running game. Each save kind has at most one earlier-point row. When more than one row names a save kind, the adoption is invalid, and this test fails. With no such rows, this test checks nothing.
Test steps and diagnostics
- Given
the
story-campaignadoption document, not the running game- When
- a reviewer groups earlier-point-returns rows by copy-kind
- Then
- no save kind is named by more than one earlier-point-returns row; when more than one row names a save kind, the adoption is invalid, and this test fails; with no such rows, this test checks nothing
- Diagnostics
story-campaign-declaration-record
copy-citations-state-their-duties · campaign-slots
declaration check: campaign-slots's required citations state their duties
Applies to the campaign-slots row.
This declaration check reads the adoption and cited prose, not the running game. save-data.campaign-contents must state the state-family set. save-data.campaign-checkpoints must state when a write succeeds and what the loss window is. save-data.campaign-slot-layout must state the residence, the authority, the identities, and the player selection that apply. When a duty is missing, the adoption is invalid, and this test fails. The test names the addresses and restates nothing from them.
Test steps and diagnostics
- Given
the
story-campaignadoption and the prose atsave-data.campaign-contents,save-data.campaign-checkpoints, andsave-data.campaign-slot-layout, not the running game- When
- a reviewer checks each citation's meaning
- Then
save-data.campaign-contentsnames the complete family set written and restored togethersave-data.campaign-checkpointsnames the trigger, success condition, final-write behavior, and quit or crash loss windowsave-data.campaign-slot-layoutnames residence, applicable sync or service acceptance and authority, layout, identities, and player selection- when a citation does not state its duty, the adoption is invalid, and this test fails
- Diagnostics
story-campaign-declaration-record
copy-citations-state-their-duties · settings-file
declaration check: settings-file's required citations state their duties
Applies to the settings-file row.
This declaration check reads the adoption and cited prose, not the running game. settings.session-hint-contents must state the state-family set. settings.timed-write must state when a write succeeds and what the loss window is. settings.current-file must state the residence, the authority, the identities, and the player selection that apply. When a duty is missing, the adoption is invalid, and this test fails. The test names the addresses and restates nothing from them.
Test steps and diagnostics
- Given
the
story-campaignadoption and the prose atsettings.session-hint-contents,settings.timed-write, andsettings.current-file, not the running game- When
- a reviewer checks each citation's meaning
- Then
settings.session-hint-contentsnames the complete family set written and restored togethersettings.timed-writenames the trigger, success condition, final-write behavior, and quit or crash loss windowsettings.current-filenames residence, applicable sync or service acceptance and authority, layout, identities, and player selection- when a citation does not state its duty, the adoption is invalid, and this test fails
- Diagnostics
story-campaign-declaration-record
backup-citations-state-their-duties · previous-campaign-checkpoint
declaration check: previous-campaign-checkpoint's backup citations state their duties
Applies to the previous-campaign-checkpoint row.
This declaration check reads the adoption and cited prose, not the running game. save-data.previous-checkpoint-policy must state how earlier copies are retained, how they are ordered, how a usable earlier copy is selected, and what a recovery can lose. menus.restore-previous-checkpoint must state the direct restore route. When a duty is missing, the adoption is invalid, and this test fails. The test names both addresses and restates nothing from them.
Test steps and diagnostics
- Given
the
story-campaignadoption and the prose atsave-data.previous-checkpoint-policyandmenus.restore-previous-checkpoint, not the running game- When
- a reviewer checks each citation's meaning
- Then
save-data.previous-checkpoint-policynames how earlier copies are retained, how they are ordered, how a usable earlier copy is selected, and the possible lost intervalmenus.restore-previous-checkpointnames the supported direct restore route- when a citation does not state its duty, the adoption is invalid, and this test fails
- Diagnostics
story-campaign-declaration-record
conflict-citation-states-its-duties
declaration check: Row.id's resolution citation states its duties
Does not apply to story-campaign: copy-conflicts has no rows.
earlier-return-citation-states-its-duties · dungeon-retry
declaration check: dungeon-retry's return citation states its duties
Applies to the dungeon-retry row.
This declaration check reads the adoption and cited prose, not the running game. dungeon.retry must state the earlier point, when activation is successful, the run identity when the point is inside a run, and whether another load remains possible. When a duty is missing, the adoption is invalid, and this test fails. The test names the address and restates nothing from it.
Test steps and diagnostics
- Given
the
story-campaignadoption and the prose atdungeon.retry, not the running game- When
- a reviewer checks the citation's meaning
- Then
dungeon.retrynames the earlier point, successful activation, run identity when applicable, and whether another load remains possible- when the citation does not state a duty, the adoption is invalid, and this test fails
- Diagnostics
story-campaign-declaration-record
copy-removal-citation-states-its-duty
declaration check: Row.id's removal citation states its duty
Does not apply to story-campaign: no row in copy-kinds matches removed-at normal quit or crash or death or slot delete or local profile delete or device data delete or account delete.
copy-mode-citation-states-its-duty
declaration check: Row.id's mode citation states its duty
Does not apply to story-campaign: no row in copy-kinds matches mode-gated only in named modes.
player-mode-return-is-mode-gated
declaration check: Row.id's player-mode return is mode-gated
Does not apply to story-campaign: no row in earlier-point-returns matches after-load player mode decides.
earlier-point-mode-citation-states-its-duty
declaration check: Row.id's mode citation states its duty
Does not apply to story-campaign: no row in earlier-point-returns matches mode-gated only in named modes.
persistence-holds
the persistence mechanism holds for the whole run
Applies to story-campaign.
Across each of quit, crash, death, slot deletion, profile deletion, and device change that the adoption can construct, plus each constructible damage and build-mismatch case, each family's value on both sides of every constructed boundary matches its row and the numbered settlement order holds. Every load the adoption can construct refuses partial state and activates all carried families together only when accepted. For a case that the adoption cannot construct, this test checks nothing. The pack's default audit seeds apply unless the adoption supplies its own.
Test steps and diagnostics
- Holds
every state family's distinguishable value on both sides of every constructed boundary matched its row; every commit, candidate, conflict, build comparison, damage response, activation, earlier return, deletion, device return, and settlement report that the adoption constructed followed the mechanism's numbered order; every constructed load that was partial or refused activated no state; every accepted load activated all carried families together; where the adoption can construct no load, conflict, damage, build mismatch, or earlier return, this test checks nothing about that case
- Seeds
["audit-a","audit-b"]- Scope
each of quit, crash, death, slot deletion, profile deletion, and device change that the adoption can construct, plus each constructible damage and build-mismatch case- Diagnostics
story-campaign-state-boundary-tracestory-campaign-commit-logstory-campaign-candidate-setstory-campaign-settlement-tracestory-campaign-load-resultstory-campaign-active-state-sourcestory-campaign-settlement-reportstory-campaign-first-ordering-violation
JSONthe adoption as one file
Answers you try on this page do not change this file. To change an adoption, open it in the authoring tool.
{
"contract": "state-persistence-scope",
"version": 2,
"summary": "This contract covers which state is kept after the end of a run or session, a quit, a crash, death, the deletion of saved data, and a move to another device. It also covers local saves, manual slots, cloud copies, server profiles, backups, conflicts, and version changes. This contract does not cover time that passes while the game is closed, replay guarantees, file formats, or the shape of saved numbers. The Suspension and catch-up contract covers time while the game is closed. The Replay scope contract covers replay guarantees.",
"questions": {
"unreadable-copy-default": {
"asks": "By default, what happens when saved state cannot be read?",
"rationale": "Lost progress is the most visible failure of a save system. This answer is the default answer. A save kind can choose another answer in its own row.",
"when": {
"row-count": {
"copy-kinds": "non-empty"
}
},
"otherwise": "No saved copy can be opened later, so unreadable saved state never reaches a load attempt.",
"options": {
"refuse-load": {
"meaning": "The unreadable copy stays closed. A city slot remains listed, but none of its progress becomes active.",
"semantics": "Loading stops before any state from that copy becomes active. The copy is neither replaced nor repaired by this response."
},
"start-fresh": {
"meaning": "Play starts from the normal starting state. A damaged puzzle slot opens with its first board instead of the old board.",
"semantics": "The starting state becomes active instead of state from the unreadable copy. The response does not by itself delete or overwrite that copy."
},
"restore-earlier-copy": {
"meaning": "An earlier copy can replace the unreadable copy through a route that the game supports. Restoring a file by hand is one such route. A factory game continues after the player chooses the backup that was made before the damaged autosave.",
"semantics": "A declared manual, in-game, or automatic recovery route can activate an earlier copy. The newest usable earlier copy under the matching backup rule becomes active."
},
"repair-readable-state": {
"meaning": "Readable progress returns and damaged parts reset. A dungeon keeps cleared floors but rebuilds one broken room record.",
"semantics": "Readable state becomes active as one repaired result. Every unreadable part takes the starting value named by the cited repair rule."
},
"warn-and-try": {
"meaning": "The player can cancel or try the unreadable copy. A colony save opens only after its damage warning is accepted.",
"semantics": "No state from the copy becomes active before the explicit choice. Accepting the warning permits an attempt but does not promise success."
}
}
},
"older-build-default": {
"asks": "By default, what happens when an older game build wrote the copy?",
"rationale": "An update can make a player's progress impossible to load, or change it permanently. Choose how the current build protects that progress.",
"when": {
"row-count": {
"copy-kinds": "non-empty"
}
},
"otherwise": "No loadable copy exists, so an older build has no saved state to open.",
"options": {
"refuse-load": {
"meaning": "The older copy stays closed. A world from last year's rules cannot open in the current build.",
"semantics": "Loading stops before state written by the older build becomes active."
},
"convert-and-load": {
"meaning": "The game updates the old state and opens it. A factory save gains the current recipe records before play resumes.",
"semantics": "A declared conversion produces current-build state before activation."
},
"warn-and-load": {
"meaning": "The old copy is tried only after a warning. A racing career from an earlier patch opens after the player accepts the risk.",
"semantics": "The player accepts a compatibility warning before old-build state becomes active. Acceptance permits an attempt but does not promise success."
}
}
},
"newer-build-default": {
"asks": "By default, what happens when a newer game build wrote the copy?",
"rationale": "Going back to an older build can erase newer content. Choose whether the player may take that risk.",
"when": {
"row-count": {
"copy-kinds": "non-empty"
}
},
"otherwise": "No loadable copy exists, so a newer build has no saved state to open.",
"options": {
"refuse-load": {
"meaning": "The newer copy stays closed. A world from a later patch remains unavailable in this build.",
"semantics": "Loading stops before state written by the newer build becomes active."
},
"warn-and-load": {
"meaning": "The newer copy is tried only after a warning. A sandbox world opens after the player accepts that newer content may be lost.",
"semantics": "The player accepts a compatibility warning before newer-build state becomes active. Acceptance permits an attempt but does not promise success."
}
}
}
},
"declares": {
"values": {},
"rows": {
"state-families": {
"description": "List each state family. A state family is a group of state that can change later play. For each state family, give how long it lasts and what happens to it at a quit, a crash, death, a deletion, and a move to another device.",
"when-empty": "No changing game state is covered, so this adoption makes no persistence promise.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for the state family in your game's words, such as campaign-progress, run-inventory, unlocks, or settings."
},
"state-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say which fields belong to this family and at which points the family can affect later play. For a family that does not return after a quit, after a crash, or on another device, the rules also name the value that the family starts from."
},
"lifetime": {
"type": "string",
"required": true,
"options": [
"run",
"session",
"slot",
"profile",
"device",
"account",
"until-event",
"not-kept"
],
"description": "How long this state lasts. Run: the state belongs to one attempt; a roguelike's carried items are lost when that attempt ends. Session: the state belongs to one continuous visit; a co-op lobby choice is lost when the party leaves that lobby. Slot: the state belongs to one player-selected save; campaign progress is lost when that slot is deleted. Profile: the state belongs to one local player profile; unlocks are lost when that profile is deleted. Device: the state belongs to one installation; local settings are lost when that device data is erased. Account: the state belongs to one account; online settings are lost when that account is deleted or reset. Until event: the state lasts to one named game event; dropped currency is lost at the next death. Not kept: the state is lost at its first named interruption; an enemy's alert meter does not return after quitting."
},
"scope-starts-in": {
"type": "citation",
"when": {
"row": {
"lifetime": [
"run",
"session"
]
}
},
"description": "Where your game's rules say when this attempt or continuous visit starts. The player can observe the start. A run may start when the character enters the dungeon; a session may start when the party joins a lobby."
},
"scope-ends-in": {
"type": "citation",
"when": {
"row": {
"lifetime": [
"run",
"session",
"slot",
"profile",
"device",
"account",
"not-kept"
]
}
},
"description": "Where your game's rules say when this lifetime ends. The player can observe the ending. It may be an attempt result, leaving a lobby, deleting a player-selected slot, deleting a local profile, erasing device data, deleting an account, or the first interruption that discards the value."
},
"lifetime-event-declared-in": {
"type": "citation",
"when": {
"row": {
"lifetime": [
"until-event"
]
}
},
"description": "Where your game's rules say which named game event destroys the value that the family had before the event. A recovery point may last until the next death after it appears."
},
"saved-in": {
"type": "string",
"pattern": "kebab-case",
"description": "The save kind that restores this family. When the field is absent, no copy restores the family."
},
"after-normal-quit": {
"type": "string",
"required": true,
"options": [
"returns-last-commit",
"does-not-return"
],
"description": "Whether this state returns after a normal quit. The answer returns-last-commit means that the state returns as it was at the last successful save. A campaign returns its last checkpoint; an unsaved match score does not."
},
"after-crash": {
"type": "string",
"required": true,
"options": [
"returns-last-commit",
"does-not-return"
],
"description": "Whether this state returns after a crash. The answer returns-last-commit means that the state returns as it was at the last successful save before the crash. A world that is saved on a timer may lose the changes made since its last save, and still return that save."
},
"after-death": {
"type": "string",
"required": true,
"options": [
"keeps-value",
"clears-value"
],
"description": "Whether death keeps this value. Roguelike unlocks may remain while the dead run's inventory clears."
},
"after-slot-delete": {
"type": "string",
"required": true,
"options": [
"keeps-value",
"clears-value",
"no-slot"
],
"description": "Whether this value is kept when the related player-selected slot is deleted. The storage rule of a save kind with several player-selected slots names that slot. For a save kind with one current copy, answer no-slot."
},
"after-local-profile-delete": {
"type": "string",
"required": true,
"options": [
"keeps-value",
"clears-value",
"no-profile"
],
"description": "Whether this value is kept when the related local player profile is deleted. The storage rule of the save kind that restores this family names that profile. For a family with no such profile, answer no-profile."
},
"on-another-device": {
"type": "string",
"required": true,
"options": [
"returns",
"does-not-return"
],
"description": "Whether the value returns when the same eligible player continues on another device. A server profile may return; local settings may not."
}
}
},
"copy-kinds": {
"description": "List each save kind. A save kind is one kind of saved copy that is written and loaded independently, such as campaign slots, profile data, cloud settings, or a server profile.",
"when-empty": "No saved copy can be opened later. Loading cannot restore state, compare builds, recover damage, or resolve a conflict.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this save kind in your game's words, such as campaign-slots, unlock-profile, or account-settings."
},
"contains-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say which state families this save kind writes and restores together. The rules list the state-family ids."
},
"write-trigger": {
"type": "string",
"required": true,
"options": [
"player-save",
"checkpoint",
"continuous",
"timer",
"service-owned"
],
"description": "What starts a save: a save action, a named checkpoint, each accepted change, a timer, or the service's own rule."
},
"write-rule-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say exactly when a save begins, what makes it successful, and which later changes a quit or a crash can lose."
},
"residence": {
"type": "string",
"required": true,
"options": [
"local-device",
"cloud-synced",
"server-authoritative"
],
"description": "Where the copy that decides the restored state is kept: on this device, in copies that are synced between devices, or on a game service. Synced copies can disagree. A profile that the service controls uses the state that the service accepted."
},
"layout": {
"type": "string",
"required": true,
"options": [
"one-current",
"manual-slots"
],
"description": "How the save is presented: one current copy or several player-selected slots. A slot is one named load choice in the storage rule; retained earlier copies belong in the backup list."
},
"storage-rule-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say where the copy is kept, which sync or service has authority over it, and how the player selects it. For a save kind with several player-selected slots, the rules also name the identity of each slot, and that identity does not change. For a save kind with one current copy, the rules name no player-selected slot."
},
"unreadable-copy": {
"type": "string",
"required": true,
"options": [
"use-game-default",
"refuse-load",
"start-fresh",
"restore-earlier-copy",
"repair-readable-state",
"warn-and-try"
],
"description": "What happens when a copy of this save kind cannot be read. Use the default answer, or choose another answer for this row."
},
"older-build-copy": {
"type": "string",
"required": true,
"options": [
"use-game-default",
"refuse-load",
"convert-and-load",
"warn-and-load"
],
"description": "What happens when an older game build wrote a copy of this save kind. Use the default answer, or choose another answer for this row."
},
"newer-build-copy": {
"type": "string",
"required": true,
"options": [
"use-game-default",
"refuse-load",
"warn-and-load"
],
"description": "What happens when a newer game build wrote a copy of this save kind. Use the default answer, or choose another answer for this row."
},
"mode-gated": {
"type": "string",
"options": [
"only-in-named-modes"
],
"description": "This save kind exists only in named player modes. A restore point may exist in a practice mode and a standard mode but not in a challenge mode."
},
"mode-rule-declared-in": {
"type": "citation",
"when": {
"row": {
"mode-gated": [
"only-in-named-modes"
]
}
},
"description": "Where your game's rules say in which player modes this save kind exists. The rules name every such mode and confirm that the save kind is absent in every other mode."
},
"removed-at": {
"type": "string",
"options": [
"normal-quit",
"crash",
"death",
"slot-delete",
"local-profile-delete",
"device-data-delete",
"account-delete"
],
"description": "The event that removes this copy from later load choices. A permadeath save may be removed when the character dies. For a save kind with several player-selected slots, this event removes the copy of the deleted slot, not the whole save kind."
},
"removal-rule-declared-in": {
"type": "citation",
"when": {
"row": {
"removed-at": [
"normal-quit",
"crash",
"death",
"slot-delete",
"local-profile-delete",
"device-data-delete",
"account-delete"
]
}
},
"description": "Where your game's rules say exactly which event removes the copy and what the player sees afterward."
}
}
},
"backup-copies": {
"description": "List save kinds that retain an earlier copy for supported recovery.",
"when-empty": "No save kind promises an earlier usable copy, so damage cannot be answered by restoring one.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for this earlier copy in your game's words, such as previous-campaign-checkpoint."
},
"copy-kind": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"description": "The save kind protected by this earlier copy."
},
"backup-rule-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say when an earlier copy is kept, how many earlier copies remain, and which later progress a recovery loses."
},
"restore-rule-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say which supported route opens an earlier copy. The rules say whether recovery is automatic, happens in the game, or is a file step outside the game."
}
}
},
"copy-conflicts": {
"description": "List shared save kinds that can present two complete but disagreeing copies. One adoption has at most one row per conflict scope: one for each named save kind and one for all synced kinds.",
"when-empty": "No shared save kind can present two disagreeing copies, so no conflict response runs.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for the conflict in your game's words, such as campaign-slot-sync."
},
"scope": {
"type": "string",
"required": true,
"options": [
"this-copy-kind",
"all-synced-kinds"
],
"description": "Whether the response settles one save kind or every synced save kind together. One cloud dialog may settle campaign slots and quicksaves as a set."
},
"copy-kind": {
"type": "string",
"when": {
"row": {
"scope": [
"this-copy-kind"
]
}
},
"pattern": "kebab-case",
"description": "The one cloud-synced or service save kind whose copies can disagree."
},
"response": {
"type": "string",
"required": true,
"options": [
"select-by-rule",
"player-chooses-copy",
"refuse-until-resolved"
],
"description": "What happens before any disagreeing state becomes active. A fixed rule may always prefer the copy the game service holds; the player may choose between dated copies; or neither copy may open until sync is repaired."
},
"resolution-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say how this conflict is handled. The rules say how disagreement is detected. They say how the identities of the copies are compared. They say which copies are selected or kept. They name the event that clears the conflict."
}
}
},
"earlier-point-returns": {
"description": "List save kinds that can return to an earlier point of the same continuous play, whether or not the game names a run. One adoption has at most one row per save kind.",
"when-empty": "No save kind can return to an earlier point of the same continuous play, so the player cannot load a save to try again.",
"record": {
"id": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"unique": true,
"description": "A short name for the earlier return point in your game's words, such as dungeon-retry or quicksave."
},
"copy-kind": {
"type": "string",
"required": true,
"pattern": "kebab-case",
"description": "The save kind that can return play to the earlier point."
},
"after-load": {
"type": "string",
"required": true,
"options": [
"resume-stays",
"resume-is-used-once",
"player-mode-decides"
],
"description": "Whether the same earlier point remains available after a successful load. A practice mode may keep a boss checkpoint while an iron mode allows one return."
},
"return-rule-declared-in": {
"type": "citation",
"required": true,
"description": "Where your game's rules say what the earlier point is, when activation is successful, and whether another load remains possible."
},
"mode-gated": {
"type": "string",
"options": [
"only-in-named-modes"
],
"description": "This earlier return point exists only in named player modes. A quicksave may be available in a practice mode but absent in a challenge mode."
},
"mode-rule-declared-in": {
"type": "citation",
"when": {
"row": {
"mode-gated": [
"only-in-named-modes"
]
}
},
"description": "Where your game's rules say in which player modes this earlier return point exists. The rules name every such mode and confirm that the point is absent in every other mode."
}
}
}
}
},
"rules": {},
"origin": "https://opengdd.org/contracts/state-persistence-scope-2",
"mechanism": [
"This text decides the order of the steps for state-family identity, scope starts and endings, commits, loads, damage, build mismatch, copy conflicts, deletion, and device change. The questions and rows supply choices and cited game rules. They do not change the order.",
"A **run** begins at the player-observable attempt start named by `scope-starts-in` and ends at the result or exit named by `scope-ends-in`. Victory, death, and abandonment are common endings, but only the cited game rules decide. A save or load does not start or end a run. A **session** begins when play enters the world, match, lobby, or profile context named by `scope-starts-in` and ends at the exit, switch, or reset named by `scope-ends-in`. It continues through a pause, background interval, closure, or relaunch that returns to that same context; a missing commit may still prevent its earlier value from returning.",
"A **save kind** is one row of `copy-kinds`. A **commit** is one coherent accepted write of a save kind. A **copy** is a load candidate with its save-kind id, copy identity, any slot identity, build identity, commit identity, and complete state-family set. A **conflict** exists when the cited rule finds two accepted shared copies in one declared conflict scope that disagree and neither has already replaced the other. An **earlier copy** is a retained older commit named by a backup row.",
"### Change and commit",
"1. When state changes, keep each `state-families` row as one family under its cited game rule. The row's lifetime is the longest time for which a value of the family is kept. An `until-event` value ends at its cited game event. A boundary result may clear a value before its lifetime ends only when the row says so.",
"2. Evaluate run and session starts and endings from their separate cited player-observable events. A save action, automatic write, timer tick, process launch, process close, or load does not create a new run or session by itself.",
"3. Start a commit of a save kind only at its selected write trigger. A player save follows the cited save action. A checkpoint follows its named game event. Continuous writing follows each accepted change named by its rule. A timer follows the cited interval. A service-owned copy follows its authority's cited acceptance event.",
"4. A commit becomes loadable only when `write-rule-declared-in` reports success for the complete `contains-declared-in` set. A failed or partial write never replaces the last successful commit. Earlier copies are retained and ordered only by their backup and storage rules.",
"### Quit, crash, and return",
"5. On a normal quit, perform a final commit only when the cited write rule of the save kind requires one. For each state row, `returns-last-commit` restores its latest successful commit on return; `does-not-return` starts from the cited post-quit value. Uncommitted changes do not return. A copy removed at normal quit is no longer a later load choice.",
"6. On a crash, no unfinished commit succeeds. For each state row, `returns-last-commit` restores the last commit that succeeded before the crash; `does-not-return` starts from the cited post-crash value. With a timer or a checkpoint, a crash can therefore lose the changes made since the last successful commit. These changes are the possible loss window, and the cited write rule states it. A copy removed at crash is no longer a later load choice.",
"7. Select the requested slot or profile before activating state. A `one-current` save kind selects its current copy. `manual-slots` selects the player's named slot. A retained earlier copy is not another layout; the damage response and matching backup rule may select it later, and a backup row's cited restore route is also available to the player directly, independent of any damage response.",
"8. A local-device copy is available only on that device unless another cited transfer exists outside this adoption. A cloud-synced save kind obtains every accepted device and cloud candidate before conflict handling. A server-authoritative save kind obtains the service's accepted copy and does not treat an unaccepted local cache as equal authority. Read each candidate's save-kind, slot, build, and commit identity now. A candidate whose identity cannot be read is not a conflict candidate. If no candidate remains, run the damage response of the requested save kind immediately, without conflict or build comparison. A readable header with an unreadable gameplay body remains a candidate for steps 9 and 10, then reaches the damage response in step 11.",
"### Conflict, compatibility, and damage",
"9. If readable shared candidates conflict, apply the matching row's response before reading their gameplay state. A one-kind row settles only that save kind; a whole-set row settles every synced save kind together. `select-by-rule` chooses one complete copy. `player-chooses-copy` waits for one complete-copy choice. `refuse-until-resolved` activates neither. No response merges fields.",
"10. Compare the selected copy's writing build with the running build. Apply the save-kind override when present; otherwise apply the matching default answer for an older or a newer build. Refusal activates no state. Conversion produces one current-build copy before activation. A warning precedes the attempt and does not promise that it succeeds. If conversion fails, refuse the load and leave the source copy unchanged unless its cited rule says otherwise.",
"11. Read the selected copy as one coherent unit. If it is unreadable, apply the save-kind override or `unreadable-copy-default`. Refusal activates nothing. `start-fresh` activates the cited starting state without silently deleting the unreadable copy. Earlier-copy recovery follows the matching backup row, selects the newest usable earlier copy, and reports the lost interval. Repair activates one coherent repaired result and resets every unreadable part under the cited rule; failure to produce a coherent result refuses the load. Warning waits for the player's choice before any attempted activation.",
"12. Activate all state families in the accepted copy together. State families saved in another save kind keep the already active value from that kind; a state family without `saved-in` is never restored by this load.",
"13. If the accepted copy has an `earlier-point-returns` row, return continuous play to its cited earlier point. If that point is inside a run, continue the same run identity. Apply `after-load` only after successful activation: keeping the resume leaves it available, using it once makes it unavailable, and a player-mode answer follows the mode that was set before the load.",
"### Death, deletion, and another device",
"14. On death, apply every state's `after-death` result. `clears-value` removes the earlier value before later play. `keeps-value` retains it. If death is also the cited run end, every run-lifetime value clears even if no save action occurs. If it is the cited event for an `until-event` value, that value clears as well. Longer-lived unlocks, settings, and profile state follow their own rows. A copy with `removed-at: death` disappears from the load choices after these state results are settled.",
"15. On slot deletion, clear each `clears-value` row tied to that slot and keep each `keeps-value` row. `no-slot` records that the state has no related player-selected slot. Deleting a local profile applies `after-local-profile-delete` the same way and includes any slots whose cited rule makes them children of that profile. Device-data deletion ends device-lifetime state under its cited rule. Account deletion or reset ends account-lifetime state through `scope-ends-in`. At each event, remove every copy whose `removed-at` value names it.",
"16. On another eligible device, `returns` obtains the family from its cloud-synced or server-authoritative copy before activation and still runs conflict handling. `does-not-return` starts from the cited new-device value. A device change does not by itself delete the source device's copy.",
"17. After every event, no earlier value affects later play once its lifetime has ended or its selected boundary result cleared it. A later game rule may derive a new value, but that is new state rather than restoration of the cleared value. A mode-gated copy or earlier return point exists only while the cited player mode permits it.",
"Every settlement report identifies the state-family id, save-kind id or its absence, event, scope before and after, last successful commit when read, selected copy and build when loaded, override or default answer used, state result, and any lost interval. Conflict reports also identify both candidate commits and the selected response. Damage reports identify refusal, starting state, earlier-copy recovery, repaired parts, or warning choice without claiming an unattempted load succeeded."
],
"pack": "sha256:4ec64b865a0ade721bc27b72550f9d1d57e3e93d9a060e72b6eba5890aea75fd",
"answers": {
"unreadable-copy-default": "repair-readable-state",
"older-build-default": "convert-and-load",
"newer-build-default": "refuse-load"
},
"values": {},
"rows": {
"state-families": [
{
"id": "campaign-progress",
"state-declared-in": "campaign.progress-fields",
"lifetime": "slot",
"scope-ends-in": "save-data.delete-campaign-slot",
"saved-in": "campaign-slots",
"after-normal-quit": "returns-last-commit",
"after-crash": "returns-last-commit",
"after-death": "keeps-value",
"after-slot-delete": "clears-value",
"after-local-profile-delete": "no-profile",
"on-another-device": "does-not-return"
},
{
"id": "dungeon-attempt",
"state-declared-in": "dungeon.attempt-fields",
"lifetime": "run",
"scope-starts-in": "dungeon.enter",
"scope-ends-in": "dungeon.victory-death-or-abandonment",
"saved-in": "campaign-slots",
"after-normal-quit": "returns-last-commit",
"after-crash": "returns-last-commit",
"after-death": "clears-value",
"after-slot-delete": "clears-value",
"after-local-profile-delete": "no-profile",
"on-another-device": "does-not-return"
},
{
"id": "session-map-hints",
"state-declared-in": "map.session-hint-fields",
"lifetime": "session",
"scope-starts-in": "map.open-campaign",
"scope-ends-in": "map.leave-campaign",
"saved-in": "settings-file",
"after-normal-quit": "returns-last-commit",
"after-crash": "returns-last-commit",
"after-death": "keeps-value",
"after-slot-delete": "no-slot",
"after-local-profile-delete": "no-profile",
"on-another-device": "does-not-return"
},
{
"id": "unlocked-cosmetics",
"state-declared-in": "cosmetics.unlock-fields",
"lifetime": "profile",
"scope-ends-in": "profile.delete-or-reset",
"saved-in": "campaign-slots",
"after-normal-quit": "returns-last-commit",
"after-crash": "returns-last-commit",
"after-death": "keeps-value",
"after-slot-delete": "keeps-value",
"after-local-profile-delete": "clears-value",
"on-another-device": "does-not-return"
}
],
"copy-kinds": [
{
"id": "campaign-slots",
"contains-declared-in": "save-data.campaign-contents",
"write-trigger": "checkpoint",
"write-rule-declared-in": "save-data.campaign-checkpoints",
"residence": "local-device",
"layout": "manual-slots",
"storage-rule-declared-in": "save-data.campaign-slot-layout",
"unreadable-copy": "warn-and-try",
"older-build-copy": "warn-and-load",
"newer-build-copy": "use-game-default"
},
{
"id": "settings-file",
"contains-declared-in": "settings.session-hint-contents",
"write-trigger": "timer",
"write-rule-declared-in": "settings.timed-write",
"residence": "local-device",
"layout": "one-current",
"storage-rule-declared-in": "settings.current-file",
"unreadable-copy": "start-fresh",
"older-build-copy": "use-game-default",
"newer-build-copy": "warn-and-load"
}
],
"backup-copies": [
{
"id": "previous-campaign-checkpoint",
"copy-kind": "campaign-slots",
"backup-rule-declared-in": "save-data.previous-checkpoint-policy",
"restore-rule-declared-in": "menus.restore-previous-checkpoint"
}
],
"earlier-point-returns": [
{
"id": "dungeon-retry",
"copy-kind": "campaign-slots",
"after-load": "resume-is-used-once",
"return-rule-declared-in": "dungeon.retry"
}
],
"copy-conflicts": []
}
}