Determinism & replay

Story campaign

story-campaign · an adoption of State persistence scope, version 1

Tests included

Use in your game

Opens the authoring tool with these answers and settings already filled in. You still add your game's own rules and tuning.

What is in the ZIP

The ZIP holds the adoption and its acceptance tests. In the authoring tool, choose Add contract and pick this ZIP. If you keep your design as files, put them in contracts/.

The contract behind it

Covers what survives the end of a run or session, a quit, a crash, death, deleting saved data, and moving to another device. It also covers local saves, manual slots, cloud copies, server profiles, backups, conflicts, and version changes. Closed-game time, replay guarantees, file formats, and the shape of saved numbers stay elsewhere.

Read the full State persistence scope contract →

Questions

The supplied answer is marked on each question. Pick other answers to see what changes. Nothing is saved here: the ZIP and the authoring tool use the supplied answers.

What normally 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.
Choices for What normally happens when saved state cannot be read?
The unreadable copy stays closed. A city slot remains listed, but none of its progress becomes active.

Loading stops before any state from that copy becomes active. The copy is neither replaced nor repaired by this response.

Play starts from normal fresh state. A damaged puzzle slot opens with its first board instead of the old board.

Fresh state becomes active instead of state from the unreadable copy. The response does not by itself delete or overwrite that copy.

An earlier copy can replace it through the supported route, including a manual file restore. A factory returns after choosing the backup before its damaged autosave.

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.

Supplied answer Readable progress returns and damaged parts reset. A dungeon keeps cleared floors but rebuilds one broken room record.

Readable state becomes active as one repaired result. Every unreadable part takes the fresh value named by the cited repair rule.

The player can cancel or try the unreadable copy. A colony save opens only after its damage warning is accepted.

No state from the copy becomes active before the explicit choice. Accepting the warning permits an attempt but does not promise success.

Why this is asked

Lost progress is the most visible failure a save system has. Choose the result players should expect first.

What normally 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.
Choices for What normally happens when an older game build wrote the copy?
The older copy stays closed. A world from last year's rules cannot open in the current build.

Loading stops before state written by the older build becomes active.

Supplied answer The game updates the old state and opens it. A factory save gains the current recipe records before play resumes.

A declared conversion produces current-build state before activation.

The old copy is tried only after a warning. A racing career from an earlier patch opens after the player accepts the risk.

The player accepts a compatibility warning before old-build state becomes active. Acceptance permits an attempt but does not promise success.

Why this is asked

Updates can strand a player's progress or change it permanently. Choose how the current build protects that progress.

What normally 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.
Choices for What normally happens when a newer game build wrote the copy?
Supplied answer The newer copy stays closed. A world from a later patch remains unavailable in this build.

Loading stops before state written by the newer build becomes active.

The newer copy is tried only after a warning. A sandbox world opens after the player accepts that newer content may be lost.

The player accepts a compatibility warning before newer-build state becomes active. Acceptance permits an attempt but does not promise success.

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

IdState declared inLifetimeScope starts inScope ends inLifetime event declared inSaved inAfter normal quitAfter crashAfter deathAfter slot deleteAfter local profile deleteOn another device
campaign-progresscampaign.progress-fieldsslotsave-data.delete-campaign-slotcampaign-slotsreturns-last-commitreturns-last-commitkeeps-valueclears-valueno-profiledoes-not-return
dungeon-attemptdungeon.attempt-fieldsrundungeon.enterdungeon.victory-death-or-abandonmentcampaign-slotsreturns-last-commitreturns-last-commitclears-valueclears-valueno-profiledoes-not-return
session-map-hintsmap.session-hint-fieldssessionmap.open-campaignmap.leave-campaignsettings-filereturns-last-commitreturns-last-commitkeeps-valueno-slotno-profiledoes-not-return
unlocked-cosmeticscosmetics.unlock-fieldsprofileprofile.delete-or-resetcampaign-slotsreturns-last-commitreturns-last-commitkeeps-valuekeeps-valueclears-valuedoes-not-return

Copy kinds

copy-kinds

IdContains declared inWrite triggerWrite rule declared inResidenceLayoutStorage rule declared inUnreadable copyOlder build copyNewer build copyMode gatedMode rule declared inRemoved atRemoval rule declared in
campaign-slotssave-data.campaign-contentscheckpointsave-data.campaign-checkpointslocal-devicemanual-slotssave-data.campaign-slot-layoutwarn-and-trywarn-and-loaduse-game-default
settings-filesettings.session-hint-contentstimersettings.timed-writelocal-deviceone-currentsettings.current-filestart-freshuse-game-defaultwarn-and-load

Backup copies

backup-copies

IdCopy kindBackup rule declared inRestore rule declared in
previous-campaign-checkpointcampaign-slotssave-data.previous-checkpoint-policymenus.restore-previous-checkpoint

Copy conflicts

copy-conflicts

This adoption declares no copy conflicts.

Earlier point returns

earlier-point-returns

IdCopy kindAfter loadReturn rule declared inMode gatedMode rule declared in
dungeon-retrycampaign-slotsresume-is-used-oncedungeon.retry
Test inputsscope and seeds

Some tests need a scope or seeds from the adoption before they can run.

This adoption supplies none; every test uses its defaults.

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

scenarioper state-families row

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; an unavailable event asserts nothing. The test names the addresses and restates nothing from them.

Test steps and diagnostics
Given

a distinguishable dungeon-attempt value before, during, and after its run

When
  • play crosses the attempt start at dungeon.enter, the run ending at dungeon.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
Then
  • the run identity begins only at dungeon.enter and ends only at dungeon.victory-death-or-abandonment
  • each constructed save, automatic write, timer tick, load, close, and relaunch does not by itself begin or end the run; an event the adoption cannot construct asserts nothing
Diagnostics
  • story-campaign-state-boundary-trace
  • story-campaign-run-identity

session-scope-uses-cited-events · session-map-hints

session-map-hints keeps one session through returns to the same context

scenarioper state-families row

Applies to the session-map-hints row.

The session for session-map-hints begins at map.open-campaign, survives 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. An unavailable event or run boundary asserts nothing. Its 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-hints value in the session that starts at map.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 run family, play also crosses that family's run boundary
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; an event the adoption cannot construct asserts nothing
  • crossing a run boundary declared by another family does not by itself end this session; where the adoption declares no run family, this clause asserts nothing
  • 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-trace
  • story-campaign-session-identity

copy-carries-complete-identity · campaign-slots

campaign-slots copies carry complete identity and family membership

scenarioper copy-kinds row

Applies to the campaign-slots row.

A copy of 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 copy 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
Diagnostics
  • story-campaign-commit-log
  • story-campaign-candidate-set

copy-carries-complete-identity · settings-file

settings-file copies carry complete identity and family membership

scenarioper copy-kinds row

Applies to the settings-file row.

A copy of 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 copy 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
Diagnostics
  • story-campaign-commit-log
  • story-campaign-candidate-set

conflict-has-two-unreplaced-accepted-copies

Row.id is a conflict only for two accepted unreplaced copies

scenarioper copy-conflicts row

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

scenarioper state-families row

Applies to the campaign-progress row.

campaign-progress has a maximum lifetime of one player-selected slot. Each shorter boundary 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-progress changes from an earlier value to a distinguishable value

When
  • the value is read immediately before and after every declared persistence boundary that story-campaign can construct
Then
  • the earlier value affects play for no longer than one player-selected slot
  • a shorter boundary 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-trace
  • story-campaign-active-state-source

state-family-follows-maximum-lifetime · dungeon-attempt

dungeon-attempt follows its declared maximum lifetime

scenarioper state-families row

Applies to the dungeon-attempt row.

dungeon-attempt has a maximum lifetime of one run. Each shorter boundary 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-attempt changes from an earlier value to a distinguishable value

When
  • the value is read immediately before and after every declared persistence boundary that story-campaign can construct
Then
  • the earlier value affects play for no longer than one run
  • a shorter boundary 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-trace
  • story-campaign-active-state-source

state-family-follows-maximum-lifetime · session-map-hints

session-map-hints follows its declared maximum lifetime

scenarioper state-families row

Applies to the session-map-hints row.

session-map-hints has a maximum lifetime of one session. Each shorter boundary 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-hints changes from an earlier value to a distinguishable value

When
  • the value is read immediately before and after every declared persistence boundary that story-campaign can construct
Then
  • the earlier value affects play for no longer than one session
  • a shorter boundary 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-trace
  • story-campaign-active-state-source

state-family-follows-maximum-lifetime · unlocked-cosmetics

unlocked-cosmetics follows its declared maximum lifetime

scenarioper state-families row

Applies to the unlocked-cosmetics row.

unlocked-cosmetics has a maximum lifetime of one local player profile. Each shorter boundary 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-cosmetics changes from an earlier value to a distinguishable value

When
  • the value is read immediately before and after every declared persistence boundary that story-campaign can construct
Then
  • the earlier value affects play for no longer than one local player profile
  • a shorter boundary 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-trace
  • story-campaign-active-state-source

until-event-value-ends-at-cited-event

Row.id ends at its cited event

scenarioper state-families row

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

scenarioper state-families row

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

scenarioper state-families row

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-progress value before save-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-slot and cannot affect later play
  • the source record distinguishes an equal value derived later as new state
Diagnostics
  • story-campaign-state-boundary-trace
  • story-campaign-active-state-source

slot-and-profile-end-at-cited-event · unlocked-cosmetics

unlocked-cosmetics ends at its cited profile boundary

scenarioper state-families row

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-cosmetics value before profile.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-reset and cannot affect later play
  • the source record distinguishes an equal value derived later as new state
Diagnostics
  • story-campaign-state-boundary-trace
  • story-campaign-active-state-source

commit-starts-only-at-selected-trigger · campaign-slots

campaign-slots starts a commit only at its selected trigger

scenarioper copy-kinds row

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-slots commit starts at the cited checkpoint event and no commit starts at a neighboring event
  • the commit log records which cited event started it
Diagnostics
  • story-campaign-commit-log

commit-starts-only-at-selected-trigger · settings-file

settings-file starts a commit only at its selected trigger

scenarioper copy-kinds row

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-file commit starts at the cited timer interval and no commit starts at a neighboring event
  • the commit log records which cited event started it
Diagnostics
  • story-campaign-commit-log

commit-loadable-only-after-complete-success · campaign-slots

campaign-slots replaces its last commit only after complete success

scenarioper copy-kinds row

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-slots commit and later complete-success, failed, and partial write attempts under save-data.campaign-checkpoints

When
  • each attempt is followed by a return that requests campaign-slots
Then
  • only the attempt reported successful for the complete set at save-data.campaign-contents becomes loadable
  • a failed or partial attempt never replaces the last successful commit
  • the commit and load records distinguish the retained last commit from the rejected attempts
Diagnostics
  • story-campaign-commit-log
  • story-campaign-load-result
  • story-campaign-active-state-source

commit-loadable-only-after-complete-success · settings-file

settings-file replaces its last commit only after complete success

scenarioper copy-kinds row

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-file commit and later complete-success, failed, and partial write attempts under settings.timed-write

When
  • each attempt is followed by a return that requests settings-file
Then
  • only the attempt reported successful for the complete set at settings.session-hint-contents becomes loadable
  • a failed or partial attempt never replaces the last successful commit
  • the commit and load records distinguish the retained last commit from the rejected attempts
Diagnostics
  • story-campaign-commit-log
  • story-campaign-load-result
  • story-campaign-active-state-source

normal-quit-final-commit-follows-write-rule · campaign-slots

campaign-slots makes a final quit commit only when its rule calls for one

scenarioper copy-kinds row

Applies to the campaign-slots row.

On ordinary quit, campaign-slots makes a final commit only when save-data.campaign-checkpoints calls for one. Otherwise the prior successful commit remains last. The test names the address and restates nothing from it.

Test steps and diagnostics
Given

each ordinary-quit case the adoption can construct for changed campaign-slots state under save-data.campaign-checkpoints

When
  • each ordinary quit completes
Then
  • where save-data.campaign-checkpoints calls for a final commit, exactly one starts
  • where the rule does not call for one, no final commit starts and the previous last successful commit remains; an unavailable branch asserts nothing
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 calls for one

scenarioper copy-kinds row

Applies to the settings-file row.

On ordinary quit, settings-file makes a final commit only when settings.timed-write calls for one. Otherwise the prior successful commit remains last. The test names the address and restates nothing from it.

Test steps and diagnostics
Given

each ordinary-quit case the adoption can construct for changed settings-file state under settings.timed-write

When
  • each ordinary quit completes
Then
  • where settings.timed-write calls for a final commit, exactly one starts
  • where the rule does not call for one, no final commit starts and the previous last successful commit remains; an unavailable branch asserts nothing
Diagnostics
  • story-campaign-commit-log

normal-quit-returns-last-commit · campaign-progress

campaign-progress returns its last commit after a normal quit

scenarioper state-families row

Applies to the campaign-progress row.

After a normal quit, campaign-progress returns from the latest successful commit of the copy kind named by saved-in; its uncommitted change does not return. If the row has no saved-in, the restoration clause asserts nothing.

Test steps and diagnostics
Given

a committed campaign-progress value followed by a distinguishable uncommitted change

When
  • the game quits normally and returns through the same context
Then
  • where this family names a copy kind at saved-in, the latest successful commit of that kind restores campaign-progress and the uncommitted change does not return
  • where this family has no saved-in, the restoration clause asserts nothing
Diagnostics
  • story-campaign-commit-log
  • story-campaign-load-result
  • story-campaign-active-state-source

normal-quit-returns-last-commit · dungeon-attempt

dungeon-attempt returns its last commit after a normal quit

scenarioper state-families row

Applies to the dungeon-attempt row.

After a normal quit, dungeon-attempt returns from the latest successful commit of the copy kind named by saved-in; its uncommitted change does not return. If the row has no saved-in, the restoration clause asserts nothing.

Test steps and diagnostics
Given

a committed dungeon-attempt value followed by a distinguishable uncommitted change

When
  • the game quits normally and returns through the same context
Then
  • where this family names a copy kind at saved-in, the latest successful commit of that kind restores dungeon-attempt and the uncommitted change does not return
  • where this family has no saved-in, the restoration clause asserts nothing
Diagnostics
  • story-campaign-commit-log
  • story-campaign-load-result
  • story-campaign-active-state-source

normal-quit-returns-last-commit · session-map-hints

session-map-hints returns its last commit after a normal quit

scenarioper state-families row

Applies to the session-map-hints row.

After a normal quit, session-map-hints returns from the latest successful commit of the copy kind named by saved-in; its uncommitted change does not return. If the row has no saved-in, the restoration clause asserts nothing.

Test steps and diagnostics
Given

a committed session-map-hints value followed by a distinguishable uncommitted change

When
  • the game quits normally and returns through the same context
Then
  • where this family names a copy kind at saved-in, the latest successful commit of that kind restores session-map-hints and the uncommitted change does not return
  • where this family has no saved-in, the restoration clause asserts nothing
Diagnostics
  • story-campaign-commit-log
  • story-campaign-load-result
  • story-campaign-active-state-source

normal-quit-returns-last-commit · unlocked-cosmetics

unlocked-cosmetics returns its last commit after a normal quit

scenarioper state-families row

Applies to the unlocked-cosmetics row.

After a normal quit, unlocked-cosmetics returns from the latest successful commit of the copy kind named by saved-in; its uncommitted change does not return. If the row has no saved-in, the restoration clause asserts nothing.

Test steps and diagnostics
Given

a committed unlocked-cosmetics value followed by a distinguishable uncommitted change

When
  • the game quits normally and returns through the same context
Then
  • where this family names a copy kind at saved-in, the latest successful commit of that kind restores unlocked-cosmetics and the uncommitted change does not return
  • where this family has no saved-in, the restoration clause asserts nothing
Diagnostics
  • story-campaign-commit-log
  • story-campaign-load-result
  • story-campaign-active-state-source

normal-quit-does-not-return

Row.id does not return after a normal quit

scenarioper state-families row

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

scenarioper state-families row

Applies to the campaign-progress row.

After a crash, no unfinished commit succeeds. campaign-progress returns from the last successful commit of the copy kind named by saved-in, without later changes. If the row has no saved-in, the restoration clause asserts nothing.

Test steps and diagnostics
Given

a committed campaign-progress value, 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 copy kind at saved-in, the last commit completed before the crash restores campaign-progress and the later change does not return
  • where this family has no saved-in, the restoration clause asserts nothing
Diagnostics
  • story-campaign-commit-log
  • story-campaign-load-result
  • story-campaign-active-state-source

crash-returns-last-commit · dungeon-attempt

dungeon-attempt returns its last completed commit after a crash

scenarioper state-families row

Applies to the dungeon-attempt row.

After a crash, no unfinished commit succeeds. dungeon-attempt returns from the last successful commit of the copy kind named by saved-in, without later changes. If the row has no saved-in, the restoration clause asserts nothing.

Test steps and diagnostics
Given

a committed dungeon-attempt value, 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 copy kind at saved-in, the last commit completed before the crash restores dungeon-attempt and the later change does not return
  • where this family has no saved-in, the restoration clause asserts nothing
Diagnostics
  • story-campaign-commit-log
  • story-campaign-load-result
  • story-campaign-active-state-source

crash-returns-last-commit · session-map-hints

session-map-hints returns its last completed commit after a crash

scenarioper state-families row

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 copy kind named by saved-in, without later changes. If the row has no saved-in, the restoration clause asserts nothing.

Test steps and diagnostics
Given

a committed session-map-hints value, 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 copy kind at saved-in, the last commit completed before the crash restores session-map-hints and the later change does not return
  • where this family has no saved-in, the restoration clause asserts nothing
Diagnostics
  • story-campaign-commit-log
  • story-campaign-load-result
  • story-campaign-active-state-source

crash-returns-last-commit · unlocked-cosmetics

unlocked-cosmetics returns its last completed commit after a crash

scenarioper state-families row

Applies to the unlocked-cosmetics row.

After a crash, no unfinished commit succeeds. unlocked-cosmetics returns from the last successful commit of the copy kind named by saved-in, without later changes. If the row has no saved-in, the restoration clause asserts nothing.

Test steps and diagnostics
Given

a committed unlocked-cosmetics value, 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 copy kind at saved-in, the last commit completed before the crash restores unlocked-cosmetics and the later change does not return
  • where this family has no saved-in, the restoration clause asserts nothing
Diagnostics
  • story-campaign-commit-log
  • story-campaign-load-result
  • story-campaign-active-state-source

crash-does-not-return

Row.id does not return after a crash

scenarioper state-families row

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 exposes its possible crash loss window

scenarioper copy-kinds row

Applies to the campaign-slots row.

campaign-slots has an explicit possible crash loss window under save-data.campaign-checkpoints. The interrupted commit fails, and the report identifies the last successful commit and lost interval. The test names the address and restates nothing from it.

Test steps and diagnostics
Given

a successful campaign-slots commit, later changes inside the possible loss window at save-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-log
  • story-campaign-settlement-report

timer-and-checkpoint-have-crash-loss-window · settings-file

settings-file exposes its possible crash loss window

scenarioper copy-kinds row

Applies to the settings-file row.

settings-file has an explicit possible crash loss window under settings.timed-write. The interrupted commit fails, and the report identifies the last successful commit and lost interval. The test names the address and restates nothing from it.

Test steps and diagnostics
Given

a successful settings-file commit, later changes inside the possible loss window at settings.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-log
  • story-campaign-settlement-report

layout-selects-requested-copy · campaign-slots

campaign-slots selects its requested copy before activation

scenarioper copy-kinds row

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; otherwise that clause asserts nothing. The test names the address and restates nothing from it.

Test steps and diagnostics
Given

the player requests campaign-slots through the selection route at save-data.campaign-slot-layout and, where the adoption retains one, an earlier commit is also available

When
  • selection completes before any state activation
Then
  • campaign-slots selects 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 clause asserts nothing
Diagnostics
  • story-campaign-candidate-set
  • story-campaign-active-state-source

layout-selects-requested-copy · settings-file

settings-file selects its requested copy before activation

scenarioper copy-kinds row

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; otherwise that clause asserts nothing. The test names the address and restates nothing from it.

Test steps and diagnostics
Given

the player requests settings-file through the selection route at settings.current-file and, where the adoption retains one, an earlier commit is also available

When
  • selection completes before any state activation
Then
  • settings-file selects 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 clause asserts nothing
Diagnostics
  • story-campaign-candidate-set
  • story-campaign-active-state-source

residence-controls-authoritative-candidates · campaign-slots

campaign-slots obtains candidates from its declared residence

scenarioper copy-kinds row

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-slots as governed at save-data.campaign-slot-layout

When
  • the game builds the candidate set before conflict handling
Then
  • campaign-slots is available only on that device unless a transfer cited outside this adoption supplies it
Diagnostics
  • story-campaign-candidate-set
  • story-campaign-authority-trace

residence-controls-authoritative-candidates · settings-file

settings-file obtains candidates from its declared residence

scenarioper copy-kinds row

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-file as governed at settings.current-file

When
  • the game builds the candidate set before conflict handling
Then
  • settings-file is available only on that device unless a transfer cited outside this adoption supplies it
Diagnostics
  • story-campaign-candidate-set
  • story-campaign-authority-trace

candidate-identity-precedes-settlement · campaign-slots

campaign-slots reads candidate identity before settlement

scenarioper copy-kinds row

Applies to the campaign-slots row.

campaign-slots reads candidate identity first. An unreadable identity cannot enter conflict handling; no survivor goes directly to the effective damage response; a readable header with an unreadable body reaches damage only after conflict and build comparison.

Test steps and diagnostics
Given

campaign-slots candidates 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 survives, 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-set
  • story-campaign-settlement-trace
  • story-campaign-damage-report

candidate-identity-precedes-settlement · settings-file

settings-file reads candidate identity before settlement

scenarioper copy-kinds row

Applies to the settings-file row.

settings-file reads candidate identity first. An unreadable identity cannot enter conflict handling; no survivor goes directly to the effective damage response; a readable header with an unreadable body reaches damage only after conflict and build comparison.

Test steps and diagnostics
Given

settings-file candidates 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 survives, 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-set
  • story-campaign-settlement-trace
  • story-campaign-damage-report

missing-conflict-row-runs-no-conflict-response · campaign-slots

campaign-slots has no conflict response without a matching row

scenarioper copy-kinds 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 asserts 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 asserts nothing
Diagnostics
  • story-campaign-candidate-set
  • story-campaign-settlement-trace

missing-conflict-row-runs-no-conflict-response · settings-file

settings-file has no conflict response without a matching row

scenarioper copy-kinds 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 asserts 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 asserts nothing
Diagnostics
  • story-campaign-candidate-set
  • story-campaign-settlement-trace

one-kind-conflict-settles-only-named-kind

Row.id settles only Row.copy kind

scenarioper copy-conflicts row

Does not apply to story-campaign: copy-conflicts has no rows.

all-synced-conflict-settles-whole-set

Row.id settles every synced kind together

scenarioper copy-conflicts row

Does not apply to story-campaign: copy-conflicts has no rows.

conflict-select-by-rule

Row.id applies select-by-rule

scenarioper copy-conflicts row

Does not apply to story-campaign: copy-conflicts has no rows.

conflict-player-chooses-copy

Row.id applies player-chooses-copy

scenarioper copy-conflicts row

Does not apply to story-campaign: copy-conflicts has no rows.

conflict-refuse-until-resolved

Row.id applies refuse-until-resolved

scenarioper copy-conflicts row

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

scenarioper copy-kinds row

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 older-build refuse-load default

scenarioper copy-kinds row

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

scenarioper copy-kinds row

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 older-build convert-and-load default

scenarioper copy-kinds row

Applies to the settings-file row.

settings-file uses the older-build default and converts to one coherent current-build copy before successful activation; a failed conversion refuses and preserves the source unless its cited rule records otherwise. The observable consequence is fixed here; the test restates nothing from the adoption's cited rule.

Test steps and diagnostics
Given

a selected readable copy of kind settings-file written 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 disposition
  • the conversion record distinguishes success, refusal, and source-copy disposition
Diagnostics
  • story-campaign-writer-and-reader-builds
  • story-campaign-conversion-result
  • story-campaign-load-result
  • story-campaign-active-state-source

older-build-override-warn-and-load · campaign-slots

campaign-slots applies its older-build warn-and-load override

scenarioper copy-kinds row

Applies to the campaign-slots row.

campaign-slots overrides the older-build default and warns before the attempt; cancellation activates nothing, and acceptance is recorded separately from success. The observable consequence is fixed here; the test restates nothing from the adoption's cited rule.

Test steps and diagnostics
Given

a selected readable copy of kind campaign-slots written 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
  • cancelling 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-builds
  • story-campaign-warning-choice
  • story-campaign-load-result
  • story-campaign-active-state-source

older-build-default-warn-and-load

Row.id applies the older-build warn-and-load default

scenarioper copy-kinds row

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

scenarioper copy-kinds row

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 newer-build refuse-load default

scenarioper copy-kinds row

Applies to the campaign-slots row.

campaign-slots uses the newer-build default and refuses before any mismatched-build state activates. The observable consequence is fixed here; the test restates nothing from the adoption's cited rule.

Test steps and diagnostics
Given

a selected readable copy of kind campaign-slots written 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-builds
  • story-campaign-load-result
  • story-campaign-active-state-source

newer-build-override-warn-and-load · settings-file

settings-file applies its newer-build warn-and-load override

scenarioper copy-kinds row

Applies to the settings-file row.

settings-file overrides the newer-build default and warns before the attempt; cancellation activates nothing, and acceptance is recorded separately from success. The observable consequence is fixed here; the test restates nothing from the adoption's cited rule.

Test steps and diagnostics
Given

a selected readable copy of kind settings-file written 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
  • cancelling 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-builds
  • story-campaign-warning-choice
  • story-campaign-load-result
  • story-campaign-active-state-source

newer-build-default-warn-and-load

Row.id applies the newer-build warn-and-load default

scenarioper copy-kinds row

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

scenarioper copy-kinds row

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 unreadable-copy refuse-load default

scenarioper copy-kinds row

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

scenarioper copy-kinds row

Applies to the settings-file row.

settings-file overrides the unreadable-copy default and activates the adoption's fresh state without silently deleting or overwriting the unreadable copy. The observable consequence is fixed here; the test restates nothing from the adoption's cited rule.

Test steps and diagnostics
Given

a selected copy of kind settings-file whose gameplay body is unreadable after conflict and build settlement

When
  • the player requests the unreadable copy
Then
  • the fresh state fixed 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 fresh start from deletion
Diagnostics
  • story-campaign-damage-report
  • story-campaign-load-result
  • story-campaign-active-state-source
  • story-campaign-removal-log

unreadable-copy-default-start-fresh

Row.id applies the unreadable-copy start-fresh default

scenarioper copy-kinds row

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

scenarioper copy-kinds row

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 unreadable-copy restore-earlier-copy default

scenarioper copy-kinds row

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

scenarioper copy-kinds row

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 unreadable-copy repair-readable-state default

scenarioper copy-kinds row

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

scenarioper copy-kinds row

Applies to the campaign-slots row.

campaign-slots overrides the unreadable-copy default and waits for an explicit choice; cancellation activates nothing, and acceptance is recorded separately from success. The observable consequence is fixed here; the test restates nothing from the adoption's cited rule.

Test steps and diagnostics
Given

a selected copy of kind campaign-slots whose 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-report
  • story-campaign-load-result
  • story-campaign-active-state-source
  • story-campaign-removal-log

unreadable-copy-default-warn-and-try

Row.id applies the unreadable-copy warn-and-try default

scenarioper copy-kinds row

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

scenarioper backup-copies row

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 copy kind named by campaign-slots, including usable and unusable candidates

When
  • the game applies save-data.previous-checkpoint-policy and the player directly uses menus.restore-previous-checkpoint
Then
  • earlier commits are retained and ordered only as save-data.previous-checkpoint-policy and the storage rule of the copy 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-checkpoint remains available independently of any damage response
Diagnostics
  • story-campaign-backup-inventory
  • story-campaign-load-result
  • story-campaign-damage-report

readable-compatible-copy-activates-together · campaign-slots

campaign-slots loads a readable compatible conflict-free copy

scenarioper copy-kinds row

Applies to the campaign-slots row.

A readable, same-build, conflict-free copy of 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 kind campaign-slots from the running build with no conflict, carrying distinguishable values for every family at save-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-result
  • story-campaign-active-state-source
  • story-campaign-activation-trace

readable-compatible-copy-activates-together · settings-file

settings-file loads a readable compatible conflict-free copy

scenarioper copy-kinds row

Applies to the settings-file row.

A readable, same-build, conflict-free copy of 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 kind settings-file from the running build with no conflict, carrying distinguishable values for every family at settings.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-result
  • story-campaign-active-state-source
  • story-campaign-activation-trace

load-preserves-families-from-other-kinds · campaign-slots

loading campaign-slots preserves state from other copy kinds

scenarioper copy-kinds row

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 kind. If no other kind exists, that clause asserts nothing.

Test steps and diagnostics
Given

active distinguishable state from campaign-slots and, where the adoption declares one, another copy kind

When
  • a readable compatible conflict-free copy of kind campaign-slots loads successfully
Then
  • families carried by campaign-slots activate together from its selected commit
  • families saved in another copy kind keep that kind's already active value
  • where no other copy kind exists, the second clause asserts nothing
Diagnostics
  • story-campaign-activation-trace
  • story-campaign-active-state-source

load-preserves-families-from-other-kinds · settings-file

loading settings-file preserves state from other copy kinds

scenarioper copy-kinds row

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 kind. If no other kind exists, that clause asserts nothing.

Test steps and diagnostics
Given

active distinguishable state from settings-file and, where the adoption declares one, another copy kind

When
  • a readable compatible conflict-free copy of kind settings-file loads successfully
Then
  • families carried by settings-file activate together from its selected commit
  • families saved in another copy kind keep that kind's already active value
  • where no other copy kind exists, the second clause asserts nothing
Diagnostics
  • story-campaign-activation-trace
  • story-campaign-active-state-source

family-without-saved-in-is-never-restored · campaign-progress

campaign-progress is never restored when it names no save kind

scenarioper state-families row

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 copy kind restores its earlier value; no other return path does.

Test steps and diagnostics
Given

a distinguishable earlier campaign-progress value and every return path the adoption can construct

When
  • the return paths complete
Then
  • where campaign-progress has no saved-in, none of the return paths restores its earlier value
  • where campaign-progress names saved-in, its earlier value is restored only by an accepted load of that copy kind and by no other return path
Diagnostics
  • story-campaign-state-boundary-trace
  • story-campaign-active-state-source

family-without-saved-in-is-never-restored · dungeon-attempt

dungeon-attempt is never restored when it names no save kind

scenarioper state-families row

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 copy kind restores its earlier value; no other return path does.

Test steps and diagnostics
Given

a distinguishable earlier dungeon-attempt value and every return path the adoption can construct

When
  • the return paths complete
Then
  • where dungeon-attempt has no saved-in, none of the return paths restores its earlier value
  • where dungeon-attempt names saved-in, its earlier value is restored only by an accepted load of that copy kind and by no other return path
Diagnostics
  • story-campaign-state-boundary-trace
  • story-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

scenarioper state-families row

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 copy kind restores its earlier value; no other return path does.

Test steps and diagnostics
Given

a distinguishable earlier session-map-hints value and every return path the adoption can construct

When
  • the return paths complete
Then
  • where session-map-hints has no saved-in, none of the return paths restores its earlier value
  • where session-map-hints names saved-in, its earlier value is restored only by an accepted load of that copy kind and by no other return path
Diagnostics
  • story-campaign-state-boundary-trace
  • story-campaign-active-state-source

family-without-saved-in-is-never-restored · unlocked-cosmetics

unlocked-cosmetics is never restored when it names no save kind

scenarioper state-families row

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 copy kind restores its earlier value; no other return path does.

Test steps and diagnostics
Given

a distinguishable earlier unlocked-cosmetics value and every return path the adoption can construct

When
  • the return paths complete
Then
  • where unlocked-cosmetics has no saved-in, none of the return paths restores its earlier value
  • where unlocked-cosmetics names saved-in, its earlier value is restored only by an accepted load of that copy kind and by no other return path
Diagnostics
  • story-campaign-state-boundary-trace
  • story-campaign-active-state-source

earlier-point-follows-after-load · dungeon-retry

dungeon-retry follows its after-load result

scenarioper earlier-point-returns row

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 copy kind campaign-slots and an earlier point at dungeon.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-result
  • story-campaign-return-point
  • story-campaign-run-identity
  • story-campaign-save-availability-after-load

missing-earlier-point-row-returns-nowhere · campaign-slots

campaign-slots returns nowhere without an earlier-point row

scenarioper copy-kinds row

Applies to the campaign-slots row.

A successful campaign-slots load returns continuous play nowhere under this contract when no earlier-point row names it. When one does, this test asserts nothing.

Test steps and diagnostics
Given

a successful campaign-slots load

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 asserts nothing
Diagnostics
  • story-campaign-load-result
  • story-campaign-return-point

missing-earlier-point-row-returns-nowhere · settings-file

settings-file returns nowhere without an earlier-point row

scenarioper copy-kinds row

Applies to the settings-file row.

A successful settings-file load returns continuous play nowhere under this contract when no earlier-point row names it. When one does, this test asserts nothing.

Test steps and diagnostics
Given

a successful settings-file load

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 asserts nothing
Diagnostics
  • story-campaign-load-result
  • story-campaign-return-point

earlier-point-mode-gate-controls-existence

Row.id exists only in its named modes

scenarioper earlier-point-returns row

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

scenarioper state-families row

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-progress value 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-trace
  • story-campaign-active-state-source

death-follows-family-result · dungeon-attempt

dungeon-attempt follows its death result

scenarioper state-families row

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-attempt value 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-trace
  • story-campaign-active-state-source

death-follows-family-result · session-map-hints

session-map-hints follows its death result

scenarioper state-families row

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-hints value 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-trace
  • story-campaign-active-state-source

death-follows-family-result · unlocked-cosmetics

unlocked-cosmetics follows its death result

scenarioper state-families row

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-cosmetics value 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-trace
  • story-campaign-active-state-source

death-at-run-end-clears-run-family · dungeon-attempt

dungeon-attempt clears when death is its run end

scenarioper state-families row

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-attempt value before a death, where the run ending at dungeon.victory-death-or-abandonment is death

When
  • death occurs without another save action
Then
  • if the cited end at dungeon.victory-death-or-abandonment is death, the earlier run-lifetime value clears before later play even without a save action; if the cited end is not death, this test asserts nothing
Diagnostics
  • story-campaign-state-boundary-trace
  • story-campaign-run-identity

death-at-until-event-clears-family

Row.id clears when death is its cited event

scenarioper state-families row

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

scenarioper state-families row

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-trace
  • story-campaign-settlement-report

slot-deletion-follows-family-result · dungeon-attempt

dungeon-attempt follows its slot-deletion result

scenarioper state-families row

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-trace
  • story-campaign-settlement-report

slot-deletion-follows-family-result · session-map-hints

session-map-hints follows its slot-deletion result

scenarioper state-families row

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-trace
  • story-campaign-settlement-report

slot-deletion-follows-family-result · unlocked-cosmetics

unlocked-cosmetics follows its slot-deletion result

scenarioper state-families row

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-trace
  • story-campaign-settlement-report

local-profile-deletion-follows-family-result · campaign-progress

campaign-progress follows its local-profile-deletion result

scenarioper state-families row

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; otherwise that clause asserts nothing. 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-trace
  • story-campaign-settlement-report
  • story-campaign-removal-log

local-profile-deletion-follows-family-result · dungeon-attempt

dungeon-attempt follows its local-profile-deletion result

scenarioper state-families row

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; otherwise that clause asserts nothing. 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-trace
  • story-campaign-settlement-report
  • story-campaign-removal-log

local-profile-deletion-follows-family-result · session-map-hints

session-map-hints follows its local-profile-deletion result

scenarioper state-families row

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; otherwise that clause asserts nothing. 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-trace
  • story-campaign-settlement-report
  • story-campaign-removal-log

local-profile-deletion-follows-family-result · unlocked-cosmetics

unlocked-cosmetics follows its local-profile-deletion result

scenarioper state-families row

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; otherwise that clause asserts nothing. 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-trace
  • story-campaign-settlement-report
  • story-campaign-removal-log

device-lifetime-ends-at-device-data-deletion

Row.id ends when device data is deleted

scenarioper state-families row

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

scenarioper state-families row

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

scenarioper state-families row

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

scenarioper state-families row

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, that clause asserts nothing. The test names the address and restates nothing from it.

Test steps and diagnostics
Given

a distinguishable campaign-progress value 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 clause asserts nothing
Diagnostics
  • story-campaign-state-boundary-trace
  • story-campaign-active-state-source
  • story-campaign-removal-log

another-device-does-not-return-family · dungeon-attempt

dungeon-attempt does not return on another device

scenarioper state-families row

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, that clause asserts nothing. The test names the address and restates nothing from it.

Test steps and diagnostics
Given

a distinguishable dungeon-attempt value 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 clause asserts nothing
Diagnostics
  • story-campaign-state-boundary-trace
  • story-campaign-active-state-source
  • story-campaign-removal-log

another-device-does-not-return-family · session-map-hints

session-map-hints does not return on another device

scenarioper state-families row

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, that clause asserts nothing. The test names the address and restates nothing from it.

Test steps and diagnostics
Given

a distinguishable session-map-hints value 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 clause asserts nothing
Diagnostics
  • story-campaign-state-boundary-trace
  • story-campaign-active-state-source
  • story-campaign-removal-log

another-device-does-not-return-family · unlocked-cosmetics

unlocked-cosmetics does not return on another device

scenarioper state-families row

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, that clause asserts nothing. The test names the address and restates nothing from it.

Test steps and diagnostics
Given

a distinguishable unlocked-cosmetics value 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 clause asserts nothing
Diagnostics
  • story-campaign-state-boundary-trace
  • story-campaign-active-state-source
  • story-campaign-removal-log

removed-copy-disappears-at-selected-event

Row.id disappears at its removal event

scenarioper copy-kinds row

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

scenarioper copy-kinds row

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

scenarioonce

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, the test asserts nothing.

Test steps and diagnostics
Given

every event the story-campaign adoption 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 asserts nothing
Diagnostics
  • story-campaign-state-boundary-trace
  • story-campaign-active-state-source

settlement-report-is-complete

every settlement report records its complete result

scenarioonce

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. A clause for a case the adoption cannot construct asserts nothing.

Test steps and diagnostics
Given

each persistence settlement the story-campaign adoption can construct

When
  • the settlement completes or refuses
Then
  • the report identifies state-family id, copy-kind id or its absence, event, scope before and after, last successful commit when read, selected copy and build when loaded, override or default used, state result, and any lost interval
  • a conflict report also identifies both candidate commits and the selected response
  • a damage report identifies refusal, fresh 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, its corresponding clause asserts nothing
Diagnostics
  • story-campaign-settlement-report
  • story-campaign-conflict-report
  • story-campaign-damage-report

state-family-citation-states-its-duty · campaign-progress

declaration check: campaign-progress's state citation states its duty

scenarioper state-families row

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 that clause asserts nothing. Another meaning is a defect this test fails by construction. The test names the address and restates nothing from it.

Test steps and diagnostics
Given

the story-campaign adoption and the prose at campaign.progress-fields, not the running game

When
  • a reviewer checks the citation's meaning
Then
  • campaign.progress-fields names 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-fields also names the value the family starts from; where it never gives that answer for those cases, this clause asserts nothing
  • another meaning is a defect this test fails by construction
Diagnostics
  • story-campaign-declaration-record

state-family-citation-states-its-duty · dungeon-attempt

declaration check: dungeon-attempt's state citation states its duty

scenarioper state-families row

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 that clause asserts nothing. Another meaning is a defect this test fails by construction. The test names the address and restates nothing from it.

Test steps and diagnostics
Given

the story-campaign adoption and the prose at dungeon.attempt-fields, not the running game

When
  • a reviewer checks the citation's meaning
Then
  • dungeon.attempt-fields names 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-fields also names the value the family starts from; where it never gives that answer for those cases, this clause asserts nothing
  • another meaning is a defect this test fails by construction
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

scenarioper state-families row

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 that clause asserts nothing. Another meaning is a defect this test fails by construction. The test names the address and restates nothing from it.

Test steps and diagnostics
Given

the story-campaign adoption and the prose at map.session-hint-fields, not the running game

When
  • a reviewer checks the citation's meaning
Then
  • map.session-hint-fields names 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-fields also names the value the family starts from; where it never gives that answer for those cases, this clause asserts nothing
  • another meaning is a defect this test fails by construction
Diagnostics
  • story-campaign-declaration-record

state-family-citation-states-its-duty · unlocked-cosmetics

declaration check: unlocked-cosmetics's state citation states its duty

scenarioper state-families row

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 that clause asserts nothing. Another meaning is a defect this test fails by construction. The test names the address and restates nothing from it.

Test steps and diagnostics
Given

the story-campaign adoption and the prose at cosmetics.unlock-fields, not the running game

When
  • a reviewer checks the citation's meaning
Then
  • cosmetics.unlock-fields names 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-fields also names the value the family starts from; where it never gives that answer for those cases, this clause asserts nothing
  • another meaning is a defect this test fails by construction
Diagnostics
  • story-campaign-declaration-record

saved-in-joins-a-copy-kind · campaign-progress

declaration check: campaign-progress names an existing copy kind when saved

scenarioper state-families row

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 copy kind in this adoption; any other name is a defect this test fails by construction. With no saved-in, this check asserts 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-campaign adoption document, not the running game

When
  • a reviewer reads campaign-progress's saved-in field
Then
  • where saved-in is present, it names exactly one copy-kinds row in this adoption; another name is a defect this test fails by construction
  • where saved-in is absent, this join check asserts nothing
Diagnostics
  • story-campaign-declaration-record

saved-in-joins-a-copy-kind · dungeon-attempt

declaration check: dungeon-attempt names an existing copy kind when saved

scenarioper state-families row

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 copy kind in this adoption; any other name is a defect this test fails by construction. With no saved-in, this check asserts 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-campaign adoption document, not the running game

When
  • a reviewer reads dungeon-attempt's saved-in field
Then
  • where saved-in is present, it names exactly one copy-kinds row in this adoption; another name is a defect this test fails by construction
  • where saved-in is absent, this join check asserts nothing
Diagnostics
  • story-campaign-declaration-record

saved-in-joins-a-copy-kind · session-map-hints

declaration check: session-map-hints names an existing copy kind when saved

scenarioper state-families row

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 copy kind in this adoption; any other name is a defect this test fails by construction. With no saved-in, this check asserts 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-campaign adoption document, not the running game

When
  • a reviewer reads session-map-hints's saved-in field
Then
  • where saved-in is present, it names exactly one copy-kinds row in this adoption; another name is a defect this test fails by construction
  • where saved-in is absent, this join check asserts nothing
Diagnostics
  • story-campaign-declaration-record

saved-in-joins-a-copy-kind · unlocked-cosmetics

declaration check: unlocked-cosmetics names an existing copy kind when saved

scenarioper state-families row

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 copy kind in this adoption; any other name is a defect this test fails by construction. With no saved-in, this check asserts 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-campaign adoption document, not the running game

When
  • a reviewer reads unlocked-cosmetics's saved-in field
Then
  • where saved-in is present, it names exactly one copy-kinds row in this adoption; another name is a defect this test fails by construction
  • where saved-in is absent, this join check asserts nothing
Diagnostics
  • story-campaign-declaration-record

backup-copy-kind-joins · previous-campaign-checkpoint

declaration check: previous-campaign-checkpoint names an existing copy kind

scenarioper backup-copies row

Applies to the previous-campaign-checkpoint row.

This declaration check reads the adoption, not the running game. campaign-slots must name exactly one copy kind in this adoption. Any other name is a defect this test fails by construction.

Test steps and diagnostics
Given

the story-campaign adoption document, not the running game

When
  • a reviewer compares campaign-slots with copy-kinds ids
Then
  • campaign-slots names exactly one copy-kinds row in this adoption; another name is a defect this test fails by construction
Diagnostics
  • story-campaign-declaration-record

conflict-copy-kind-joins

declaration check: Row.id names an existing shared copy kind

scenarioper copy-conflicts row

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 copy kind

scenarioper earlier-point-returns row

Applies to the dungeon-retry row.

This declaration check reads the adoption, not the running game. campaign-slots must name exactly one copy kind in this adoption. Any other name is a defect this test fails by construction.

Test steps and diagnostics
Given

the story-campaign adoption document, not the running game

When
  • a reviewer compares campaign-slots with copy-kinds ids
Then
  • campaign-slots names exactly one copy-kinds row in this adoption; another name is a defect this test fails by construction
Diagnostics
  • story-campaign-declaration-record

account-family-names-account-ending

declaration check: Row.id names account deletion or reset as its end

scenarioper state-families row

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

scenarioper copy-kinds 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: Row.id's restore-earlier-copy default has a backup row

scenarioper copy-kinds 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 shared authority

scenarioper state-families row

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

scenarioonce

Applies to story-campaign.

This declaration check reads the adoption, not the running game. Each represented conflict scope has at most one row. A duplicate is a defect this test fails by construction. With no conflict rows, the check asserts nothing.

Test steps and diagnostics
Given

the story-campaign adoption 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; a duplicate scope is a defect this test fails by construction; with no conflict rows, this check asserts nothing
Diagnostics
  • story-campaign-declaration-record

one-earlier-point-row-per-copy-kind

declaration check: each copy kind has at most one earlier-point row

scenarioonce

Applies to story-campaign.

This declaration check reads the adoption, not the running game. Each copy kind has at most one earlier-point row. A duplicate is a defect this test fails by construction. With no such rows, the check asserts nothing.

Test steps and diagnostics
Given

the story-campaign adoption document, not the running game

When
  • a reviewer groups earlier-point-returns rows by copy-kind
Then
  • no copy kind is named by more than one earlier-point-returns row; a duplicate is a defect this test fails by construction; with no such rows, this check asserts nothing
Diagnostics
  • story-campaign-declaration-record

copy-citations-state-their-duties · campaign-slots

declaration check: campaign-slots's required citations state their duties

scenarioper copy-kinds row

Applies to the campaign-slots row.

This declaration check reads the adoption and cited prose, not the running game. save-data.campaign-contents, save-data.campaign-checkpoints, and save-data.campaign-slot-layout must state their family-set, write-success and loss-window, and applicable residence, authority, identity, and selection duties. A missing duty is a defect this test fails by construction. The test names the addresses and restates nothing from them.

Test steps and diagnostics
Given

the story-campaign adoption and the prose at save-data.campaign-contents, save-data.campaign-checkpoints, and save-data.campaign-slot-layout, not the running game

When
  • a reviewer checks each citation's meaning
Then
  • save-data.campaign-contents names the complete family set written and restored together
  • save-data.campaign-checkpoints names the trigger, success condition, final-write behavior, and quit or crash loss window
  • save-data.campaign-slot-layout names residence, applicable sync or service acceptance and authority, layout, identities, and player selection
  • a citation missing its duty is a defect this test fails by construction
Diagnostics
  • story-campaign-declaration-record

copy-citations-state-their-duties · settings-file

declaration check: settings-file's required citations state their duties

scenarioper copy-kinds row

Applies to the settings-file row.

This declaration check reads the adoption and cited prose, not the running game. settings.session-hint-contents, settings.timed-write, and settings.current-file must state their family-set, write-success and loss-window, and applicable residence, authority, identity, and selection duties. A missing duty is a defect this test fails by construction. The test names the addresses and restates nothing from them.

Test steps and diagnostics
Given

the story-campaign adoption and the prose at settings.session-hint-contents, settings.timed-write, and settings.current-file, not the running game

When
  • a reviewer checks each citation's meaning
Then
  • settings.session-hint-contents names the complete family set written and restored together
  • settings.timed-write names the trigger, success condition, final-write behavior, and quit or crash loss window
  • settings.current-file names residence, applicable sync or service acceptance and authority, layout, identities, and player selection
  • a citation missing its duty is a defect this test fails by construction
Diagnostics
  • story-campaign-declaration-record

backup-citations-state-their-duties · previous-campaign-checkpoint

declaration check: previous-campaign-checkpoint's backup citations state their duties

scenarioper backup-copies row

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 retention, ordering, usable selection, and loss duties; menus.restore-previous-checkpoint must state the direct restore route. A missing duty is a defect this test fails by construction. The test names both addresses and restates nothing from them.

Test steps and diagnostics
Given

the story-campaign adoption and the prose at save-data.previous-checkpoint-policy and menus.restore-previous-checkpoint, not the running game

When
  • a reviewer checks each citation's meaning
Then
  • save-data.previous-checkpoint-policy names retention, ordering, usable selection, and the possible lost interval
  • menus.restore-previous-checkpoint names the supported direct restore route
  • a citation missing its duty is a defect this test fails by construction
Diagnostics
  • story-campaign-declaration-record

conflict-citation-states-its-duties

declaration check: Row.id's resolution citation states its duties

scenarioper copy-conflicts row

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

scenarioper earlier-point-returns row

Applies to the dungeon-retry row.

This declaration check reads the adoption and cited prose, not the running game. dungeon.retry must state the point, activation, run-identity, and reuse duties. A missing duty is a defect this test fails by construction. The test names the address and restates nothing from it.

Test steps and diagnostics
Given

the story-campaign adoption and the prose at dungeon.retry, not the running game

When
  • a reviewer checks the citation's meaning
Then
  • dungeon.retry names the earlier point, successful activation, run identity when applicable, and whether another load remains possible
  • a citation missing a duty is a defect this test fails by construction
Diagnostics
  • story-campaign-declaration-record

copy-removal-citation-states-its-duty

declaration check: Row.id's removal citation states its duty

scenarioper copy-kinds row

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

scenarioper copy-kinds row

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

scenarioper earlier-point-returns row

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

scenarioper earlier-point-returns row

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

generalonce

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. A case the adoption cannot construct asserts 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, that clause asserts nothing

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-trace
  • story-campaign-commit-log
  • story-campaign-candidate-set
  • story-campaign-settlement-trace
  • story-campaign-load-result
  • story-campaign-active-state-source
  • story-campaign-settlement-report
  • story-campaign-first-ordering-violation
JSONthe adoption as one file

