use crate::core::hash::FastMap;
use crate::core::sync::RwLock;
use crate::core::entangled::EntangledHVec;
#[derive(Clone, Debug)]
pub struct Goal {
pub name: String,
pub description: String,
pub vector: EntangledHVec,
pub relevance: f64,
pub urgency: f64,
pub cost: f64,
pub active: bool,
}
impl Goal {
pub fn utility(&self) -> f64 {
self.relevance * self.urgency * (1.0 - self.cost.clamp(0.0, 0.99))
}
}
struct GoalStoreInner {
goals: Vec<Goal>,
by_name: FastMap<String, usize>,
}
pub struct GoalStore {
inner: RwLock<GoalStoreInner>,
}
impl Default for GoalStore {
fn default() -> Self {
Self::new()
}
}
impl GoalStore {
pub fn new() -> Self {
Self {
inner: RwLock::new(GoalStoreInner {
goals: Vec::new(),
by_name: FastMap::default(),
}),
}
}
pub fn add(&self, goal: Goal) -> usize {
let mut inner = self.inner.write();
let idx = inner.goals.len();
inner.by_name.insert(goal.name.clone(), idx);
inner.goals.push(goal);
idx
}
pub fn get(&self, name: &str) -> Option<Goal> {
let inner = self.inner.read();
let idx = *inner.by_name.get(name)?;
inner.goals.get(idx).cloned()
}
pub fn deactivate(&self, name: &str) -> bool {
let mut inner = self.inner.write();
if let Some(&idx) = inner.by_name.get(name) {
inner.goals[idx].active = false;
true
} else {
false
}
}
pub fn active_goals(&self) -> Vec<Goal> {
self.inner
.read()
.goals
.iter()
.filter(|g| g.active)
.cloned()
.collect()
}
pub fn prioritized(&self) -> Vec<Goal> {
let mut active = self.active_goals();
active.sort_by(|a, b| {
b.utility()
.partial_cmp(&a.utility())
.unwrap_or(std::cmp::Ordering::Equal)
});
active
}
pub fn goal_relevance(&self, vec: &EntangledHVec) -> f64 {
let inner = self.inner.read();
let best = inner
.goals
.iter()
.filter(|g| g.active)
.fold(None::<&Goal>, |best, goal| {
if best.is_none_or(|b| goal.utility() > b.utility()) {
Some(goal)
} else {
best
}
});
best.map_or(0.0, |goal| goal.vector.similarity(vec))
}
pub fn count(&self) -> usize {
self.inner.read().goals.len()
}
pub fn active_count(&self) -> usize {
self.inner.read().goals.iter().filter(|g| g.active).count()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_goal(name: &str, seed: u64, relevance: f64, urgency: f64, cost: f64) -> Goal {
Goal {
name: name.to_string(),
description: format!("Goal: {}", name),
vector: EntangledHVec::new_deterministic(16384, seed),
relevance,
urgency,
cost,
active: true,
}
}
#[test]
fn relevance_preserves_first_tie_and_ignores_inactive_goals() {
let store = GoalStore::new();
let vector = EntangledHVec::new_deterministic(16384, 1);
assert_eq!(store.goal_relevance(&vector), 0.0);
for (name, seed, relevance) in [
("low", 2, 0.1),
("first", 1, 0.9),
("tie", 3, 0.9),
("inactive", 4, 1.0),
] {
store.add(make_goal(name, seed, relevance, 1.0, 0.0));
}
store.deactivate("inactive");
assert_eq!(store.goal_relevance(&vector), 1.0);
store.deactivate("first");
assert_eq!(
store.goal_relevance(&vector),
store.prioritized()[0].vector.similarity(&vector)
);
}
#[test]
fn test_utility_scoring() {
let g = make_goal("test", 1, 0.8, 0.9, 0.2);
let u = g.utility();
let expected = 0.8 * 0.9 * (1.0 - 0.2);
assert!((u - expected).abs() < 0.001);
}
#[test]
fn test_utility_clamps_cost() {
let g = make_goal("test", 1, 1.0, 1.0, 1.5);
assert!(g.utility() > 0.0, "cost should be clamped to 0.99");
}
#[test]
fn test_add_get() {
let store = GoalStore::new();
store.add(make_goal("learn_rust", 1, 0.9, 0.8, 0.3));
let g = store.get("learn_rust").unwrap();
assert_eq!(g.name, "learn_rust");
assert!(g.active);
}
#[test]
fn test_deactivate() {
let store = GoalStore::new();
store.add(make_goal("g1", 1, 1.0, 1.0, 0.0));
assert_eq!(store.active_count(), 1);
store.deactivate("g1");
assert_eq!(store.active_count(), 0);
}
#[test]
fn test_prioritized_ordering() {
let store = GoalStore::new();
store.add(make_goal("low", 1, 0.1, 0.1, 0.0));
store.add(make_goal("high", 2, 1.0, 1.0, 0.0));
store.add(make_goal("mid", 3, 0.5, 0.5, 0.0));
let pri = store.prioritized();
assert_eq!(pri[0].name, "high");
assert_eq!(pri[1].name, "mid");
assert_eq!(pri[2].name, "low");
}
#[test]
fn test_goal_relevance() {
let store = GoalStore::new();
let g = make_goal("g1", 42, 1.0, 1.0, 0.0);
let vec = g.vector.clone();
store.add(g);
let rel = store.goal_relevance(&vec);
assert!(
(rel - 1.0).abs() < 0.001,
"Same vector should have relevance ~1.0"
);
}
#[test]
fn test_empty_store() {
let store = GoalStore::new();
assert_eq!(store.count(), 0);
assert_eq!(store.active_count(), 0);
assert!(store.get("x").is_none());
let v = EntangledHVec::new_deterministic(16384, 1);
assert_eq!(store.goal_relevance(&v), 0.0);
}
}