#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Salience {
pub recency: f32,
pub recurrence: f32,
pub reusability: f32,
pub validation: f32,
pub consequence: f32,
pub emotional_charge: f32,
pub identity_relevance: f32,
pub contradiction_penalty: f32,
pub use_count: u32,
}
impl Default for Salience {
fn default() -> Self {
Self {
recency: 0.0,
recurrence: 0.0,
reusability: 0.0,
validation: 0.0,
consequence: 0.0,
emotional_charge: 0.0,
identity_relevance: 0.0,
contradiction_penalty: 0.0,
use_count: 0,
}
}
}
#[must_use]
pub fn brightness(salience: &Salience) -> f32 {
let recurrence = bounded(salience.recurrence);
let validation = bounded(salience.validation);
let unvalidated_use_penalty = if validation <= f32::EPSILON && salience.use_count > 5 {
((salience.use_count - 5) as f32 * 0.01).min(0.10)
} else {
0.0
};
let score = 0.10
+ 0.08 * bounded(salience.recency)
+ 0.14 * bounded(salience.reusability)
+ 0.30 * validation
+ 0.14 * bounded(salience.consequence)
+ 0.06 * bounded(salience.emotional_charge)
+ 0.12 * bounded(salience.identity_relevance)
+ 0.06 * recurrence * validation
- 0.35 * bounded(salience.contradiction_penalty)
- unvalidated_use_penalty;
bounded(score)
}
fn bounded(value: f32) -> f32 {
if value.is_nan() {
return 0.0;
}
value.clamp(0.0, 1.0)
}
#[cfg(test)]
mod tests {
use super::*;
fn baseline() -> Salience {
Salience {
recency: 0.4,
recurrence: 0.5,
reusability: 0.6,
validation: 0.0,
consequence: 0.4,
emotional_charge: 0.2,
identity_relevance: 0.3,
contradiction_penalty: 0.0,
use_count: 0,
}
}
#[test]
fn brightness_is_monotonic_in_validation() {
let mut previous = 0.0;
for step in 0..=20 {
let mut salience = baseline();
salience.validation = step as f32 / 20.0;
let current = brightness(&salience);
assert!(
current >= previous,
"brightness decreased from {previous} to {current} at validation step {step}"
);
previous = current;
}
}
#[test]
fn brightness_decreases_with_contradiction_penalty() {
let mut previous = 1.0;
for step in 0..=20 {
let mut salience = baseline();
salience.validation = 0.8;
salience.contradiction_penalty = step as f32 / 20.0;
let current = brightness(&salience);
assert!(
current <= previous,
"brightness increased from {previous} to {current} at penalty step {step}"
);
previous = current;
}
}
#[test]
fn repeated_use_without_validation_is_not_positive_uplift() {
let mut low_use = baseline();
low_use.use_count = 5;
let mut repeated = low_use;
repeated.use_count = 20;
assert!(brightness(&repeated) < brightness(&low_use));
}
#[test]
fn brightness_is_bounded_and_nan_safe() {
let salience = Salience {
recency: f32::NAN,
recurrence: 99.0,
reusability: 99.0,
validation: 99.0,
consequence: 99.0,
emotional_charge: 99.0,
identity_relevance: 99.0,
contradiction_penalty: -99.0,
use_count: 0,
};
let score = brightness(&salience);
assert!((0.0..=1.0).contains(&score));
}
}