///|
fn check_survival_threshold(min_survival : Double) -> Unit raise BeliefError {
  if !finite(min_survival) || min_survival < 0.0 || min_survival >= 1.0 {
    raise Invalid("minimum survival must be finite and in [0,1)")
  }
}

///|
/// Removes explicit conflict by conditioning on nonempty mass.
/// Summing surviving focals directly avoids catastrophic 1-conflict cancellation.
pub fn Mass::normalize(
  self : Mass,
  min_survival~ : Double = 1.0e-12,
  budget~ : Limits = default_limits(),
) -> Mass raise BeliefError {
  check_survival_threshold(min_survival)
  budget.check_dense(self.domain)
  let surviving = self.survival()
  if surviving <= min_survival {
    raise TotalConflict
  }
  let bins = Array::make(self.domain.universe() + 1, 0.0)
  for (mask, value) in self.focals {
    if mask != 0 {
      bins[mask] = value / surviving
    }
  }
  from_bins(self.domain, bins, budget)
}

///|
/// Dempster's normalized conjunctive rule, with an explicit instability gate.
pub fn dempster(
  a : Mass,
  b : Mass,
  min_survival~ : Double = 1.0e-12,
  budget~ : Limits = default_limits(),
) -> Mass raise BeliefError {
  check_survival_threshold(min_survival)
  conjunctive(a, b, budget~).normalize(min_survival~, budget~)
}