From dfb3c52b0101bb6ec20778b7aa5b2deee27eebb7 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 16 Sep 2026 15:53:04 +0200 Subject: [PATCH] Guard the lens draw's divisor directly (L1 c1.s1.f2) The draw checked `draws()` over the map's values and then divided by a sum over `Lens::ALL`. Those were two computations that happened to agree: weakening the guard alone reached a modulo by zero. The draw now sums the total first and refuses a zero total, as `select_band` does; `draws()` stays the admission rule and no longer guards the draw. Builds. Co-Authored-By: Claude Opus 5 --- crates/ghostlight/src/lens.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/ghostlight/src/lens.rs b/crates/ghostlight/src/lens.rs index 610f73c..ae2ae48 100644 --- a/crates/ghostlight/src/lens.rs +++ b/crates/ghostlight/src/lens.rs @@ -186,7 +186,13 @@ pub(crate) fn draw( world_id: WorldId, command_id: CommandId, ) -> Result { - if !weights.draws() { + // The guard is on the divisor itself, as `select_band`'s is: no other + // statement of "these weights draw" can let the modulo below divide by zero. + let total: u128 = Lens::ALL + .iter() + .map(|lens| u128::from(weights.get(*lens))) + .sum(); + if total == 0 { return Err(KernelError::Invariant("lens weights never draw".into())); } let preimage = digest(&LensPreimage { @@ -198,10 +204,6 @@ pub(crate) fn draw( .and_then(|hex| hex.get(..16)) .and_then(|head| u64::from_str_radix(head, 16).ok()) .ok_or_else(|| KernelError::Invariant("lens draw digest is not hex".into()))?; - let total: u128 = Lens::ALL - .iter() - .map(|lens| u128::from(weights.get(*lens))) - .sum(); let mut position = u128::from(head) % total; for lens in Lens::ALL { let weight = u128::from(weights.get(lens));