pub const FOG_HEADER_WORDS: usize = 18;
pub const FOG_MAX_DECKS: usize = 4;
pub const FOG_ACTIVE_DECK: usize = 17;
#[must_use]
pub fn pack_fog_mask(
grid_index: u32,
origin_cell: [i32; 2],
width: u32,
height: u32,
decks: &[[i32; 2]],
active_deck: usize,
memory_dim: f32,
memory_desaturate: f32,
cells: &[u8],
) -> Vec<u32> {
if decks.len() > FOG_MAX_DECKS {
use std::sync::atomic::{AtomicBool, Ordering};
static WARNED: AtomicBool = AtomicBool::new(false);
if !WARNED.swap(true, Ordering::Relaxed) {
log::warn!(
"fog-of-war mask has {} decks; the GPU shader scans only {FOG_MAX_DECKS} — \
decks {FOG_MAX_DECKS}+ render Hidden on GPU (split the ship or raise \
FOG_MAX_DECKS)",
decks.len(),
);
}
}
let deck_count = decks.len().min(FOG_MAX_DECKS);
let cell_words = cells.len().div_ceil(4);
let mut w = vec![0u32; FOG_HEADER_WORDS + cell_words];
w[0] = 1; w[1] = grid_index; w[2] = deck_count as u32; w[3] = origin_cell[0] as u32; w[4] = origin_cell[1] as u32; w[5] = width; w[6] = height; w[7] = memory_dim.to_bits(); w[8] = memory_desaturate.to_bits(); for (d, band) in decks.iter().take(FOG_MAX_DECKS).enumerate() {
w[9 + d * 2] = band[0] as u32; w[9 + d * 2 + 1] = band[1] as u32; }
w[FOG_ACTIVE_DECK] = active_deck.min(deck_count.saturating_sub(1)) as u32;
for (i, chunk) in cells.chunks(4).enumerate() {
let mut word = 0u32;
for (j, &b) in chunk.iter().enumerate() {
word |= u32::from(b) << (j * 8);
}
w[FOG_HEADER_WORDS + i] = word;
}
w
}
#[must_use]
pub fn disabled_fog_mask() -> Vec<u32> {
vec![0u32; 4]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_layout() {
let w = pack_fog_mask(
3,
[-128, 256],
2,
1,
&[[10, 20], [30, 40]],
1,
0.5,
0.25,
&[0xC1, 0x02, 0x83, 0x00],
);
assert_eq!(w[0], 1);
assert_eq!(w[1], 3);
assert_eq!(w[2], 2);
assert_eq!(w[3] as i32, -128);
assert_eq!(w[4], 256);
assert_eq!(w[5], 2);
assert_eq!(w[6], 1);
assert_eq!(f32::from_bits(w[7]), 0.5);
assert_eq!(f32::from_bits(w[8]), 0.25);
assert_eq!((w[9] as i32, w[10] as i32), (10, 20));
assert_eq!((w[11] as i32, w[12] as i32), (30, 40));
assert_eq!(w[FOG_ACTIVE_DECK], 1, "active deck");
assert_eq!(w[18], 0x0083_02C1);
assert_eq!(w.len(), 19);
}
#[test]
fn truncates_excess_decks() {
let decks = [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]; let w = pack_fog_mask(0, [0, 0], 1, 1, &decks, 0, 1.0, 0.0, &[0]);
assert_eq!(w[2], FOG_MAX_DECKS as u32, "deck count clamps to MAX");
}
#[test]
fn active_deck_clamps_to_deck_count() {
let w = pack_fog_mask(0, [0, 0], 1, 1, &[[0, 1], [2, 3]], 9, 1.0, 0.0, &[0]);
assert_eq!(w[FOG_ACTIVE_DECK], 1, "clamped to last valid deck");
}
}