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.
Campaign save selection
Section titled “Campaign save selection”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.
Automatic backups and damaged saves
Section titled “Automatic backups and damaged saves”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.xmlrecords the version in aneclipseattribute; 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.
Content compatibility fingerprints
Section titled “Content compatibility fingerprints”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.
When a mod is disabled or missing
Section titled “When a mod is disabled or missing”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.
When you change an item ID
Section titled “When you change an item ID”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.
When you change saved fields
Section titled “When you change saved fields”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.
Interrupted profile writes
Section titled “Interrupted profile writes”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.
Eclipse mode battles in older saves
Section titled “Eclipse mode battles in older saves”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.
Before publishing an update
Section titled “Before publishing an update”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.
Recorded shop purchase history
Section titled “Recorded shop purchase history”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.