use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::state::{CognitiveEdgeKind, NodeId, NodeKind};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PerspectiveId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum PerspectiveType {
Domain,
Emotional,
Temporal,
Social,
TaskMode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum TemporalFocus {
Immediate,
ShortTerm,
LongTerm,
Reflective,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CognitiveStyle {
pub exploration_vs_exploitation: f64,
pub risk_tolerance: f64,
pub abstraction_level: f64,
pub social_weight: f64,
pub temporal_focus: TemporalFocus,
}
impl Default for CognitiveStyle {
fn default() -> Self {
Self {
exploration_vs_exploitation: 0.5,
risk_tolerance: 0.5,
abstraction_level: 0.5,
social_weight: 0.5,
temporal_focus: TemporalFocus::ShortTerm,
}
}
}
impl CognitiveStyle {
pub fn blend(&self, other: &Self, other_weight: f64) -> Self {
let w = other_weight.clamp(0.0, 1.0);
let self_w = 1.0 - w;
Self {
exploration_vs_exploitation: self.exploration_vs_exploitation * self_w
+ other.exploration_vs_exploitation * w,
risk_tolerance: self.risk_tolerance * self_w + other.risk_tolerance * w,
abstraction_level: self.abstraction_level * self_w + other.abstraction_level * w,
social_weight: self.social_weight * self_w + other.social_weight * w,
temporal_focus: if w > 0.5 {
other.temporal_focus
} else {
self.temporal_focus
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SalienceTarget {
Node(NodeId),
Kind(NodeKind),
Domain(String),
Tag(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SalienceOverride {
pub target: SalienceTarget,
pub multiplier: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EdgeWeightModifier {
pub edge_kind: CognitiveEdgeKind,
pub multiplier: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ActivationCondition {
TimeWindow { start_hour: u8, end_hour: u8 },
AppContext { app_id: String },
GoalActive(NodeId),
StressAbove { threshold: f64 },
ExplicitRequest,
PatternDetected { pattern: String },
}
impl ActivationCondition {
pub fn is_met(&self, ctx: &ActivationContext) -> bool {
match self {
Self::TimeWindow {
start_hour,
end_hour,
} => {
if start_hour <= end_hour {
ctx.hour >= *start_hour && ctx.hour < *end_hour
} else {
ctx.hour >= *start_hour || ctx.hour < *end_hour
}
}
Self::AppContext { app_id } => ctx.active_apps.contains(app_id),
Self::GoalActive(goal_id) => ctx.active_goals.contains(goal_id),
Self::StressAbove { threshold } => ctx.stress_level >= *threshold,
Self::ExplicitRequest => false, Self::PatternDetected { pattern } => ctx.detected_patterns.contains(pattern),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ActivationContext {
pub hour: u8,
pub active_apps: Vec<String>,
pub active_goals: Vec<NodeId>,
pub stress_level: f64,
pub detected_patterns: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Perspective {
pub id: PerspectiveId,
pub name: String,
pub perspective_type: PerspectiveType,
pub salience_overrides: Vec<SalienceOverride>,
pub edge_modifiers: Vec<EdgeWeightModifier>,
pub active_goals: Vec<NodeId>,
pub suppressed_goals: Vec<NodeId>,
pub cognitive_style: CognitiveStyle,
pub activation_conditions: Vec<ActivationCondition>,
pub created_at: u64,
pub usage_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerspectiveStack {
pub stack: Vec<PerspectiveId>,
pub resolved_style: CognitiveStyle,
}
impl PerspectiveStack {
pub fn new() -> Self {
Self {
stack: Vec::new(),
resolved_style: CognitiveStyle::default(),
}
}
pub fn depth(&self) -> usize {
self.stack.len()
}
pub fn is_active(&self, id: PerspectiveId) -> bool {
self.stack.contains(&id)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerspectiveStore {
pub perspectives: Vec<Perspective>,
pub learned_perspectives: Vec<PerspectiveId>,
next_id: u64,
}
impl PerspectiveStore {
pub fn new() -> Self {
Self {
perspectives: Vec::new(),
learned_perspectives: Vec::new(),
next_id: 1,
}
}
pub fn alloc_id(&mut self) -> PerspectiveId {
let id = PerspectiveId(self.next_id);
self.next_id += 1;
id
}
pub fn insert(&mut self, perspective: Perspective) {
self.perspectives.push(perspective);
}
pub fn get(&self, id: PerspectiveId) -> Option<&Perspective> {
self.perspectives.iter().find(|p| p.id == id)
}
pub fn find_by_name(&self, name: &str) -> Option<&Perspective> {
self.perspectives.iter().find(|p| p.name == name)
}
pub fn len(&self) -> usize {
self.perspectives.len()
}
pub fn is_empty(&self) -> bool {
self.perspectives.is_empty()
}
}
pub fn activate_perspective(
stack: &mut PerspectiveStack,
id: PerspectiveId,
store: &PerspectiveStore,
) {
if stack.is_active(id) {
return; }
stack.stack.push(id);
stack.resolved_style = resolve_cognitive_style(stack, store);
}
pub fn deactivate_perspective(
stack: &mut PerspectiveStack,
id: PerspectiveId,
store: &PerspectiveStore,
) {
stack.stack.retain(|&pid| pid != id);
stack.resolved_style = resolve_cognitive_style(stack, store);
}
pub fn resolve_salience(
stack: &PerspectiveStack,
node_id: NodeId,
node_kind: NodeKind,
node_domain: Option<&str>,
node_tags: &[String],
base_salience: f64,
store: &PerspectiveStore,
) -> f64 {
let mut multiplier = 1.0;
for &pid in &stack.stack {
if let Some(perspective) = store.get(pid) {
for ovr in &perspective.salience_overrides {
let matches = match &ovr.target {
SalienceTarget::Node(n) => *n == node_id,
SalienceTarget::Kind(k) => *k == node_kind,
SalienceTarget::Domain(d) => {
node_domain.map(|nd| nd == d).unwrap_or(false)
}
SalienceTarget::Tag(t) => node_tags.contains(t),
};
if matches {
multiplier *= ovr.multiplier;
}
}
if node_kind == NodeKind::Goal {
if perspective.active_goals.contains(&node_id) {
multiplier *= 2.0;
}
if perspective.suppressed_goals.contains(&node_id) {
multiplier *= 0.1;
}
}
}
}
(base_salience * multiplier).clamp(0.0, 10.0)
}
pub fn resolve_edge_weight(
stack: &PerspectiveStack,
edge_kind: CognitiveEdgeKind,
base_weight: f64,
store: &PerspectiveStore,
) -> f64 {
let mut multiplier = 1.0;
for &pid in &stack.stack {
if let Some(perspective) = store.get(pid) {
for modifier in &perspective.edge_modifiers {
if modifier.edge_kind == edge_kind {
multiplier *= modifier.multiplier;
}
}
}
}
(base_weight * multiplier).clamp(-10.0, 10.0)
}
pub fn resolve_cognitive_style(
stack: &PerspectiveStack,
store: &PerspectiveStore,
) -> CognitiveStyle {
if stack.stack.is_empty() {
return CognitiveStyle::default();
}
let n = stack.stack.len();
let mut result = CognitiveStyle::default();
for (i, &pid) in stack.stack.iter().enumerate() {
if let Some(perspective) = store.get(pid) {
let weight = (i + 1) as f64 / n as f64;
result = result.blend(&perspective.cognitive_style, weight);
}
}
result
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerspectiveTransition {
pub activate: PerspectiveId,
pub deactivate: Option<PerspectiveId>,
pub reason: String,
pub confidence: f64,
}
pub fn detect_perspective_shift(
ctx: &ActivationContext,
stack: &PerspectiveStack,
store: &PerspectiveStore,
) -> Vec<PerspectiveTransition> {
let mut transitions = Vec::new();
for perspective in &store.perspectives {
let already_active = stack.is_active(perspective.id);
let met_conditions: Vec<&ActivationCondition> = perspective
.activation_conditions
.iter()
.filter(|c| c.is_met(ctx))
.collect();
if !met_conditions.is_empty() && !already_active {
let confidence = met_conditions.len() as f64
/ perspective.activation_conditions.len().max(1) as f64;
transitions.push(PerspectiveTransition {
activate: perspective.id,
deactivate: None,
reason: format!(
"Conditions met for '{}': {} of {} conditions satisfied.",
perspective.name,
met_conditions.len(),
perspective.activation_conditions.len()
),
confidence,
});
}
if already_active
&& !perspective.activation_conditions.is_empty()
&& met_conditions.is_empty()
{
transitions.push(PerspectiveTransition {
activate: perspective.id, deactivate: Some(perspective.id),
reason: format!(
"No activation conditions met for '{}' — consider deactivating.",
perspective.name
),
confidence: 0.5,
});
}
}
transitions.sort_by(|a, b| {
b.confidence
.partial_cmp(&a.confidence)
.unwrap_or(std::cmp::Ordering::Equal)
});
transitions
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerspectiveConflict {
pub perspective_a: PerspectiveId,
pub perspective_b: PerspectiveId,
pub conflict_type: ConflictType,
pub severity: f64,
pub description: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConflictType {
ExplorationConflict,
RiskConflict,
GoalConflict,
EdgeWeightConflict,
}
pub fn perspective_conflict_check(
stack: &PerspectiveStack,
store: &PerspectiveStore,
) -> Vec<PerspectiveConflict> {
let mut conflicts = Vec::new();
let active: Vec<&Perspective> = stack
.stack
.iter()
.filter_map(|&id| store.get(id))
.collect();
for i in 0..active.len() {
for j in (i + 1)..active.len() {
let a = active[i];
let b = active[j];
let explore_diff = (a.cognitive_style.exploration_vs_exploitation
- b.cognitive_style.exploration_vs_exploitation)
.abs();
if explore_diff > 0.5 {
conflicts.push(PerspectiveConflict {
perspective_a: a.id,
perspective_b: b.id,
conflict_type: ConflictType::ExplorationConflict,
severity: explore_diff,
description: format!(
"'{}' favors {} while '{}' favors {}.",
a.name,
if a.cognitive_style.exploration_vs_exploitation > 0.5 {
"exploration"
} else {
"exploitation"
},
b.name,
if b.cognitive_style.exploration_vs_exploitation > 0.5 {
"exploration"
} else {
"exploitation"
},
),
});
}
let risk_diff =
(a.cognitive_style.risk_tolerance - b.cognitive_style.risk_tolerance).abs();
if risk_diff > 0.5 {
conflicts.push(PerspectiveConflict {
perspective_a: a.id,
perspective_b: b.id,
conflict_type: ConflictType::RiskConflict,
severity: risk_diff,
description: format!(
"'{}' is {} while '{}' is {}.",
a.name,
if a.cognitive_style.risk_tolerance > 0.5 {
"risk-tolerant"
} else {
"risk-averse"
},
b.name,
if b.cognitive_style.risk_tolerance > 0.5 {
"risk-tolerant"
} else {
"risk-averse"
},
),
});
}
for &goal in &a.active_goals {
if b.suppressed_goals.contains(&goal) {
conflicts.push(PerspectiveConflict {
perspective_a: a.id,
perspective_b: b.id,
conflict_type: ConflictType::GoalConflict,
severity: 0.8,
description: format!(
"'{}' activates a goal that '{}' suppresses.",
a.name, b.name
),
});
break; }
}
for mod_a in &a.edge_modifiers {
for mod_b in &b.edge_modifiers {
if mod_a.edge_kind == mod_b.edge_kind {
let ratio = if mod_b.multiplier > 0.0 {
mod_a.multiplier / mod_b.multiplier
} else {
10.0
};
if ratio > 3.0 || ratio < 0.33 {
conflicts.push(PerspectiveConflict {
perspective_a: a.id,
perspective_b: b.id,
conflict_type: ConflictType::EdgeWeightConflict,
severity: (ratio.max(1.0 / ratio) - 1.0).min(1.0),
description: format!(
"'{}' and '{}' have opposing weights for {:?} edges.",
a.name, b.name, mod_a.edge_kind
),
});
break;
}
}
}
}
}
}
conflicts.sort_by(|a, b| {
b.severity
.partial_cmp(&a.severity)
.unwrap_or(std::cmp::Ordering::Equal)
});
conflicts
}
pub fn create_preset(name: &str, id: PerspectiveId, now_ms: u64) -> Option<Perspective> {
match name {
"creative" => Some(Perspective {
id,
name: "creative-brainstorm".to_string(),
perspective_type: PerspectiveType::TaskMode,
salience_overrides: vec![],
edge_modifiers: vec![
EdgeWeightModifier {
edge_kind: CognitiveEdgeKind::AssociatedWith,
multiplier: 2.0,
},
EdgeWeightModifier {
edge_kind: CognitiveEdgeKind::Contradicts,
multiplier: 0.3,
},
EdgeWeightModifier {
edge_kind: CognitiveEdgeKind::SimilarTo,
multiplier: 1.5,
},
],
active_goals: vec![],
suppressed_goals: vec![],
cognitive_style: CognitiveStyle {
exploration_vs_exploitation: 0.8,
risk_tolerance: 0.7,
abstraction_level: 0.6,
social_weight: 0.3,
temporal_focus: TemporalFocus::LongTerm,
},
activation_conditions: vec![],
created_at: now_ms,
usage_count: 0,
}),
"deadline" => Some(Perspective {
id,
name: "deadline-crunch".to_string(),
perspective_type: PerspectiveType::Temporal,
salience_overrides: vec![
SalienceOverride {
target: SalienceTarget::Kind(NodeKind::Task),
multiplier: 2.5,
},
],
edge_modifiers: vec![
EdgeWeightModifier {
edge_kind: CognitiveEdgeKind::Requires,
multiplier: 2.0,
},
EdgeWeightModifier {
edge_kind: CognitiveEdgeKind::AssociatedWith,
multiplier: 0.3,
},
],
active_goals: vec![],
suppressed_goals: vec![],
cognitive_style: CognitiveStyle {
exploration_vs_exploitation: 0.1,
risk_tolerance: 0.2,
abstraction_level: 0.2,
social_weight: 0.3,
temporal_focus: TemporalFocus::Immediate,
},
activation_conditions: vec![],
created_at: now_ms,
usage_count: 0,
}),
"reflective" => Some(Perspective {
id,
name: "reflective-review".to_string(),
perspective_type: PerspectiveType::TaskMode,
salience_overrides: vec![
SalienceOverride {
target: SalienceTarget::Kind(NodeKind::Episode),
multiplier: 2.0,
},
SalienceOverride {
target: SalienceTarget::Kind(NodeKind::Belief),
multiplier: 1.5,
},
],
edge_modifiers: vec![
EdgeWeightModifier {
edge_kind: CognitiveEdgeKind::Causes,
multiplier: 1.5,
},
],
active_goals: vec![],
suppressed_goals: vec![],
cognitive_style: CognitiveStyle {
exploration_vs_exploitation: 0.4,
risk_tolerance: 0.3,
abstraction_level: 0.7,
social_weight: 0.4,
temporal_focus: TemporalFocus::Reflective,
},
activation_conditions: vec![],
created_at: now_ms,
usage_count: 0,
}),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::{NodeId, NodeKind};
fn goal(seq: u32) -> NodeId {
NodeId::new(NodeKind::Goal, seq)
}
fn belief(seq: u32) -> NodeId {
NodeId::new(NodeKind::Belief, seq)
}
fn task(seq: u32) -> NodeId {
NodeId::new(NodeKind::Task, seq)
}
fn make_store_with_presets() -> PerspectiveStore {
let mut store = PerspectiveStore::new();
let creative_id = store.alloc_id();
let deadline_id = store.alloc_id();
let reflective_id = store.alloc_id();
store.insert(create_preset("creative", creative_id, 1000).unwrap());
store.insert(create_preset("deadline", deadline_id, 1000).unwrap());
store.insert(create_preset("reflective", reflective_id, 1000).unwrap());
store
}
fn make_custom_perspective(
id: PerspectiveId,
name: &str,
explore: f64,
risk: f64,
) -> Perspective {
Perspective {
id,
name: name.to_string(),
perspective_type: PerspectiveType::Domain,
salience_overrides: vec![],
edge_modifiers: vec![],
active_goals: vec![],
suppressed_goals: vec![],
cognitive_style: CognitiveStyle {
exploration_vs_exploitation: explore,
risk_tolerance: risk,
..CognitiveStyle::default()
},
activation_conditions: vec![],
created_at: 1000,
usage_count: 0,
}
}
#[test]
fn test_activate_deactivate() {
let store = make_store_with_presets();
let mut stack = PerspectiveStack::new();
activate_perspective(&mut stack, PerspectiveId(1), &store);
assert_eq!(stack.depth(), 1);
assert!(stack.is_active(PerspectiveId(1)));
deactivate_perspective(&mut stack, PerspectiveId(1), &store);
assert_eq!(stack.depth(), 0);
assert!(!stack.is_active(PerspectiveId(1)));
}
#[test]
fn test_no_double_activation() {
let store = make_store_with_presets();
let mut stack = PerspectiveStack::new();
activate_perspective(&mut stack, PerspectiveId(1), &store);
activate_perspective(&mut stack, PerspectiveId(1), &store);
assert_eq!(stack.depth(), 1); }
#[test]
fn test_multiple_perspectives() {
let store = make_store_with_presets();
let mut stack = PerspectiveStack::new();
activate_perspective(&mut stack, PerspectiveId(1), &store); activate_perspective(&mut stack, PerspectiveId(2), &store); assert_eq!(stack.depth(), 2);
}
#[test]
fn test_salience_boost() {
let store = make_store_with_presets();
let mut stack = PerspectiveStack::new();
activate_perspective(&mut stack, PerspectiveId(2), &store);
let salience = resolve_salience(
&stack,
task(1),
NodeKind::Task,
None,
&[],
0.5,
&store,
);
assert!(salience > 0.5);
}
#[test]
fn test_salience_dampen_domain() {
let mut store = PerspectiveStore::new();
let id = store.alloc_id();
let mut p = make_custom_perspective(id, "focus", 0.5, 0.5);
p.salience_overrides.push(SalienceOverride {
target: SalienceTarget::Domain("health".to_string()),
multiplier: 0.2,
});
store.insert(p);
let mut stack = PerspectiveStack::new();
activate_perspective(&mut stack, id, &store);
let salience = resolve_salience(
&stack,
belief(1),
NodeKind::Belief,
Some("health"),
&[],
1.0,
&store,
);
assert!(salience < 0.5); }
#[test]
fn test_goal_boost_and_suppress() {
let mut store = PerspectiveStore::new();
let id = store.alloc_id();
let mut p = make_custom_perspective(id, "focus", 0.5, 0.5);
p.active_goals.push(goal(1));
p.suppressed_goals.push(goal(2));
store.insert(p);
let mut stack = PerspectiveStack::new();
activate_perspective(&mut stack, id, &store);
let boosted = resolve_salience(
&stack,
goal(1),
NodeKind::Goal,
None,
&[],
0.5,
&store,
);
let suppressed = resolve_salience(
&stack,
goal(2),
NodeKind::Goal,
None,
&[],
0.5,
&store,
);
assert!(boosted > 0.5);
assert!(suppressed < 0.5);
}
#[test]
fn test_edge_weight_creative_mode() {
let store = make_store_with_presets();
let mut stack = PerspectiveStack::new();
activate_perspective(&mut stack, PerspectiveId(1), &store);
let weight = resolve_edge_weight(
&stack,
CognitiveEdgeKind::AssociatedWith,
1.0,
&store,
);
assert!(weight > 1.0);
let weight = resolve_edge_weight(
&stack,
CognitiveEdgeKind::Contradicts,
1.0,
&store,
);
assert!(weight < 1.0);
}
#[test]
fn test_style_blend() {
let base = CognitiveStyle {
exploration_vs_exploitation: 0.3,
risk_tolerance: 0.2,
..CognitiveStyle::default()
};
let overlay = CognitiveStyle {
exploration_vs_exploitation: 0.9,
risk_tolerance: 0.8,
..CognitiveStyle::default()
};
let blended = base.blend(&overlay, 0.7);
assert!(blended.exploration_vs_exploitation > 0.6);
assert!(blended.risk_tolerance > 0.5);
}
#[test]
fn test_resolved_style_from_stack() {
let store = make_store_with_presets();
let mut stack = PerspectiveStack::new();
activate_perspective(&mut stack, PerspectiveId(1), &store);
assert!(stack.resolved_style.exploration_vs_exploitation > 0.6);
activate_perspective(&mut stack, PerspectiveId(2), &store);
assert!(stack.resolved_style.exploration_vs_exploitation < 0.6);
}
#[test]
fn test_time_window_condition() {
let cond = ActivationCondition::TimeWindow {
start_hour: 9,
end_hour: 17,
};
let mut ctx = ActivationContext::default();
ctx.hour = 12;
assert!(cond.is_met(&ctx));
ctx.hour = 20;
assert!(!cond.is_met(&ctx));
}
#[test]
fn test_time_window_midnight_wrap() {
let cond = ActivationCondition::TimeWindow {
start_hour: 22,
end_hour: 6,
};
let mut ctx = ActivationContext::default();
ctx.hour = 23;
assert!(cond.is_met(&ctx));
ctx.hour = 3;
assert!(cond.is_met(&ctx));
ctx.hour = 12;
assert!(!cond.is_met(&ctx));
}
#[test]
fn test_goal_active_condition() {
let cond = ActivationCondition::GoalActive(goal(42));
let mut ctx = ActivationContext::default();
assert!(!cond.is_met(&ctx));
ctx.active_goals.push(goal(42));
assert!(cond.is_met(&ctx));
}
#[test]
fn test_detect_perspective_shift() {
let mut store = PerspectiveStore::new();
let id = store.alloc_id();
let mut p = make_custom_perspective(id, "morning-routine", 0.5, 0.5);
p.activation_conditions.push(ActivationCondition::TimeWindow {
start_hour: 6,
end_hour: 10,
});
store.insert(p);
let mut ctx = ActivationContext::default();
ctx.hour = 8;
let stack = PerspectiveStack::new();
let transitions = detect_perspective_shift(&ctx, &stack, &store);
assert_eq!(transitions.len(), 1);
assert_eq!(transitions[0].activate, id);
}
#[test]
fn test_exploration_risk_conflict() {
let mut store = PerspectiveStore::new();
let id_a = store.alloc_id();
let id_b = store.alloc_id();
store.insert(make_custom_perspective(id_a, "explore", 0.9, 0.9));
store.insert(make_custom_perspective(id_b, "cautious", 0.1, 0.1));
let mut stack = PerspectiveStack::new();
activate_perspective(&mut stack, id_a, &store);
activate_perspective(&mut stack, id_b, &store);
let conflicts = perspective_conflict_check(&stack, &store);
assert!(conflicts.len() >= 2); }
#[test]
fn test_goal_conflict() {
let mut store = PerspectiveStore::new();
let id_a = store.alloc_id();
let id_b = store.alloc_id();
let mut pa = make_custom_perspective(id_a, "focus-a", 0.5, 0.5);
pa.active_goals.push(goal(1));
let mut pb = make_custom_perspective(id_b, "focus-b", 0.5, 0.5);
pb.suppressed_goals.push(goal(1));
store.insert(pa);
store.insert(pb);
let mut stack = PerspectiveStack::new();
activate_perspective(&mut stack, id_a, &store);
activate_perspective(&mut stack, id_b, &store);
let conflicts = perspective_conflict_check(&stack, &store);
let goal_conflicts: Vec<_> = conflicts
.iter()
.filter(|c| c.conflict_type == ConflictType::GoalConflict)
.collect();
assert!(!goal_conflicts.is_empty());
}
#[test]
fn test_preset_creation() {
let p = create_preset("creative", PerspectiveId(1), 1000);
assert!(p.is_some());
let p = p.unwrap();
assert!(p.cognitive_style.exploration_vs_exploitation > 0.7);
let p = create_preset("deadline", PerspectiveId(2), 1000);
assert!(p.is_some());
let p = p.unwrap();
assert!(p.cognitive_style.exploration_vs_exploitation < 0.3);
assert!(create_preset("nonexistent", PerspectiveId(3), 1000).is_none());
}
#[test]
fn test_edge_weight_conflict() {
let mut store = PerspectiveStore::new();
let id_a = store.alloc_id();
let id_b = store.alloc_id();
let mut pa = make_custom_perspective(id_a, "boost-assoc", 0.5, 0.5);
pa.edge_modifiers.push(EdgeWeightModifier {
edge_kind: CognitiveEdgeKind::AssociatedWith,
multiplier: 5.0,
});
let mut pb = make_custom_perspective(id_b, "dampen-assoc", 0.5, 0.5);
pb.edge_modifiers.push(EdgeWeightModifier {
edge_kind: CognitiveEdgeKind::AssociatedWith,
multiplier: 0.1,
});
store.insert(pa);
store.insert(pb);
let mut stack = PerspectiveStack::new();
activate_perspective(&mut stack, id_a, &store);
activate_perspective(&mut stack, id_b, &store);
let conflicts = perspective_conflict_check(&stack, &store);
let edge_conflicts: Vec<_> = conflicts
.iter()
.filter(|c| c.conflict_type == ConflictType::EdgeWeightConflict)
.collect();
assert!(!edge_conflicts.is_empty());
}
}