fix(lod): light saved far columns' open water as open sky

A saved chunk's LOD columns kept each water block's own skylight, which
fades a level per block of water, while the generator's far columns light
open water as open sky. Where the two met - the stride limit in
`SavedLodSampler::grid` - the difference drew a dark band through the sea.

`Column::light_open_water` already existed for this and the live path
(`Column::from_chunk`) applies it; `decode_lod_surfaces` never did. It has
no `BlockFacts` and so cannot reach `state_kinds`, so the rule moves into
`Column::light_open_water_where`, taking the caller's own test for water;
the decoder names water from the block registry, which leaves lava out as
the emissive test did.

Only where there is a sky: the decoder's `sky_light` gates it, so the
Nether's water gains no skylight it never had.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
itarqos5andClaude Opus 5 committed 2026-11-02 18:24:51 +00:00
1 parent 44ca91ef8f
commit b4629e064f
2 files changed
+40 -3

No files matched your search

+11 -3
View File
@@ -278,10 +278,18 @@ impl Column {
/// The water is one span again.
fn light_open_water(&mut self, kinds: &[u8]) {
// Water; lava glows.
let water = |span: &Span| {
let kind = kinds.get(span.state.0 as usize).copied().unwrap_or(0);
self.light_open_water_where(|state| {
let kind = kinds.get(state.0 as usize).copied().unwrap_or(0);
kind & FLUID != 0 && kind >> 4 == 0
};
});
}
/// [`Column::light_open_water`] for a caller that knows which states are
/// water without `BlockFacts`: the save decoder, whose palette already
/// names water apart from lava. Saved columns skipping this lit their
/// water a level darker per block, which drew a band through the sea
/// where they met the generator's columns.
pub fn light_open_water_where(&mut self, is_water: impl Fn(BlockStateId) -> bool) {
let water = |span: &Span| is_water(span.state);
if !self.spans.last().is_some_and(water) {
return;
}
+29
View File
@@ -4846,8 +4846,23 @@ fn decode_lod_surfaces(
}
}
}
// Spans are pushed top down, so the reverse puts the topmost last, as
// `Column::from_chunk` leaves them. Open water is then lit as open sky,
// exactly as the live path does: without it a saved column kept each
// water block's own faded skylight and drew a dark band through the sea
// where saved columns met the generator's (`grid`'s stride limit).
// Only where there is a sky to be lit by: the Nether's water must not
// gain skylight it never had.
let is_water = |state| {
sky_light
&& assets
.registry
.state(state)
.is_some_and(|block| block.name == "minecraft:water")
};
for column in &mut *columns {
column.spans.reverse();
column.light_open_water_where(is_water);
}
Some(Arc::from(columns))
}
@@ -5639,6 +5654,20 @@ mod tests {
let underwater = surfaces[4 * 16 + 4].surface.unwrap();
assert_eq!((underwater.height, underwater.state), (55, sand));
assert_eq!(underwater.water, Some((64, water)));
// Open water reads as open sky, as the generator's far columns do.
// Saved columns that kept each block's own faded skylight drew a dark
// band through the sea where the two met.
let sea = &surfaces[4 * 16 + 4].spans;
let lit: Vec<_> = sea
.iter()
.filter(|span| span.state == water)
.map(|span| span.light & 0xf0)
.collect();
assert!(!lit.is_empty(), "the column keeps its water spans");
assert!(
lit.iter().all(|sky| *sky == 0xf0),
"saved open water is lit as open sky, got {lit:?}"
);
let chorus_column = &surfaces[2 * 16 + 12];
assert_eq!(
chorus_column