Skip to content

Keeping player saves compatible

A player’s saved mod content depends on stable IDs. Decide your manifest ID and local content IDs before publishing, then preserve them across updates.

Choose Campaign on the title screen to create, select, rename, or delete a campaign. The list has four entries per page and no fixed save-slot limit; available disk space and filesystem performance remain practical limits. Names are 1–48 characters and may be duplicated: each campaign has a separate internal ID. Deleting a campaign requires confirmation and removes all of its saved progress, including mod state.

Each campaign stores its normal XML profiles, backups, hashes, pending write records, and profile-local settings under CampaignSaves/<id>/userdata/, beside the original userdata/ directory in the game’s persistent-data location. campaign.xml in the campaign directory holds its display name and dates. Mod state remains inside the selected profile; mods do not need a new save API or a hard-coded campaign path. Enabled mods and installation-wide mod settings are shared across campaigns.

The first visit copies an existing legacy profile and its companion files into Existing campaign, leaving the original userdata/ intact. Developer SaveProfiles archives are not copied. This import happens only once; deleting the imported campaign does not import it again. New campaigns start from the normal default profile using currently enabled content. Return to the title to switch campaigns. A campaign open in another game process cannot be opened, renamed, or deleted until that process releases it.

Unreadable campaign metadata is isolated to its own list entry; if the previous copy, campaign.xml.bak, is readable it is used instead. Save-directory symbolic links and junctions are refused.

Eclipse keeps up to five timestamped backups of each campaign in CampaignSaves/<id>/backups/. Each backup is a complete copy of that campaign’s userdata/ folder plus a backup.xml note recording when and why it was made. The oldest is removed when a sixth is created. A backup is made:

  • the first time a different Eclipse version opens the campaign, before the game reads it. This covers upgrades, rollbacks, and saves imported from older builds. campaign.xml records the version in an eclipse attribute; older versions ignore that attribute.
  • when a campaign loads with a different set of enabled mods or mod versions than it was last saved with, before mod state migrations run;
  • before restoring another backup.

No backup is made when the save on disk is identical to the newest backup. If a backup cannot be written, for example because the disk is full, the campaign still opens. The backup is attempted again the next time the campaign is opened.

Every profile write also keeps the previous distinct version of users.xml and users_backup.xml as users.xml.bak and users_backup.xml.bak. A damaged file never replaces a good .bak.

When a campaign is opened, Eclipse first finishes any interrupted write. If users.xml is then missing or cannot be parsed, it uses the newest readable copy, in this order: users_backup.xml, the two .bak files, then the backups from newest to oldest. The unreadable file is renamed to users.xml.unreadable-<time> and kept. If nothing readable exists, the campaign does not open and no file is changed. Previously, the game silently started a new profile over the damaged one. A campaign with neither users.xml nor users_backup.xml is treated as new, so an intentional profile reset is not undone.

These checks only test that the profile is well-formed XML rooted at <Root>; a well-formed but logically inconsistent profile is not detected.

To restore one yourself, open Campaign on the title screen, focus a save and choose Backups. The list shows each backup’s date, why it was made and the Eclipse version that made it; choose one and confirm Restore. Your current save is backed up first, so a restore can be undone from the same list. Back up now makes a backup on demand. If a save cannot be read at all, opening it shows this list instead. When opening a save repaired it or a backup could not be made, a short notice appears before the game continues.

Filesystem checks cover import retries, independent profiles, more than 130 saves, locking, and deletion boundaries; full-game switching and title-screen visual validation remain unverified.

Forge candidate exclusions and deviation overrides participate in the content compatibility fingerprint. Changing an override’s bounds is detected even when the mod version stays the same. These changes affect future rolls; existing equipment enchantments retain their saved values.

Default equipment enchantment loadouts also participate in that fingerprint, including entry order and optional aspects. Changing or disabling a loadout restores acquisition defaults without rewriting enchantments already saved on inventory items. The original game’s load check still applies to saved core enchantments, though: a core single enchantment that is no longer one of the item’s default enchantments and that none of the item’s forge recipes can roll is removed from the save when the profile next loads, and extra single or combo enchantments beyond the original limits are removed too. See How the forge works. Mod perks and scripted enchantments are never removed by this check.

Move patches, including their playback rates, interval edits and attack edits, and move forks with their subtype or item lock edits participate in the fingerprint, whether they come from Lua or from moveset files. Mods without these edits keep their previous fingerprint. Move changes affect fights only; saved equipment and progress are unchanged.

Innate equipment loadouts are definition-level effects, not saved enchantments. Their ordered perks and named numeric parameters affect the fingerprint. Disabling them restores the original effects for subsequently built fighters without modifying the inventory’s saved enchantments.

Eclipse preserves unavailable mod-owned inventory records, including counts, upgrades, deliveries, and enchantments. The missing items are excluded from active inventory and equipment processing. Re-enabling or reinstalling the mod restores access when the profile next loads.

If a missing item was equipped, the game uses its normal default item for the visible fighter. That fallback does not overwrite the saved equipment choice. If the player deliberately equips something else, restoring the mod does not override that newer choice.

Mod-owned saved progress is retained while the mod is disabled. This includes Lua state, saved battles, and the checkpoints of the mod’s in-progress quests, which resume after the mod is re-enabled. “New” marks on the mod’s items are also kept. Saved battles of a disabled mod are skipped when the game works out the current act.

A pending lottery reward that names a disabled mod’s item or currency is kept, but it cannot be shown or claimed. Until the mod is re-enabled, it blocks starting another fight. Re-enabling the mod restores the claim. Re-forging an item while its mod is absent can still replace enchantments of the same kind, as described for forging.

Fix loading problems using the error details while preserving the player’s save.

Prefer keeping the published ID even if the display name changes. If an ID must change, register an alias from the old item ID to its replacement using sf2.items.alias. An old save can then resolve to the new item.

For intentionally retired content, a tombstone reserves the old ID while preserving its unavailable save record. It does not erase ownership or grant replacement content.

Use the schema, aliases, tombstones, and migration rules documented in Mod state. A failed state migration preserves the previous saved XML. Avoid reusing an old field name for a different meaning or type.

Eclipse records a pending profile snapshot before replacing users.xml or users_backup.xml and its optional hash. The profile reader replays a valid pending write before loading that file. This handles interruption between the snapshot and hash replacements; it does not make the two profile copies one transaction. Each replacement uses a flushed temporary file in the same directory.

Pending records use a .eclipse-write suffix and are removed after installation. The .eclipse-write.lock sidecar remains for coordination. Recovery checks record integrity and preserves the game’s hash-validation policy when enabled. During gameplay, an invalid record produces an error and is not discarded. When a campaign is opened, a record that cannot be decoded is renamed to .eclipse-write.unreadable-<time> and kept for diagnosis. The previous complete profile then loads, and later saves are no longer blocked. Intentional profile reset or replacement discards pending records so they cannot restore the old profile.

A save request is still not confirmation that writing finished. This mechanism does not roll back partial gameplay mutations or guarantee durability against every filesystem or power failure. Disk failure and fresh-process recovery fixtures cover the write mechanism; full-game restart acceptance remains pending.

The internal lottery workflow stores one pending EclipseLotteryClaim per profile, with a versioned, evaluated prize. Resuming does not rerun reward selection. Claiming defers native profile saves until reward application and the completion marker are ready to save together. A failed settlement blocks later saves until profile reload, so partial in-memory changes cannot overwrite the recoverable snapshot. Missing item definitions or changed upgrade identity prevent restoration rather than silently substituting a different reward. Nested draws and native resistance grants are not supported by claiming yet.

The legacy top-level DialogLottery quest action now uses these helpers for one draw from the fight named by its FightName expression. It shows the saved reward in a parchment dialog using the game’s UI renderer. Claim grants the reward and advances the quest; Later, Back, or scene teardown preserves it without advancing. Opening another non-combat scene reopens the outstanding dialog while that quest action remains active. Replaying the quest after a profile reload uses the saved receipt. This is not an automatic quest-resume scheduler for unresumable quests: their triggering event must run again.

The fight must supply exactly one lottery with an eligible slot. Missing rewards raise an error rather than completing the action without a prize. SpinNumber (archived paid-spin continuation) and native Lock attributes are explicitly unsupported. The preview resolves item and currency names through the current game localization, retaining the definition name when no translation exists. Its title, level suffix and Claim/Later buttons use the existing game strings. New status and scalar-reward labels have English defaults: eclipse.lottery.saved, eclipse.lottery.unavailable, eclipse.lottery.failed, eclipse.lottery.empty, eclipse.lottery.Money (Coins), eclipse.lottery.Bonus (Gems), and eclipse.lottery.Experience. These are host localization keys, not a new Lua API. The preview shows the saved slot’s sprite when available, resolving item names through their normal equipment/seal icon paths and preserving qualified mod sprite IDs. If the slot artwork is unavailable, it tries the first saved item’s icon. Missing or unreadable optional artwork leaves the text and Claim button available. The image keeps its aspect ratio inside the parchment’s scroll area; short reward summaries no longer reserve a large blank text section. Full-game visual acceptance, translations of the new fallback labels and paid rerolls remain unfinished. There is no public Lua lottery API yet. Fight-end lottery deferral checks the reward already composed for that result, including its selected reward scope and mode. A lottery in another win-count reward does not suppress the current fight’s normal quest events. Detection does not re-evaluate reward expressions after progression or level-up, and losses and surrenders do not create a deferred winning claim.

An awarded battle lottery now saves its draw with a BattleEnd continuation before presentation. The reward opens on a non-combat scene, including after profile reload. Claiming saves the inventory change first; accepting the deferred FightEnd or RaidFightEnd event then checkpoints matching quests and saves a dispatch marker before requesting their execution. A reload between those steps resumes event acceptance without rerolling or granting the saved prize again. Encounter identity prevents repeated preparation of the same tracked encounter while its claim record is retained. A pending claim or unsettled continuation blocks starting another fight and replacing the draw. This applies to the single pending claim per profile; it is not a multi-claim inventory or a paid-reroll system.

Automated fixtures exercise claim/continuation recovery with controlled native quest queues and disk services. Full-game scene transitions, quest resume behavior and end-to-end crash recovery still require playtesting; the dispatch marker does not certify that every asynchronous quest action has finished. Automated claim fixtures control reward grants and disk saves; full-game settlement/restart and visual verification remain pending.

When a host supplies a quest invocation ledger, claim completion also records a receipt for that action in the same save. Replaying an acknowledged action does not create another prize. A new quest run uses a new ID; pending receipts prevent replacement of an unfinished run. The native quest runner now exposes its start, checkpoint-resume, and completion boundaries to this bookkeeping. Quest claims are identified by quest file and name under EclipseQuestClaims; an unfinished run is reused when reopened, while a completed run can start a new draw. Resuming after the action definition changes is rejected so an old receipt cannot acknowledge a different action. Concurrent instances of the same lottery quest and nested action identities are not supported by this bridge yet.

Native quest checkpoints also preserve lottery condition context in an optional EclipseLotteryContext child: inLottery, the last spin number, the awarded item identity, and the raid identity. Restoring a checkpoint recovers those values before evaluating later quest actions. Ordinary checkpoints with none of these values do not acquire this metadata; overwriting a checkpoint with ordinary context removes a previous lottery context. Older saves without it retain the normal defaults. This preserves context only; it does not implement paid spins or paid lottery continuation.

Each queued native quest captures its event parameters when it is accepted. Later events cannot replace its fight identity, lottery item, spin or purchase payload while it waits for another quest to finish. Fight IDs and purchase payload values are copied; item definitions and language objects remain shared catalog references. On profile reload, each quest restores its own checkpoint instead of applying the first queued quest’s parameters to the entire queue. The saved average frame rate is restored along with the other native fight fields. Resumed quests retain their saved action index; queue restoration does not rerun the initial checkpoint and reset them to the first action. This preserves the fields the checkpoint format supports; it does not add persistence for every transient event field or make concurrent instances of one quest independently resumable.

Earlier Eclipse versions swapped normal and Eclipse map entries with their own logic, even before the player had finished the Eclipse replay tutorial. Eclipse now uses the game’s original update, which leaves those pairs alone until then. The first time a campaign is opened by this version, a save that has neither finished the replay tutorial nor won the Act VI final battle gets every saved pair restored to the pre-tutorial state: the normal entry shown and its Eclipse counterpart hidden. Entries, locks, replay counts and fight history are kept, and the replay tutorial shows the counterparts again. The profile records this with an EclipseBattleExchangeMigrated="1" attribute so it runs only once; the usual automatic backup is taken before the new version first opens the campaign.

Test with a save from the previous version, including owned and equipped items and any saved behavior progress. Also test disabling and re-enabling the mod. Restart Eclipse after file changes; replacing files does not reload existing definitions.

The save records mod versions and content metadata for diagnostics. Changes to that metadata do not invalidate the player’s save.

Owned weapon tactic_subtype participates in the content fingerprint when specified. Changing the AI group is a content change; omitting the field preserves the previous weapon fingerprint format and subtype fallback.

Weapon AI group overrides through items.set_tactic_subtype record the target, owner and group in the fingerprint. An empty group is an explicit subtype fallback and differs from no override.

The standard coin, gem and consumable shop dispatcher and the three alternate immediate coin, gem and consumable purchase helpers record recognized item purchases in profile metadata while a mod runtime/profile is active. Immediate purchases check affordability and inventory capacity before entering settlement. Each receipt tracks completed transactions and purchased units separately from current inventory. The receipt is included with the final balance/inventory snapshot; intermediate save requests are deferred. A partial settlement failure blocks later saves in that live profile until reload.

This is host bookkeeping, not a public purchase-limit or history-query API. It does not infer purchases made before recording began, and does not cover free grants, external payments, upgrades or deliveries. Bootstrap and unresolved legacy item identities retain their original path without invented receipts. Full-game save/reload acceptance remains pending; do not edit receipt metadata manually.