Skip to content

SaveLoadX robustness - #82

Draft
Goober5000 wants to merge 5 commits into
FSO-Scripters:masterfrom
Goober5000:fix/saveloadx_robustness
Draft

SaveLoadX robustness#82
Goober5000 wants to merge 5 commits into
FSO-Scripters:masterfrom
Goober5000:fix/saveloadx_robustness

Conversation

@Goober5000

Copy link
Copy Markdown
Contributor
  • fix StripFS2 and detect support ships by ship type
  • validate loaded data before applying it
  • key save slots by string so sparse indexes survive a restart
  • document support-ship rule, tolerant loading, and slot keys
  • restore subsystem data by name at arrival, not by parse index

Goober5000 and others added 5 commits September 4, 2026 01:13
StripFS2 assigned its result to a stray global and returned the input
unchanged.  It was also applied to the finished "<pilot>_<mission>.sav"
filename, whose last four characters can never be ".fs2", so a mission
argument given with its extension was never stripped.  Mission names
passed to the external-load and check SEXPs are now normalized (extension
stripped, lowercased) before being used as a filename or a cache key.

Support ships were identified purely by the "Support" name prefix.  The
engine ship type (objecttypes.tbl, exposed as shipclass.Type.Name) is now
recorded whenever a live handle is available, with a ship-registry lookup
and finally the name prefix as fallbacks for ships whose handles can no
longer be resolved at save time.  The save log names the detection route.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Table lookups (tb.ShipClasses, tb.WeaponClasses, mn.Teams) return an
invalid handle rather than nil for an unknown name, so a save whose class
or team no longer exists used to be assigned blindly.  New Resolve*
helpers return a valid handle or nil and log the skip.

Apply paths now tolerate a save that does not match the current ship:
missing or extra subsystems, missing or extra weapon banks, absent
Position/Orientation/Velocity tables, and malformed order or bank entries
are logged and skipped instead of indexing nil.  SanitizeShipData fills in
the sub-tables every path indexes into.

File and slot level: a save file that fails to parse resets to empty with
a log line instead of leaving LoadedData nil; LoadAll only commits
CurrentIndex once the slot is confirmed to exist, so a bad index no longer
makes CheckLateData crash on every later arrival; QueryStatus on a missing
slot returns true (same as "nothing loaded") instead of indexing nil.

HandleIsValid is a forward-compatible validity test for parse_subsystem
handles, which do not yet expose isValid(); it uses isValid() when present
and otherwise falls back to a guarded field read.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
LoadedData was keyed by the numeric slot index.  dkjson writes a Lua table
as a JSON array only when its keys are dense, so a mission that only ever
saved to a slot above 10 got an object with a string key on disk, and the
numeric lookup after the next mission start found nothing.

Every slot access now goes through SlotKey (tostring), and NormalizeSlots
converts the keys of a freshly read file so legacy array-shaped saves keep
working.  Exists() with no index uses next() instead of the length
operator, and the SaveAll fallback for a nil index scans for the highest
numeric key instead of relying on #.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ApplyParseData applied saved subsystem damage and turret loadouts to a
not-yet-arrived ship by position in the parse object's subsystem array.
That array only holds the subsystems FRED explicitly wrote to the mission
file, in file order, while the save records every model subsystem in
model order, so the two lists never lined up and damage landed on the
wrong subsystem or nowhere.  The parse object's MainStatus is likewise an
invalid handle when FRED made no loadout change, so a late arrival's
saved weapons were never restored.

Saved subsystem entries now carry the canonical model name.  Two shared
helpers, ApplySubsystemData and ApplyShipBankData, apply subsystem and
bank data to a live ship matched by name (case-insensitive), with a
positional fallback and a log line for saves written before names were
recorded.  ApplyData uses them at load time for ships already present;
ApplyLateData uses them on the arrival hook for ships that were on the
arrival list, where every subsystem is addressable.  The parse-side
subsystem loop is removed; MainStatus is still applied when valid so the
loadout is right at spawn, and re-applied idempotently on arrival.

A saved 0% subsystem is destroyed with destroy-subsys-instantly rather
than by writing a non-positive HitpointsLeft, which would trigger debris
and effects.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant