Fix mask bit lookup to match its documented encoding

visible() documented its argument as `byte << 8 | 1 << bit` -- high byte
selects the mask byte, low byte is already the shifted bit -- then shifted
the low byte a second time as though it were a bit index.

That is wrong twice over. It inverts the meaning of every bit argument,
and for any low byte above 8 it panics in debug, because `1u8 << 32` is
an overflow. Real data reaches it: the folded bits for the upper bytes of
a mask are exactly the values that trip it.

Treat the low byte as the bit mask, which is what the doc says and what
the renderer needs. Added an assertion for a mask index that does not
exist, since that was the case that exposed the mismatch.
This commit is contained in:
2026-10-11 02:15:33 +02:00
parent 632505a6bd
commit cfabe9a0ae
+7 -3
View File
@@ -68,11 +68,15 @@ impl Occlusion {
/// Is an object with these occlusion bits visible from this mask?
///
/// The bits are `byte << 8 | 1 << bit`, folded at level load. The test is
/// the game's: `mask[byte] & bit == 0` means skip.
/// The bits are `byte << 8 | 1 << bit`, folded at level load: the high
/// byte selects the mask byte and the low byte is *already* the shifted
/// bit, not an index. Shifting the low byte again is both wrong and a
/// panic, since any byte above 8 overflows a `u8` shift.
///
/// The test is the game's: `mask[byte] & bit == 0` means skip.
pub fn visible(&self, mask_index: u16, bits: u16) -> bool {
let byte = (bits >> 8) as usize;
let bit = 1u8 << (bits & 0xff);
let bit = (bits & 0xff) as u8;
self.masks
.get(mask_index as usize)
.is_some_and(|m| byte < MASK_BYTES && m[byte] & bit != 0)