Part of #839 — [Audit][Round 2] A++ umbrella.
Phase: P2 · Finding: R2-6
Problem
Two registered production generators use catch unreachable on lighting computation. Any allocation failure inside LightingComputer will @panic the game for players using the flat or shadow-test world types. This is the riskiest error-handling pattern in the codebase.
Evidence
modules/worldgen-flat/src/root.zig:62 (verified at HEAD):
LightingComputer.computeSkylight(chunk, self.allocator) catch unreachable;
modules/worldgen-test/src/root.zig:59-60 (per Round 2 audit; verify exact lines during fix):
LightingComputer.computeSkylight(chunk, self.allocator) catch unreachable;
LightingComputer.computeBlockLight(chunk, self.allocator) catch unreachable;
Both FlatWorldGenerator and ShadowTestWorldGenerator are registered in world-worldgen/src/registry.zig:21 and selectable by players.
For context: the rest of the codebase has 11 production catch unreachable total, 8 of which are defensible (bounds-checked indexing after a length check, font glyph indices clamped to a known range). These lighting ones are not — LightingComputer.computeSkylight allocates working buffers for BFS flood fill and can return error.OutOfMemory.
Fix
Three steps:
-
Make generator interface error-aware. The generator generate signature already returns error{OutOfMemory} (or similar) in some paths — confirm and propagate. Change catch unreachable to try:
try LightingComputer.computeSkylight(chunk, self.allocator);
try LightingComputer.computeBlockLight(chunk, self.allocator);
-
Define a WorldgenError set in worldgen-api (or extend existing RegistryError):
pub const WorldgenError = error{
OutOfMemory,
ChunkAllocationFailed,
LightingComputationFailed,
};
Map from LightingComputer's native error set in one place.
-
Caller-side recovery. WorldStreamer and chunk_queue_coordinator already handle error.OutOfMemory by requeuing the chunk (chunk_queue_coordinator.zig:326-353). Verify the new propagated error reaches that path and is handled gracefully — the chunk stays in pending state and is retried next frame, instead of crashing the game.
Verification
nix develop --command zig build test
nix develop --command zig build test -- --test-filter "LightingComputer"
nix develop --command zig build test -- --test-filter "FlatWorld" and --test-filter "ShadowTest"
- Add a property test: inject a failing allocator (use
std.testing.FailingAllocator) into FlatWorldGenerator.generate and assert the error propagates cleanly (no panic).
- Manually launch the flat world type via
-Dauto-world=flat -Dskip-present to confirm it still generates.
Constraints
- Behavior change is limited to: lighting allocation failure → recoverable error instead of panic. No visual change in the success path.
- Small reviewable PR targeting
dev; conventional commit (fix(worldgen): propagate lighting allocation failure instead of panic).
- Do not change
LightingComputer's algorithm — only error propagation.
Notes
- Other concerning silent-failure patterns to address in the same PR (or follow-up):
game-ui/screens/world_list.zig:364,384,413 — silent catch {} on world delete/clear/rename (covered in R2-8)
game-core/src/player.zig:386,405 — silent catch {} on block mutation (covered in R2-8)
- Out of scope: the other 8 defensible
catch unreachable (font/bounds-checked indexing). Leave them.
Tracking: #839
Part of #839 — [Audit][Round 2] A++ umbrella.
Phase: P2 · Finding: R2-6
Problem
Two registered production generators use
catch unreachableon lighting computation. Any allocation failure insideLightingComputerwill@panicthe game for players using theflatorshadow-testworld types. This is the riskiest error-handling pattern in the codebase.Evidence
modules/worldgen-flat/src/root.zig:62(verified at HEAD):modules/worldgen-test/src/root.zig:59-60(per Round 2 audit; verify exact lines during fix):Both
FlatWorldGeneratorandShadowTestWorldGeneratorare registered inworld-worldgen/src/registry.zig:21and selectable by players.For context: the rest of the codebase has 11 production
catch unreachabletotal, 8 of which are defensible (bounds-checked indexing after a length check, font glyph indices clamped to a known range). These lighting ones are not —LightingComputer.computeSkylightallocates working buffers for BFS flood fill and can returnerror.OutOfMemory.Fix
Three steps:
Make generator interface error-aware. The generator
generatesignature already returnserror{OutOfMemory}(or similar) in some paths — confirm and propagate. Changecatch unreachabletotry:Define a
WorldgenErrorset inworldgen-api(or extend existingRegistryError):Map from
LightingComputer's native error set in one place.Caller-side recovery.
WorldStreamerandchunk_queue_coordinatoralready handleerror.OutOfMemoryby requeuing the chunk (chunk_queue_coordinator.zig:326-353). Verify the new propagated error reaches that path and is handled gracefully — the chunk stays inpendingstate and is retried next frame, instead of crashing the game.Verification
nix develop --command zig build testnix develop --command zig build test -- --test-filter "LightingComputer"nix develop --command zig build test -- --test-filter "FlatWorld"and--test-filter "ShadowTest"std.testing.FailingAllocator) intoFlatWorldGenerator.generateand assert the error propagates cleanly (no panic).-Dauto-world=flat -Dskip-presentto confirm it still generates.Constraints
dev; conventional commit (fix(worldgen): propagate lighting allocation failure instead of panic).LightingComputer's algorithm — only error propagation.Notes
game-ui/screens/world_list.zig:364,384,413— silentcatch {}on world delete/clear/rename (covered in R2-8)game-core/src/player.zig:386,405— silentcatch {}on block mutation (covered in R2-8)catch unreachable(font/bounds-checked indexing). Leave them.Tracking: #839