44 KiB
Aetherbound Guild: Save And Failure Contract
Authority: versioned persistence, pending/applied operations, exactly-once commit, retry, duplicate protection, deterministic battle resume, Recruit death, dismissal, run failure, backup, corruption, migration, rollback, cloud conflict, clock behavior, bounded offline work, delete, and recovery UX.
Revision: recruit-line-design-r3 / 2026-08-11
GAME_PRODUCT_CONTRACT.md owns the player promise. SYSTEMS_AND_BATTLE.md owns battle state and casualty predicates. ECONOMY_AND_BALANCE.md owns normal prices, rewards, pacing, and difficulty multipliers. This document is final authority for operation and gameplay-failure application.
1. Persistence Invariants
- A confirmed operation applies exactly once or not at all. Cost, result, receipt, and final status are one committed state.
- A durable
pendingrow contains immutable intent and zero gameplay deltas. It can be safely finalized or rejected; it can never contain a spent cost. - Suspend, crash, power loss, retry, duplicate input, device change, or cloud transport cannot redraw a Market/reward, duplicate an item/Renown grant, reverse a valid casualty, or change a deterministic battle.
- The newest valid local commit remains fully playable offline. Platform and cloud copies never become gameplay authority silently.
- A Fallen Recruit never respawns in the same battle and never returns to the same run through Reserve substitution, reload, backup choice, rehearsal, migration, reward, promotion, difficulty, or clock manipulation.
- Death removes the Recruit but returns exactly the Recruit's optional
equipped_item_idto run inventory. Neither half of that result can commit alone; an empty slot returns nothing. - Offline gameplay advances by zero simulation ticks and grants zero Coin, XP, Renown, offers, healing, or progress.
- Migration is copy-first, deterministic, sequential, and reversible until a later verified boundary. It never rerolls generated Recruit fields.
- Corruption recovery identifies the exact verified boundary. If later irreversible run operations cannot be reconstructed, the run is sealed rather than resumed from a state that could reverse death or duplicate value.
- Save, conflict, recovery, overwrite, abandon, dismissal, and delete flows support touch, mouse, keyboard, controller, localization, and accessibility.
- A Run seed is reserved in a durable
run_creation_draftbefore any opening Recruit is generated. At most one draft orcurrent_runexists, and a draft can only become thecurrent_runthat carries that same seed.
2. Save Topology And Envelope
The product supports three independent Guild slots. Each slot holds one active commit, four rotating verified backups, one run-start checkpoint, one latest Market checkpoint, one optional pre-migration copy, and redundant closure receipt ledgers.
profile_save
profile_schema_version
settings_revision, language, accessibility, input mappings
Guild_slot_index[]
platform_service_outbox (non-authoritative, bounded)
Guild_slot
Guild_id, lineage_id, slot_id
save_schema_version, ruleset_version, content_revision
commit_id, parent_commit_id, commit_sequence
generation_root_seed, next_run_ordinal
Region/Contract progress, Renown, unlocks, Records, discoveries
run_creation_draft or null
current_run or null
operation_journal, compact_applied_ids
closure_receipt_head, run_creation_receipt_head
payload_sha256, envelope_crc32
run_creation_draft
draft_id, run_id, Contract, difficulty, status
seed_ordinal, run_seed, source_commit_id
ruleset_version, content_revision, opening_role_weight_snapshot
opening_offers[0..3] or empty, opening_offer_set_hash or null
selected_offer_ids[2] or empty, selection_revision
draft_hash, run_creation_receipt_head
current_run
run_id, Contract, difficulty, run_seed, status
Market/cleared-Round/boss state
Coin, Replacement Credit, capacity, offers, locks, refresh counters
living Recruits, Reserve, inventory, Artifacts
encounter or null, pending outcome/reward or null
hired/fallen/dismissed totals
IDs and seeds are 128-bit values rendered as lowercase hexadecimal. Commit sequence is a local monotonic unsigned integer. CRC32 catches incomplete media writes; SHA-256 covers canonical payload bytes. Checksums are integrity tools, not online signatures or anti-cheat requirements.
run_creation_draft and current_run are mutually exclusive. A draft with
status seed_reserved has an empty offer array and selection. offers_ready
has exactly four complete offers and no accepted pair; selection_ready has the
same four offers and exactly two distinct selected IDs. No state may serialize a
partial offer array, one selected ID, or a draft whose seed ordinal is not less
than next_run_ordinal.
Wall-clock fields may be stored for display and diagnostics only. They cannot select conflict winners or calculate gameplay.
3. Versioning And Compatibility
| Field | Changes when | Compatibility rule |
|---|---|---|
profile_schema_version |
settings/slot index layout changes | profile-only ordered migration |
save_schema_version |
Guild/run/operation serialization changes | sequential copy migration required |
ruleset_version |
combat, target, price, failure, or progression formula changes | committed battle finishes under stored version |
content_revision |
stable catalog row or localization data changes | stable-ID mapping or explicit legacy handling |
The current schema and previous three shipped schemas must have tested adjacent migrations. Older schemas may have a longer supported chain; absence of a chain blocks that slot with export/recovery options and never authorizes reset.
A run-creation draft, battle, reward draft, Market board, or outcome already committed stays on its stored ruleset/content snapshot. A new ruleset becomes active only before seed reservation or a new Market is generated, after all prior result operations are finalized. It cannot rematerialize an existing opening four.
No shipped gameplay save exists during this design-only stage. These rules bind all later prototype and production schemas and do not imply a current runtime migration obligation.
4. Operation Identity And Status
Every state-changing action has:
operation_id = hash(Guild_id, lineage_id, commit_sequence_at_intent,
operation_kind, source_stable_id, local_nonce)
intent_hash = sha256(canonical_operation_inputs)
status: pending | applied | rejected
The journal row stores:
operation_id, operation_kind, intent_hash, status
pre_commit_id, result_commit_id
cost_delta, result_delta, rejection_reason
run_id, Market_index, encounter_id, simulation_tick as applicable
receipt_hash, previous_closure_receipt_hash as applicable
Rules for duplicate IDs:
- same
operation_idand sameintent_hash: return stored pending status, applied receipt, or rejection without creating a new operation; - same ID and different hash: reject
OPERATION_ID_CONFLICT, applying nothing; - new ID: validate and enter the transaction procedure below.
Applied IDs remain in the full journal through run close. They then compact into an immutable membership set retained for the Guild lineage. Receipt-bearing casualty, reward, boss-clear, Renown, run-close, migration, and delete operations remain in the redundant closure ledger until the Guild slot is deleted.
The detailed journal is bounded to 4,096 rows. At a Market with no pending operation, reaching 3,584 rows compacts the oldest finalized non-closure rows into sorted exact-ID segments containing operation ID, intent hash, final status, and receipt hash. The newest 512 detailed rows and every closure receipt remain expanded. Lookup checks exact segments before accepting a new ID; probabilistic membership is forbidden because a false match could reject a valid purchase or hide a duplicate.
5. Pending-To-Applied Transaction Procedure
Every durable transaction uses these steps:
- Look up ID/hash before current-state validation and return any existing row.
- Validate the active commit, legal phase, referenced IDs, target, and intent.
- Build a candidate child commit containing a
pendingrow with the complete immutable intent and zerocost_delta/result_delta. - Validate, write, flush, atomically point to, and read back that pending child.
- From the pending intent and unchanged parent facts, compute the complete cost and result. No new random draw is allowed; generated outputs were fixed in intent or derive from stored labeled seeds.
- Build one child that replaces
pendingwithapplied, includes all cost and result deltas, receipt, and closure link when required. - Validate schema, nonnegative resources, unique ownership, Recruit/equipment consistency, Market lineage, battle hashes, and operation transition.
- Serialize to a new file, write integrity values, flush file and directory, atomically replace the active pointer, then read back.
- Rotate backups only after the applied child reads back and validates.
A precondition failure after step 3 produces a rejected child with zero deltas
and one reason. It cannot return to pending. Presentation, sound, achievement,
cloud upload, and screen transition occur only after step 9.
5.1 Crash And Retry Boundaries
| Crash point | Reachable active state | Recovery/retry |
|---|---|---|
| Before pending pointer switch | Parent only | retry creates same intent; no cost exists |
| After pending switch | Pending intent, zero deltas | deterministic finalize or reject once |
| During applied pointer switch | Pending or complete applied child | validate selected pointer; finalize or return receipt |
| After applied switch, before backup rotation | Complete applied child | return stored receipt; never reapply |
| During backup/closure replication | Applied active plus older replicas | repair replicas from active receipt; no gameplay replay |
Recovery may finalize a pending buy, casualty, reward, or run-close operation only from its fixed intent. It never asks the player to redraw or reconfirm a result whose confirm was already durably recorded. A pending destructive action that lacks a valid target after recovery becomes rejected with no deltas.
6. Transaction Boundaries
| Action | Pending intent contains | Applied child contains |
|---|---|---|
| Begin run creation | Contract, difficulty, expected next_run_ordinal, derived draft/run IDs and seed, version/role-weight snapshot |
one seed_reserved draft and ordinal increment; no offers or current_run |
| Materialize opening offers | draft ID/hash, run seed hash, versions, expected empty offer set | all four indexed offer records/IDs/output hashes and one offer-set hash |
| Set opening selection | draft ID/hash, offer-set hash, expected selection revision, exactly two distinct offer IDs | same four offers, canonical selected pair, revision increment, receipt |
| Commit run creation | draft ID/hash, offer-set hash, selection revision, exact pair and output hashes | draft removed; same run ID/seed and selected Recruit records in one new current_run; opening Coin/capacity and run-start receipt |
| Buy Recruit | offer ID, recruit output hash, Credit choice, expected price | Coin/Credit cost, offer removal, Recruit ownership |
| Buy/sell equipment | offer/item ID and expected receipt | Coin delta and exactly one ownership change |
| Lock/refresh | Market hash, selected locks, expected cost | Coin cost, counters, complete fixed offer board |
| Equip/reorder | Recruit, candidate item, expected prior item or null, and/or order IDs | one-slot replacement receipt, complete legal inventory and Party-line state |
| Promote | Recruit, old/new Profession, eligibility proof | one Profession replacement and derived-state refresh |
| Dismiss | Recruit, optional equipped item ID, rebate preview, expected live_recruit_count |
Recruit removal, optional item return, Coin rebate, post-count at least one |
| Commit battle | battle offer, lineup, stats, seed, risk | immutable encounter start snapshot |
| Start retreat | encounter, accepted tick | one retreat timestamp; no duplicate start |
| Lock outcome | objective and final state hashes | immutable victory/defeat/timeout/retreat result |
| Apply casualties | exact Recruit/optional-item/Credit set | all Recruit removals, all present item returns, all Credit grants |
| Claim reward | fixed draft/card/disposition | one reward and draft removal |
| Close run | terminal cause and persistent grants | Coin/Credit clear, run result, Records/Renown once |
| Change difficulty/law | Guild-only selected rule set | next-run configuration only |
An operation cannot use a presentation animation as a commit boundary. A double-tap, key repeat, controller repeat, OS lifecycle retry, or cloud retry returns the same status/receipt.
7. Run Creation, Market, And Round Save Points
7.1 Seed-First Run Creation
Begin run creation derives values without an ambient RNG or wall clock:
seed_ordinal = next_run_ordinal
run_seed = low128(sha256(generation_root_seed || lineage_id ||
"run-seed" || u64(seed_ordinal)))
draft_id = low128(sha256(run_seed || "run-creation-draft"))
run_id = low128(sha256(run_seed || "run-id"))
Its pending intent fixes the chosen Contract/difficulty and generation snapshot
but generates no Recruit. Its applied child creates the seed_reserved draft,
increments next_run_ordinal once, mirrors a seed-reservation receipt into both
closure ledgers, and is flushed, pointed to, and read back before the opening
generator may run. A retry from the unchanged parent derives the same ordinal
and seed. Once a draft exists, a new begin request returns
RUN_CREATION_DRAFT_ACTIVE and that draft; it cannot consume another ordinal.
If current_run exists, begin returns RUN_ALREADY_ACTIVE and applies nothing.
Materialize opening offers uses only the read-back draft and the content
authority's fixed mappings for indices 0..3. One applied child stores all four
complete Recruit records, offer IDs, output hashes, and their canonical
offer-set hash. A missing index, duplicate ID, output-hash mismatch, changed
generation snapshot, or any fifth offer rejects with zero deltas and leaves the
same reserved seed. The materialization receipt is mirrored redundantly and is
sufficient to regenerate and verify the same four records.
The UI may hold a zero- or one-card highlight transiently, but the durable
Set opening selection operation accepts only a canonical unordered pair of two
distinct IDs from the saved four. A different legal pair replaces the prior
pair and increments selection_revision; repeating the same operation ID/hash
returns its original receipt. A new operation requesting the already accepted
pair returns SELECTION_UNCHANGED and that receipt without a commit. The player
may revise the pair until Run commit without regenerating an offer.
Back or Cancel exits the view only; it does not discard the draft, change its Contract/difficulty, consume an ordinal, or expose a refresh action. Save and Quit, suspension, process restart, locale/timezone change, and wall-clock rollback reopen the same four offers and latest durable pair. No player-facing action allocates a replacement draft while one exists.
Commit run creation revalidates the draft ID/hash, offer-set hash, exact
selection revision, pair, and both selected output hashes. Its applied child is
one atomic state: it removes the draft, creates current_run with the same
run_id and run_seed, transfers the two unchanged Recruit records, establishes
52 Coin and Line capacity four, and writes the run-start checkpoint and receipt.
There is no intermediate state with both objects or neither object. A request
for a stale draft/revision returns STALE_RUN_CREATION_DRAFT plus the current
draft summary and applies nothing. A retry naming an already promoted draft and
matching its commit receipt returns RUN_CREATION_ALREADY_APPLIED plus the
original receipt; a conflicting pair/hash is rejected. Neither path generates
offers or creates a second Run.
The Run-creation commit receipt is indexed by both operation_id and
draft_id. Therefore a duplicate confirm with the original ID and a recovery
retry that acquired a new operation ID both resolve to the one applied receipt
when their draft/pair/hash intent matches. A reused draft_id with different
intent returns RUN_CREATION_COMMIT_CONFLICT and cannot reach the generic
new-ID path.
Run-Creation Crash And Retry Matrix
| Durable boundary | Reachable state after recovery | Exact retry result |
|---|---|---|
| Before begin-pending pointer | parent, no draft, unchanged ordinal | derive the same ordinal/seed and retry |
| After begin-pending pointer | fixed seed intent, zero deltas | finalize the same seed_reserved draft once |
| After seed-reservation pointer/read-back | seed_reserved, no offers |
materialize indices 0..3 from that seed |
| Before/during materialization pointer | seed-only or one complete four-offer child | validate pointer; regenerate same four or return receipt |
| After materialization pointer | offers_ready, complete saved four |
reopen exact four; no generator call on normal load |
| Before/during selection pointer | prior pair or one complete revised pair | validate pointer; finalize pair or return its receipt |
| After selection pointer | selection_ready with exact pair/revision |
reopen same pair; later revision remains legal |
| Before/during Run-commit pointer | complete draft or complete current_run |
finalize from pending intent or return original commit receipt |
| After Run-commit pointer, before replica rotation | current_run, no draft |
repair replicas; never recreate draft or transfer twice |
Every row uses the generic pending-to-applied procedure in Section 5. A corrupt candidate is never partially merged with its parent.
7.2 Market, Round, And Run Save Points
Safe commits occur after:
- run-seed reservation, complete opening-offer materialization, each accepted two-Recruit selection revision, and atomic Run creation;
- Market generation, lock, refresh, buy, sell, equip, reorder, promote, and dismissal;
- battle offer commit and countdown start;
- accepted retreat start;
- encounter outcome lock;
- casualty application;
- reward claim or failure application;
- capacity increase, boss Muster, boss result, and run close.
Save and Quit completes the current tick/transaction and points to the same
active commit. It does not create a selectable branch or reroll point. Loading a
Market returns the exact offers, locks, refresh counter, Coin, Credit, Company,
Reserve, inventory, and order.
Failed attempts increment Market_index but not cleared_rounds. The next
Market still prepares the same required Round number with a new precommitted
board from the run seed. A boss failure reopens boss Muster with the same fixed
boss and newly derived Recruit/equipment offers. This progression cannot be
reset by closing or restoring a player-selectable save.
8. Battle Interruption And Deterministic Resume
An encounter snapshot stores:
encounter_id, ruleset_version, content_revision, root_seed
simulation_tick and per-label draw counters
ordered actor IDs/footprints, fixed-point HP/stats/resources/readiness
casts, projectiles, movement intents, summons, statuses, barriers, cooldowns
objective/phase/Overtime/retreat state
automatic event journal and accepted lifecycle commands
snapshot_hash, previous_snapshot_hash
The game writes a resumable snapshot every ten ticks, at every accepted retreat command, after every Fallen/collapse batch, and at outcome lock. On normal suspend it completes the current priority batch before acknowledging suspend.
If killed earlier, recovery:
- loads the newest valid encounter snapshot;
- replays deterministic automatic events and accepted lifecycle commands from the previous valid hash to the last journaled tick;
- verifies all labeled draw counters and state hashes;
- resumes at the first unprocessed priority of the next tick.
No wall time is substituted. A week-long suspend resumes the same tick. Camera, animation, particles, and audio may restart; authoritative events may not.
If all encounter snapshots are corrupt but the committed battle start and event
journal validate, replay from countdown using the same seed and retreat tick.
If replay hash diverges, seal the run as recovery_incomplete under Section 12;
never offer a fresh seed or pre-battle edit after observed casualties.
9. Recruit Death, Equipment Return, And Dismissal
9.1 Casualty Settlement
Fallen state exists in battle snapshots as soon as the death batch resolves. Run ownership changes only in the casualty settlement after outcome lock. The pending intent lists, for every casualty:
recruit_id, final Profession/level/XP/Trait/history hash
equipped_item_id or null
original list price, Coin paid, computed Credit grant
death tick and causal event ID
Applied settlement must satisfy:
all casualty Recruit IDs absent from living Company and Reserve
every non-null casualty equipped_item_id present exactly once in run inventory
Replacement Credit equals the capped sum
fallen total increases by exact casualty count
no non-casualty Recruit or item changes ownership
If any condition fails, the candidate is rejected and the outcome remains locked pending recovery. The game cannot enter the next Market with a partial casualty result.
Casualties apply on victory, defeat, timeout, and retreat. A same-batch mutual defeat does not preserve either Fallen Recruit. Rehearsal simulations create no operation and cannot affect ownership.
9.2 Dismissal
Dismissal is an explicit destructive operation from a Market. The pending review shows generated name, Profession, Trait, level, survival history, returned equipment, Coin rebate, resulting deployable footprint, and no Credit grant. Default focus is cancel. Both preview and commit use the product authority's canonical count:
live_recruit_count = count(unique Recruit IDs owned by the current run
whose state is living,
across deployed party and Reserve)
dismissal_legal = Market_open and target_is_living and
live_recruit_count >= 2
Dismissal is rejected for a Recruit referenced by a committed battle, pending
promotion, pending casualty settlement, or another dismissal. It is also
rejected as FINAL_LIVE_RECRUIT when the canonical count is one.
Preview rejection creates no operation ID, pending row, or save mutation. A
crafted or stale confirm is revalidated before a pending gameplay operation can
apply. Its FINAL_LIVE_RECRUIT response has zero cost/result deltas: no Recruit
or equipment moves, no Coin rebate, no Credit, no protected recovery offer, no
dismissed-total change, and no commit-sequence advance. The implementation may
retain only the rejected intent response keyed by operation ID and intent hash
so duplicate confirms return the same response; this is not an applied/pending
gameplay receipt and cannot later become legal after the Company changes.
A pending dismissal recovered after interruption stores its parent commit and expected count. Before transition to applied, it must prove the active parent is unchanged, pre-count is at least two, and post-count is exactly pre-count minus one and at least one. Otherwise it becomes the same zero-mutation rejected intent response. A successfully applied dismissal returns its original receipt on duplicate confirm and cannot remove another Recruit or pay a second rebate.
Explicit Abandon Run is a separate run-close operation; it does not reuse a
dismissal ID or pay a rebate. Casualty settlement is also separate and remains
the only path by which a final Recruit's death can create Credit, a protected
offer, recovery viability evaluation, or Company collapse.
9.3 No Resurrection Boundary
Applied casualty receipts are closure receipts. An older backup that predates a casualty can become active only after the receipt chain deterministically replays through that casualty or after the affected run is sealed. A recovery UI must never present "continue run" from an older state containing a Recruit whose later valid casualty receipt is known.
10. Duplicate Purchase, Reward, And Persistent Grant Protection
UI debounce is optional convenience; operation IDs are authority.
For purchase:
if operation applied: return stored Recruit/item and receipt
if pending: finalize same fixed offer and price
if offer absent under a new operation ID: reject OFFER_ALREADY_CONSUMED
For reward:
verify card ID belongs to stored draft
pending contains exact selected card and replacement choice if required
applied removes draft and adds one item/Artifact/Coin result
For Renown, clear, and discovery facts, the stable grant ID is derived from Guild ID plus objective/content ID. A repeated boss outcome or platform retry finds the existing applied ID and grants nothing again.
Optional platform achievements derive from applied Records. The outbox holds at most 100 idempotent events. When full, older events are regenerated from Records rather than blocking play or changing gameplay resources.
11. Backup, Corruption, And Closure Receipts
11.1 Backup Set
Each Guild slot stores:
active: newest validated commit;backup_1..4: distinct prior verified transaction boundaries;run_creation: latest verified seed/offers/selection draft boundary until atomic Run commit;run_start: current run creation commit until run close;Market_checkpoint: latest Market-open commit;pre_migration: source bytes before the latest migration;closure_Aandclosure_B: alternating append-only closure receipt ledgers;quarantine: corrupt bytes and diagnostics, never auto-deleted.
Backups rotate only after active read-back. Storage pressure removes diagnostic
logs before any active, closure, run-creation, run-start, Market, or
pre-migration copy. Seed reservation, offer materialization, selection revision,
and Run commit receipts are mirrored into both closure ledgers under
run_creation_receipt_head. Older selection receipts may compact only after an
exact-ID segment and the latest complete pair/revision snapshot are retained.
11.2 Validation Order
- envelope parse and size bounds;
- CRC32 and SHA-256;
- schema and required fields;
- Guild/lineage/parent/sequence links;
- operation status transitions and receipt chain;
- nonnegative Coin/Credit/Renown and cap checks;
- Recruit uniqueness, generated output hashes, and Profession topology;
- equipment ownership uniqueness and equip references;
- run-creation seed ordinal, draft/current-run exclusivity, indexed opening offer hashes, and selection revision;
- Market seed ancestry, offers, locks, and counters;
- encounter snapshot, event journal, draw counters, and outcome;
- persistent clear/discovery IDs and run-close consistency.
A settings error falls back to defaults without touching gameplay. Optional cloud/platform failure never labels a local save corrupt.
11.3 Recovery Algorithm
- Validate active, then backups newest to oldest.
- Validate both closure ledgers and choose the longest common valid chain.
- Select the newest valid gameplay commit whose ancestry matches the slot.
- Before Run commit, reconstruct a draft only from a valid seed-reservation receipt plus a complete matching receipt chain. Regenerate the four offers and verify their saved hashes, then restore the latest complete pair/revision.
- After a Run-commit receipt exists, never reconstruct its former draft. Recover
the matching
current_run, or seal it if the full transferred state cannot be verified. - Replay later closure receipts only when parent/result hashes form a complete chain and each result validates.
- If the chain reconstructs the newer state, write a recovered child and continue normally.
- If a missing/corrupt gap crosses a Run commit, casualty, reward, boss clear,
Renown grant,
or run close, restore persistent Guild state only through the verified
boundary and seal the current run as
recovery_incomplete. - If no active, backup, draft checkpoint, or redundant receipt proves the
reserved seed, mark the slot
creation_recovery_blocked; do not advance the ordinal, allocate another seed, or offer a playable branch. Export and the existing explicit Guild-slot delete/reset recovery remain available. - Preserve all rejected bytes in quarantine and write a diagnostic manifest.
Sealing clears run-only Coin/Credit and prevents reward or casualty farming. Recovered persistent facts are never guessed beyond the last verified receipt. The UI states the boundary and any unverifiable operations; it never claims full preservation without matching hashes.
12. Failure State Model And Consequences
Failure is classified before one operation calculates consequences:
| Kind | Predicate | Casualties | Coin fee | Run transition |
|---|---|---|---|---|
| Victory with losses | objective succeeds with one or more Fallen Recruits | Apply all | 0 | pay reward, next Market/boss/clear |
| Encounter defeat | all deployed non-summons Fallen or objective failure before timeout | Apply all | difficulty table | no reward; recovery Market if viable |
| Timeout defeat | objective incomplete at tick 1500 | Apply prior Fallen only | difficulty table | no reward; recovery Market if viable |
| Voluntary retreat | withdrawal completes | Apply all Fallen before completion | difficulty table | no reward; recovery Market if viable |
| Run abandonment | player confirms from Market/result | No new casualties | clear all run Coin/Credit | close run, no unearned boss grant |
| Company collapse | no living Recruit and no affordable protected recovery hire | already applied | 0 additional | close run |
| Process interruption | suspend/crash/power loss | none beyond deterministic replay | 0 | resume/reconstruct; not gameplay failure |
| Save corruption | active data fails validation | none invented | 0 | recover or seal; not gameplay failure |
Difficulty fees are the sole numeric failure-fee authority:
| Difficulty | Encounter defeat | Timeout | Retreat |
|---|---|---|---|
| Wayfinder | 0 Coin | min(2,current Coin) |
min(4,current Coin) |
| Standard | min(2,current Coin) |
min(4,current Coin) |
min(6,current Coin) |
| Oathbound | min(4,current Coin) |
min(6,current Coin) |
min(8,current Coin) |
Fee, casualty settlement, failure totals, consumed battle offer, and next-Market seed advance are one result transaction. Fees never make Coin negative and do not alter Credit. Retreat is therefore a controlled loss rather than a free way to cycle battle/Market offers.
12.1 Recovery Viability And Run Close
After failure settlement:
can_continue = living_recruit_count > 0
or coin + replacement_credit >= protected_offer_list_price
If true, open a recovery Market using the same required cleared-Round number. If false, show Company collapse and close the run after confirmation/automatic receipt. Explicitly declining the only affordable protected offer recomputes the predicate and previews run close before confirm.
This predicate runs only after casualty/failure settlement or protected-offer
decline. Dismissal cannot reduce the canonical live count below one and cannot
invoke this recovery/closure path. A player who wants to end a run while one or
more Recruits live must use the separate Abandon Run transaction.
Boss defeat follows the same rule. Cleared-Round count remains 12; boss Muster reopens if viable. No failure restores a consumed reward, dead Recruit, prior Market, or refresh counter.
12.2 Run Close
Run close stores terminal cause, Company history, all casualties/dismissals, boss/clear facts, discoveries, and eligible Renown. It then clears run-only Coin, Credit, Recruits, inventory, Artifacts, Market, and encounter state. Survivors become nonplayable history Records. One operation applies closure and persistent grants; retry returns the same run result.
13. Migration Contract
Migration runs only at startup or a Market with no pending operation. It never runs during countdown, battle, outcome, casualty settlement, reward selection, or run close.
- Validate and copy source bytes to
pre_migration. - Build an adjacent-version plan; skipping a required step is illegal.
- Resolve stable content IDs and list legacy/missing rows.
- Apply each pure deterministic migration to a copy.
- Preserve generated name parts, rendered-name snapshot, appearance seed, attributes, Trait, Profession, level, and survival history for living Recruits; do not draw replacements.
- Recompute derived stats from preserved source fields under the boundary rule.
- Validate operation, ownership, Market, battle, and closure chains.
- Write/read a new lineage child, then switch active pointer.
- Preserve pre-migration bytes until two later valid Market commits and run close or 30 launches, whichever is later.
A removed equipment/Artifact ID becomes a sealed legacy record with its stable ID and original ownership. At the next safe Market, the player chooses one of two deterministic same-band behavior replacements or leaves it sealed. A removed Profession keeps its stored ruleset for the current committed battle; afterward a deterministic parent-compatible replacement review is required.
If migration fails, discard the candidate and leave the source active under its
last supported ruleset where possible. Mark only that slot migration_blocked
and offer details/export. Never reset, auto-sell, reroll, or auto-equip.
14. Local, Cloud, Multi-Device, And Rollback
Cloud transports complete validated commits plus closure ledgers. It does not merge Coin, Recruits, equipment, offers, casualties, rewards, or operation rows.
| Commit topology | Resolution |
|---|---|
| Identical commit | no action |
| Local ancestor of cloud | offer verified cloud descendant; retain local backup |
| Cloud ancestor of local | keep local; queue upload |
| Different Guild/lineage | import only into an empty slot after validation |
| Same lineage, divergent descendants | never merge; choose one active branch, archive the other read-only |
Conflict comparison shows commit boundary, Region/Contract, Market/encounter, cleared Rounds, living/fallen counts, Renown, and closure receipt head. Wall clock is context only. Default is cancel/inspect.
The unchosen divergent branch cannot be duplicated into a second playable slot, because that would duplicate generated offers and allow alternate casualty or reward outcomes. It remains exportable/read-only until explicitly deleted.
A developer/support rollback follows the same recovery algorithm as corruption. It may activate an older commit only after replaying later valid closure receipts or sealing the affected run. No debug, platform, or user-facing path may resume an older branch that reverses a known applied casualty or reopens a known claimed reward.
15. Clock Rollback And Bounded Offline Behavior
All gameplay time uses simulation ticks or monotonic in-session duration. Wall clock is display/diagnostic data.
wall_delta = current_wall_clock - last_seen_wall_clock
if wall_delta < -300 seconds or wall_delta > 180 days:
record CLOCK_ANOMALY
change no gameplay state
While closed, the game advances exactly zero ticks and generates no Market, offer, Coin, Credit, XP, Renown, healing, promotion, unlock, enemy outcome, or reward. Return opens the saved state plus a compact recap.
Background work is bounded:
- at most one local save flush at a time;
- at most three complete cloud payloads queued per Guild slot;
- at most 100 idempotent platform events;
- at most 30 seconds of best-effort upload after an explicit foreground save, cancellable without affecting the local commit;
- no retry loop blocks exit, local play, or a new local commit.
Changing clock, timezone, locale, sleep duration, or network availability cannot change generated names, offers, difficulty, results, or Records.
16. Confirmation, Overwrite, Delete, And Reset
These actions require an effect review and a separate confirm: dismissal with history, promotion, Artifact replacement, protected recovery-offer decline that would close a run, run abandonment, difficulty/Oath-law change, cloud branch choice, migration fallback, Guild-slot overwrite, and Guild-slot delete.
Delete moves the complete Guild slot and closure ledgers to recoverable local trash for seven successful launches or until explicit empty-trash confirmation. A profile-wide reset additionally requires selecting a displayed localized phrase in a second dialog; typing is not required. Default focus is cancel, and destructive actions cannot share a single repeated input with the opener.
Every accepted confirm receives an operation ID. Repeating it returns the first receipt and cannot dismiss, grant, overwrite, close, or delete twice.
17. Accessibility, Localization, And Input Requirements
Saving,Saved,Pending,Recovered,Sealed,Conflict, andBlockeduse text plus distinct icons, never color/spinner alone.- Closing during
Pendingis legal; recovery finalizes/rejects from intent. - Recovery and conflict dialogs support 130% UI scale, screen-reader order, complete touch path, keyboard/controller focus, and reduced motion.
- Casualty review lists generated name, Profession, equipment returned, Credit, death tick, and first cause; it does not rely on animation or sound.
- Technical IDs/hashes are hidden by default but available in details and an exportable diagnostic manifest.
- Localized messages use semantic IDs and typed values, never string fragments.
- Controller disconnect or focus loss pauses presentation and commits nothing until a new explicit confirm is accepted.
- Haptic/audio feedback may acknowledge a save or casualty but cannot be the only state indication.
18. Required Recovery And Failure Matrix
| Test | Fault/input | Expected invariant |
|---|---|---|
| Seed reservation crash | crash after begin-pending or applied pointer | same ordinal/seed; zero offers before applied draft read-back; one draft after retry |
| Offer materialization crash | crash around four-offer pointer | seed-only draft or complete indices 0..3; retry yields identical IDs/output hashes |
| Selection revision crash | revise accepted pair and crash around pointer | prior complete pair or new complete pair with one revision/receipt; never one selected ID |
| Run creation duplicate | repeat original ID or use a new ID naming the applied draft/pair | same original receipt, same seed/two Recruit records, no draft and no second Run |
| Run creation stale draft | submit old draft hash or selection revision | STALE_RUN_CREATION_DRAFT, current summary returned, zero mutation |
| Run creation cancel/restart | Cancel, Save and Quit, or process restart before commit | same draft, four offer IDs/hashes and latest durable pair; no new ordinal/seed |
| Run draft clock rollback | wall clock moves back 24 hours before Run commit | diagnostic only; same draft/offers/pair and zero gameplay delta |
| Run draft active corruption | invalid active checksum, intact draft checkpoint/backup | recover newest valid draft boundary and exact four/pair; no replacement seed |
| Run draft replica corruption | active/backups invalid, redundant creation receipts intact | rebuild same seed/four/pair, or same committed Run if commit receipt exists |
| Run draft total corruption | no valid primary, backup, checkpoint, or creation receipt | creation_recovery_blocked; quarantine/export, no new draft or free reroll |
| Pending purchase | crash after pending pointer | zero cost until deterministic applied child; one Recruit after retry |
| Applied purchase | duplicate same operation ID | one cost, one Recruit, same receipt |
| Refresh crash | crash after Coin validation | old complete board or new complete board and cost; never mixed |
| Legal dismiss duplicate | start with two live Recruits; repeat confirm | one Recruit removal, zero-or-one item return, one rebate; final live Recruit remains |
| Final dismiss preview | one live Recruit, low Coin, no protected offer | FINAL_LIVE_RECRUIT; no operation ID or mutation; Abandon Run remains separate |
| Stale final dismiss | preview at two, another serialized dismissal leaves one, submit old confirm | zero-delta FINAL_LIVE_RECRUIT; no rebate, protected offer, or count change |
| Interrupted final dismiss | recover pending intent against active count one | reject without apply/receipt beyond rejected intent response; one Recruit remains |
| Battle suspend | suspend during projectile at tick 417 | identical next event/draw counters/final hash |
| Death snapshot | crash one tick after front Recruit falls | replay contains same death and collapse |
| Outcome crash | crash before casualty settlement | locked result reopens; settlement applies exact set once |
| Casualty partial candidate | omit the non-null equipped item in candidate | validation rejects; no Recruit removal/Credit commits |
| Reward duplicate | double-tap selected card | one item/Artifact/Coin result and draft removal |
| Boss grant duplicate | replay same boss-clear result | one clear fact and Renown grant |
| Three deaths | fixed economy fixture | Recruits removed, all equipment returned, 54 Credit capped |
| Standard retreat | 10 Coin at/after tick 70 | 6 Coin fee, all earlier casualties apply, no reward |
| Wayfinder timeout | 1 Coin at tick 1500 | 1 Coin fee, living Recruits survive, no reward |
| Company collapse | zero living, 9 Coin+Credit, protected price 14 | run closes; no free Recruit invented |
| Recoverable company | zero living, 14 Coin+Credit, protected price 14 | recovery Market opens; purchase still requires confirm |
| Clock rollback | wall clock moves back 24 hours | diagnostic only; zero gameplay delta |
| Thirty-day offline | close mid-run and return | zero tick/resource/offer progress; exact recap |
| Active corruption | invalid active checksum, intact receipts/backups | reconstruct newest valid chain or seal run at stated boundary |
| Casualty receipt beyond backup | selected backup contains later-Fallen Recruit | replay casualty or seal run; never resume with Recruit |
| All battle snapshots corrupt | valid start/journal remains | replay same seed/retreat tick or seal on hash divergence |
| Migration failure | stable-ID mapping invalid | source stays active; candidate discarded; export offered |
| Cloud divergence | same lineage, different descendants | one chosen active, other read-only; no merge/duplicate slot |
| Run-close retry | crash after persistent grants before screen transition | same result, Coin/Credit remain cleared, no duplicate Renown |
19. Acceptance Checklist
- Schema, ruleset, content, lineage, commit, and operation versions are separate and migration is copy-first.
- Pending/applied/rejected operations preserve zero-delta pending and atomic cost/result receipts.
- Run creation durably reserves one seed before offer generation, stores an
all-or-nothing indexed four, receipts revisable exact-pair selection, and
promotes that same seed and pair into
current_runexactly once. - Market, battle, casualty, reward, and run-close retries are idempotent.
- Battle suspend/crash reproduces deterministic state or seals the run on an unverifiable divergence.
- Death, equipment return, Credit, dismissal, replacement, defeat, timeout, retreat, and Company collapse are explicit and testable.
- Preview, stale confirm, duplicate confirm, and interruption cannot dismiss the final living Recruit or route dismissal through casualty recovery.
- Backups, closure receipts, corruption, migration, rollback, and cloud conflicts cannot knowingly reverse casualties or duplicate rewards.
- Clock rollback and bounded offline behavior produce zero gameplay progress.
- Confirmation, delete, accessibility, localization, and input rules cover every destructive and recovery state.