Skip to content

[R2-8][P2] Surface silent save/mutation errors to user and log #843

Description

@MichaelFisher1997

Part of #839 — [Audit][Round 2] A++ umbrella.
Phase: P2 · Finding: R2-8

Problem

User-facing save operations and player block mutations silently swallow errors with catch {}. The user gets no feedback that their save data wasn't deleted/renamed, and level.dat corruption is possible without surfacing. Masks real bugs (out-of-bounds, chunk-unloaded races, disk full).

Evidence

Silent save failures (user-facing)

modules/game-ui/src/screens/world_list.zig:364,384,413 (verified in audit):

self.confirmDelete(idx) catch {};      // line 364 — world delete failure silent
self.clearAllWorlds() catch {};        // line 384 — clear-all failure silent
self.renameWorld(idx) catch {};        // line 413 — rename failure silent

These touch disk. A failure means the user clicked "Delete World", got a green toast, and the world is still on disk.

Silent block mutations

modules/game-core/src/player.zig:386,405:

world.setBlock(target.x, target.y, target.z, .air) catch {};   // line 386 — break block
world.setBlock(px, py, pz, block_type) catch {};               // line 405 — place block

Minor for gameplay, but masks real bugs (chunk-unloaded races, out-of-bounds).

Logged-then-dropped save errors

modules/world-persistence/src/save_manager.zig:131-132, 167-168:

self.level_data.saveToFile(self.allocator, self.save_dir) catch |err| {
    log.log.err("Failed to save level.dat: {}", .{err});
};

The save thread logs the error then drops it. level.dat corruption is possible without surfacing to the player. No failed_chunks retry-on-next-session logic visible here.

Count for context

  • 27 catch {} total across the codebase; most benign (tmp-file cleanup, optional UI actions, sleep fallbacks). The 7 above are the user-facing/risky ones.

Fix

Tier 1 — surface to user (Tier 1 PR)

  • world_list.zig:364,384,413: replace catch {} with catch |err| { log.log.err("...", .{err}); showErrorToast("Failed to delete world"); }. Use the existing toast/notification system in engine-ui.
  • save_manager.zig:131-132, 167-168: introduce a SaveFailureCounter (in-memory) that's checked on next save-session startup. If non-zero, surface "N chunks failed to save last session" warning to the player on world load.

Tier 2 — propagate to logs (Tier 2 PR)

  • player.zig:386,405: replace catch {} with catch |err| { log.log.warn("Block mutation failed at ({}, {}, {}): {}", .{ x, y, z, err }); }. Use warn (not err) — these are expected under chunk-unload races.
  • Audit the remaining 20 catch {} calls; convert any that touch disk, GPU, or user state to catch |err| log....

Tier 3 — surface to UI thread

  • The save thread failure should set an atomic flag that WorldScreen.update() polls and surfaces via a non-blocking "Save warning" indicator (similar to Minecraft's "saving..." badge).

Verification

  • nix develop --command zig build test
  • New unit tests:
    • world_list_tests.zig: inject failing filesystem call, assert error toast is shown (mock the toast system)
    • save_manager_tests.zig: inject failing saveToFile, assert log message and counter increment
    • player_tests.zig: inject failing setBlock, assert warning log
  • grep -rE "catch \{\}" modules/game-ui/ modules/game-core/src/player.zig modules/world-persistence/src/save_manager.zig should return zero matches after fix

Constraints

  • Behavior changes are limited to: previously-silent errors now produce user-visible feedback and/or log entries. No new panics.
  • Tier 1 and Tier 2 can land as separate PRs.
  • Conventional commits: fix(game-ui): surface world-delete errors, fix(world-persistence): log and counter level.dat save failures, fix(game-core): log block-mutation failures.

Notes

  • This is not about making every error fatal — most of these are legitimately recoverable. It's about observability: a silent disk-write failure that loses player data is a real production bug class.
  • Coordinate with R2-9 (game-ui test coverage) — the toast-mock harness developed here benefits both.

Tracking: #839

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggamehotfixquestionFurther information is requestedworld

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions