Skip to content

Incomplete save files crash on load: KeyError escapes the load handlers #57

@dmccoystephenson

Description

@dmccoystephenson

Summary

During a triage pass, the save-loading paths were read end-to-end. The load handlers are clearly meant to recover gracefully from a bad save (they print a warning and create a fresh object), but they only catch (IOError, OSError, json.JSONDecodeError) — not KeyError. The reader/writers access most fields with direct [] indexing, so a save file that is valid JSON but missing a field raises an uncaught KeyError and crashes the game instead of recovering.

  • src/fishE.py:227-234 (loadPlayer) — except (IOError, OSError, json.JSONDecodeError) as e: does not include KeyError. Same pattern in loadStats (src/fishE.py:236) and loadTimeService (src/fishE.py:245).
  • src/player/playerJsonReaderWriter.py:18-22 — direct access: player.fishCount = playerJson["fishCount"], ... = playerJson["money"], etc. (only energy uses .get(..., 100) at line 23).
  • src/stats/statsJsonReaderWriter.py:19-24 — direct access for six fields (only moneyLostWhileDrunk uses .get(..., 0) at line 26).
  • src/world/timeServiceJsonReaderWriter.py:11-12timeService.time = timeServiceJson["time"], ... = timeServiceJson["day"] (no defaults).

Why it matters

A truncated, hand-edited, or older-format save (anything missing a key) crashes on startup rather than falling back to a new game, which is exactly the failure the surrounding try/except was written to prevent. It also undermines the project's backward-compatibility approach to save files — the .get(default) pattern was added for energy/moneyLostWhileDrunk precisely so old saves keep loading, but the remaining fields don't follow it.

Suggested fix

Make field loading backward-compatible and crash-safe. Either (a) read every persisted field with dict.get("<field>", <default>) consistently across the three reader/writers, or (b) add KeyError (and ValueError) to the caught exception tuples in loadPlayer/loadStats/loadTimeService. Option (a) is preferable since it preserves partial data instead of discarding the whole object. Add a regression test that loads a save dict missing a field and asserts graceful handling.

Filed by Claude during an automated triage pass; claims above were verified against source.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions