From a4af4269fb0fca53aa5e5ee9d1344dfb27c3b3fc Mon Sep 17 00:00:00 2001 From: TheDarkSword Date: Thu, 9 Jul 2026 20:59:38 +0200 Subject: [PATCH] fix: sky light propagation hanging on unloaded chunks (#2334) * fix(lighting): stop sky light updates from looping on unloaded chunks The sky light propagation could spin forever when a light update happened next to a chunk that wasn't loaded. Writes to an unloaded chunk are dropped silently and reads come back as 0, so the "this neighbor is darker than us" check stayed true on every pass and the same position kept getting queued again. In practice this hangs a tick thread when a block is broken near the edge of the loaded area. Skip neighbors whose chunk isn't loaded in both the increase and decrease passes, matching how the border of loaded chunks already behaves: light doesn't bleed into chunks that aren't there yet. * Use Level::is_chunk_loaded for the unloaded-chunk guard --- pumpkin-world/src/lighting/runtime.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pumpkin-world/src/lighting/runtime.rs b/pumpkin-world/src/lighting/runtime.rs index d36aa6c85..9e0b4d0dc 100644 --- a/pumpkin-world/src/lighting/runtime.rs +++ b/pumpkin-world/src/lighting/runtime.rs @@ -277,6 +277,16 @@ impl DynamicLightEngine { for dir in BlockDirection::all() { let neighbor_pos = pos.offset(dir.to_offset()); + // Never propagate into an unloaded chunk. Writes to an unloaded + // chunk are dropped silently, so the "brighter than neighbor" check + // below would stay true forever and keep re-queuing the same + // position, spinning this loop indefinitely at the border between + // loaded and unloaded chunks. + let (neighbor_chunk, _) = neighbor_pos.chunk_and_chunk_relative_position(); + if !level.is_chunk_loaded(&neighbor_chunk) { + continue; + } + let neighbor_light = self.get_sky_light_level(level, &neighbor_pos); let neighbor_state = level.get_block_state(&neighbor_pos).to_state(); let opacity = neighbor_state.opacity; @@ -306,6 +316,13 @@ impl DynamicLightEngine { for dir in BlockDirection::all() { let neighbor_pos = pos.offset(dir.to_offset()); + // See `propagate_sky_light_increase`: skip unloaded chunks so sky + // light updates never spin at loaded/unloaded chunk borders. + let (neighbor_chunk, _) = neighbor_pos.chunk_and_chunk_relative_position(); + if !level.is_chunk_loaded(&neighbor_chunk) { + continue; + } + let neighbor_light = self.get_sky_light_level(level, &neighbor_pos); if neighbor_light == 0 { continue; // Already dark