The adoption file as supplied. Answers you try on this page are not written into it; to change an adoption, open it in the authoring tool.

{
  "contract": "state-persistence-scope",
  "version": 1,
  "summary": "Covers what survives the end of a run or session, a quit, a crash, death, deleting saved data, and moving to another device. It also covers local saves, manual slots, cloud copies, server profiles, backups, conflicts, and version changes. Closed-game time, replay guarantees, file formats, and the shape of saved numbers stay elsewhere.",
  "questions": {
    "unreadable-copy-default": {
      "asks": "What normally happens when saved state cannot be read?",
      "rationale": "Lost progress is the most visible failure a save system has. Choose the result players should expect first.",
      "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 normal fresh state. A damaged puzzle slot opens with its first board instead of the old board.",
          "semantics": "Fresh 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 it through the supported route, including a manual file restore. A factory returns after choosing the backup before its 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 fresh 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": "What normally happens when an older game build wrote the copy?",
      "rationale": "Updates can strand a player's progress 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": "What normally 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 family of state that can change later play and give its observed persistence boundaries.",
        "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": "Name the state family in game words, such as campaign-progress, run-inventory, unlocks, or settings."
          },
          "state-declared-in": {
            "type": "citation",
            "required": true,
            "description": "The fixed game prose naming every field in this family and the points where it can affect later play. For a family that does not return after a quit, a crash, or on another device, it also names the value the family starts from."
          },
          "lifetime": {
            "type": "string",
            "required": true,
            "options": [
              "run",
              "session",
              "slot",
              "profile",
              "device",
              "account",
              "until-event",
              "not-kept"
            ],
            "description": "Run: the state belongs to one attempt; a roguelike's carried items go when that attempt ends. Session: the state belongs to one continuous visit; a co-op lobby choice goes when the party leaves that lobby. Slot: the state belongs to one player-selected save; campaign progress goes when that slot is deleted. Profile: the state belongs to one local player profile; unlocks go when that profile is deleted. Device: the state belongs to one installation; local settings go when that device data is erased. Account: the state belongs to one account; online settings go when that account is deleted or reset. Until event: the state lasts to one named game event; dropped currency goes at the next death. Not kept: the state does not cross 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": "The player-observable start of this attempt or continuous visit. 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": "The player-observable ending of this lifetime. 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": "The named game event that destroys the earlier value. 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. Absence means no copy restores it."
          },
          "after-normal-quit": {
            "type": "string",
            "required": true,
            "options": [
              "returns-last-commit",
              "does-not-return"
            ],
            "description": "Whether the last saved point returns after an ordinary quit. 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 the last saved point returns after a crash. A timer-written world may lose changes since its last write while still returning that write."
          },
          "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 deleting the related player-selected save keeps this value. That slot is named by the storage rule for several player-selected saves; a save presented as one current copy answers that no related slot exists."
          },
          "after-local-profile-delete": {
            "type": "string",
            "required": true,
            "options": [
              "keeps-value",
              "clears-value",
              "no-profile"
            ],
            "description": "Whether deleting the related local player profile keeps this value. That profile is named by the restoring copy's storage rule; a family with no such profile answers that no related profile exists."
          },
          "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 independently written and loaded save kind, 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": "Name this save kind in game words, such as campaign-slots, unlock-profile, or account-settings."
          },
          "contains-declared-in": {
            "type": "citation",
            "required": true,
            "description": "The fixed list of state-family ids written and restored together by this save kind."
          },
          "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": "The rule naming exactly when a save begins, what makes it successful, and which later changes a quit or crash can lose."
          },
          "residence": {
            "type": "string",
            "required": true,
            "options": [
              "local-device",
              "cloud-synced",
              "server-authoritative"
            ],
            "description": "Where the copy that can restore play is authoritative: this device, synced device copies, or a game service. Copies shared through sync can conflict; a profile controlled by the service reads its accepted state."
          },
          "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": "The rule naming copy residence, sync or service authority, and the player-visible selection route. For several player-selected saves it also names each stable slot identity; one current copy names 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": "Use the game's ordinary answer for a save that cannot be read, or choose a different result for this save kind."
          },
          "older-build-copy": {
            "type": "string",
            "required": true,
            "options": [
              "use-game-default",
              "refuse-load",
              "convert-and-load",
              "warn-and-load"
            ],
            "description": "Use the game's ordinary answer for a save written by an older build, or choose a different result for this save kind."
          },
          "newer-build-copy": {
            "type": "string",
            "required": true,
            "options": [
              "use-game-default",
              "refuse-load",
              "warn-and-load"
            ],
            "description": "Use the game's ordinary answer for a save written by a newer build, or choose a different result for this save kind."
          },
          "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": "The rule naming every player mode where this save kind exists and confirming that it is absent elsewhere."
          },
          "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 vanish when the character dies. For several player-selected saves this names removal of the deleted slot's copy, not of 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": "The rule naming the exact event that 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": "Name the earlier-copy family, 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": "The rule naming when an earlier copy is retained, how many remain, and what later progress recovery loses."
          },
          "restore-rule-declared-in": {
            "type": "citation",
            "required": true,
            "description": "The supported route for opening an earlier copy. It names whether recovery is automatic, in the game, or an external file step."
          }
        }
      },
      "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 competing copies, so no conflict response runs.",
        "record": {
          "id": {
            "type": "string",
            "required": true,
            "pattern": "kebab-case",
            "unique": true,
            "description": "Name the conflict in game 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": "The rule detecting disagreement, comparing copy identity, selecting or preserving copies, and naming 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 reload-to-retry is unavailable.",
        "record": {
          "id": {
            "type": "string",
            "required": true,
            "pattern": "kebab-case",
            "unique": true,
            "description": "Name the earlier return point in game 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": "The rule naming the earlier point, successful activation, 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": "The rule naming every player mode where this earlier return point exists and confirming that it is absent elsewhere."
          }
        }
      }
    }
  },
  "rules": {},
  "origin": "https://opengdd.org/contracts/state-persistence-scope-1",
  "mechanism": [
    "This is the one normative settlement authority for state-family identity, scope starts and endings, commits, loads, damage, build mismatch, copy conflicts, deletion, and device change. Questions and rows supply defaults, overrides, and cited game rules; they do not define another sequence.",
    "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 survives 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 **commit** is one coherent accepted write of a copy 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 maximum scope of that earlier value. An `until-event` value ends at its cited game event; a shorter boundary result may clear any value sooner 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 copy-kind commit 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 an ordinary quit, perform a final commit only when the copy kind's cited write rule calls for 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. A timer or checkpoint therefore has an explicit possible loss window. 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` 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 kind obtains every accepted device and cloud candidate before conflict handling. A server-authoritative 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 survives, run the requested kind's damage response 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 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 copy-kind override when present; otherwise apply the matching older or newer default. 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 copy-kind override or `unreadable-copy-default`. Refusal activates nothing. Fresh start activates the cited fresh 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 copy 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 a `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 fixed 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, copy-kind id or its absence, event, scope before and after, last successful commit when read, selected copy and build when loaded, override or default used, state result, and any lost interval. Conflict reports also identify both candidate commits and the selected response. Damage reports identify refusal, fresh state, earlier-copy recovery, repaired parts, or warning choice without claiming an unattempted load succeeded."
  ],
  "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": []
  },
  "pack": "sha256:86060e861a3d6b10e0019612467e03749328dba98ab1a148bbc54c09e2492cf7"
}