use zzstat::source::ConstantSource;
use zzstat::transform::{
AdditiveTransform, ClampTransform, MultiplicativeTransform, ScalingTransform, StackRule,
TransformPhase,
};
use zzstat::*;
#[test]
fn test_complete_pipeline() {
let mut resolver = StatResolver::new();
let str_id = StatId::from("STR");
let dex_id = StatId::from("DEX");
let atk_id = StatId::from("ATK");
let crit_id = StatId::from("CRIT");
let dps_id = StatId::from("DPS");
resolver.register_source(str_id.clone(), Box::new(ConstantSource(10.0)));
resolver.register_source(dex_id.clone(), Box::new(ConstantSource(15.0)));
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(50.0)));
resolver.register_transform(
atk_id.clone(),
Box::new(ScalingTransform::new(str_id.clone(), 2.0)),
);
resolver.register_source(crit_id.clone(), Box::new(ConstantSource(5.0)));
resolver.register_transform(
crit_id.clone(),
Box::new(ScalingTransform::new(dex_id.clone(), 1.5)),
);
resolver.register_source(dps_id.clone(), Box::new(ConstantSource(0.0)));
resolver.register_transform(
dps_id.clone(),
Box::new(ScalingTransform::new(atk_id.clone(), 1.0)),
);
resolver.register_transform(
dps_id.clone(),
Box::new(ScalingTransform::new(crit_id.clone(), 0.1)),
);
let context = StatContext::new();
let results = resolver.resolve_all(&context).unwrap();
let str_resolved = results.get(&str_id).unwrap();
assert_eq!(str_resolved.value.to_f64(), 10.0);
let dex_resolved = results.get(&dex_id).unwrap();
assert_eq!(dex_resolved.value.to_f64(), 15.0);
let atk_resolved = results.get(&atk_id).unwrap();
assert_eq!(atk_resolved.value.to_f64(), 70.0);
let crit_resolved = results.get(&crit_id).unwrap();
assert_eq!(crit_resolved.value.to_f64(), 27.5);
let dps_resolved = results.get(&dps_id).unwrap();
assert_eq!(dps_resolved.value.to_f64(), 72.75);
}
#[test]
fn test_additive_sources() {
let mut resolver = StatResolver::new();
let hp_id = StatId::from("HP");
resolver.register_source(hp_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_source(hp_id.clone(), Box::new(ConstantSource(50.0)));
resolver.register_source(hp_id.clone(), Box::new(ConstantSource(25.0)));
let context = StatContext::new();
let resolved = resolver.resolve(&hp_id, &context).unwrap();
assert_eq!(resolved.value.to_f64(), 175.0); assert_eq!(resolved.sources.len(), 3);
}
#[test]
fn test_transform_chain() {
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_transform(atk_id.clone(), Box::new(MultiplicativeTransform::new(1.5)));
resolver.register_transform(atk_id.clone(), Box::new(MultiplicativeTransform::new(1.2)));
resolver.register_transform(atk_id.clone(), Box::new(ClampTransform::new(0.0, 200.0)));
let context = StatContext::new();
let resolved = resolver.resolve(&atk_id, &context).unwrap();
assert!((resolved.value.to_f64() - 180.0).abs() < 0.01);
assert_eq!(resolved.transforms.len(), 2);
}
#[test]
fn test_cache_behavior() {
let mut resolver = StatResolver::new();
let hp_id = StatId::from("HP");
resolver.register_source(hp_id.clone(), Box::new(ConstantSource(100.0)));
let context = StatContext::new();
let resolved1 = resolver.resolve(&hp_id, &context).unwrap();
assert_eq!(resolved1.value.to_f64(), 100.0);
let resolved2 = resolver.resolve(&hp_id, &context).unwrap();
assert_eq!(resolved2.value.to_f64(), 100.0);
resolver.register_source(hp_id.clone(), Box::new(ConstantSource(50.0)));
let resolved3 = resolver.resolve(&hp_id, &context).unwrap();
assert_eq!(resolved3.value.to_f64(), 150.0);
}
#[test]
fn test_complex_dependency_chain() {
let mut resolver = StatResolver::new();
let base_id = StatId::from("BASE");
let mid_id = StatId::from("MID");
let top_id = StatId::from("TOP");
resolver.register_source(base_id.clone(), Box::new(ConstantSource(10.0)));
resolver.register_source(mid_id.clone(), Box::new(ConstantSource(20.0)));
resolver.register_source(top_id.clone(), Box::new(ConstantSource(30.0)));
resolver.register_transform(
mid_id.clone(),
Box::new(ScalingTransform::new(base_id.clone(), 1.0)),
);
resolver.register_transform(
top_id.clone(),
Box::new(ScalingTransform::new(mid_id.clone(), 1.0)),
);
let context = StatContext::new();
let results = resolver.resolve_all(&context).unwrap();
assert_eq!(results.get(&base_id).unwrap().value.to_f64(), 10.0);
assert_eq!(results.get(&mid_id).unwrap().value.to_f64(), 30.0);
assert_eq!(results.get(&top_id).unwrap().value.to_f64(), 60.0);
}
#[test]
fn test_breakdown_information() {
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(50.0)));
resolver.register_transform(atk_id.clone(), Box::new(MultiplicativeTransform::new(1.5)));
let context = StatContext::new();
let resolved = resolver.resolve(&atk_id, &context).unwrap();
assert_eq!(resolved.sources.len(), 2);
assert_eq!(resolved.transforms.len(), 1);
let breakdown = resolver.get_breakdown(&atk_id).unwrap();
assert_eq!(breakdown.value.to_f64(), 225.0); }
#[test]
fn test_resolver_fork() {
let mut base = StatResolver::new();
let hp_id = StatId::from("HP");
base.register_source(hp_id.clone(), Box::new(ConstantSource(100.0)));
let mut fork = base.fork();
fork.register_source(hp_id.clone(), Box::new(ConstantSource(50.0)));
let context = StatContext::new();
let base_resolved = base.resolve(&hp_id, &context).unwrap();
assert_eq!(base_resolved.value.to_f64(), 100.0);
let fork_resolved = fork.resolve(&hp_id, &context).unwrap();
assert_eq!(fork_resolved.value.to_f64(), 150.0);
}
#[test]
fn test_resolver_fork_with_transforms() {
let mut base = StatResolver::new();
let atk_id = StatId::from("ATK");
base.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
base.register_transform(atk_id.clone(), Box::new(MultiplicativeTransform::new(1.5)));
let mut fork = base.fork();
fork.register_transform(atk_id.clone(), Box::new(MultiplicativeTransform::new(1.2)));
let context = StatContext::new();
let base_resolved = base.resolve(&atk_id, &context).unwrap();
assert_eq!(base_resolved.value.to_f64(), 150.0);
let fork_resolved = fork.resolve(&atk_id, &context).unwrap();
assert!((fork_resolved.value.to_f64() - 180.0).abs() < 0.01);
}
#[test]
fn test_resolve_batch() {
let mut resolver = StatResolver::new();
let str_id = StatId::from("STR");
let atk_id = StatId::from("ATK");
let hp_id = StatId::from("HP");
let mp_id = StatId::from("MP");
resolver.register_source(str_id.clone(), Box::new(ConstantSource(10.0)));
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(50.0)));
resolver.register_source(hp_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_source(mp_id.clone(), Box::new(ConstantSource(50.0)));
resolver.register_transform(
atk_id.clone(),
Box::new(ScalingTransform::new(str_id.clone(), 2.0)),
);
let context = StatContext::new();
let results = resolver
.resolve_batch(&[atk_id.clone(), hp_id.clone()], &context)
.unwrap();
assert!(results.contains_key(&atk_id));
assert!(results.contains_key(&hp_id));
assert!(results.contains_key(&str_id));
assert!(!results.contains_key(&mp_id));
assert_eq!(results[&str_id].value.to_f64(), 10.0);
assert_eq!(results[&atk_id].value.to_f64(), 70.0); assert_eq!(results[&hp_id].value.to_f64(), 100.0);
}
#[test]
fn test_resolve_batch_empty() {
let mut resolver = StatResolver::new();
let context = StatContext::new();
let results = resolver.resolve_batch(&[], &context).unwrap();
assert!(results.is_empty());
}
#[test]
fn test_cache_invalidation() {
let mut resolver = StatResolver::new();
let hp_id = StatId::from("HP");
resolver.register_source(hp_id.clone(), Box::new(ConstantSource(100.0)));
let context = StatContext::new();
let resolved1 = resolver.resolve(&hp_id, &context).unwrap();
assert_eq!(resolved1.value.to_f64(), 100.0);
let resolved2 = resolver.resolve(&hp_id, &context).unwrap();
assert_eq!(resolved2.value.to_f64(), 100.0);
resolver.invalidate(&hp_id);
resolver.register_source(hp_id.clone(), Box::new(ConstantSource(50.0)));
let resolved3 = resolver.resolve(&hp_id, &context).unwrap();
assert_eq!(resolved3.value.to_f64(), 150.0);
}
#[test]
fn test_invalidate_all() {
let mut resolver = StatResolver::new();
let hp_id = StatId::from("HP");
let mp_id = StatId::from("MP");
resolver.register_source(hp_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_source(mp_id.clone(), Box::new(ConstantSource(50.0)));
let context = StatContext::new();
let _ = resolver.resolve_all(&context).unwrap();
assert!(resolver.get_breakdown(&hp_id).is_some());
assert!(resolver.get_breakdown(&mp_id).is_some());
resolver.invalidate_all();
assert!(resolver.get_breakdown(&hp_id).is_none());
assert!(resolver.get_breakdown(&mp_id).is_none());
}
#[test]
fn test_transform_phase_ordering() {
use zzstat::transform::{AdditiveTransform, ClampTransform};
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_transform(atk_id.clone(), Box::new(ClampTransform::new(0.0, 150.0)));
resolver.register_transform(atk_id.clone(), Box::new(MultiplicativeTransform::new(2.0)));
resolver.register_transform(atk_id.clone(), Box::new(AdditiveTransform::new(50.0)));
let context = StatContext::new();
let resolved = resolver.resolve(&atk_id, &context).unwrap();
assert_eq!(resolved.value.to_f64(), 150.0);
}
#[test]
fn test_custom_transform_phase() {
use zzstat::transform::{StatTransform, TransformPhase};
struct CustomPhaseTransform {
phase: TransformPhase,
value: f64,
}
impl StatTransform for CustomPhaseTransform {
fn depends_on(&self) -> Vec<StatId> {
Vec::new()
}
fn phase(&self) -> TransformPhase {
self.phase
}
fn apply(
&self,
input: StatValue,
_dependencies: &rustc_hash::FxHashMap<StatId, StatValue>,
_context: &StatContext,
) -> Result<StatValue, StatError> {
use zzstat::StatNumeric;
Ok(input + StatValue::from_f64(self.value))
}
fn description(&self) -> String {
format!("Custom phase +{}", self.value)
}
}
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_transform(
atk_id.clone(),
Box::new(CustomPhaseTransform {
phase: TransformPhase::Custom(10),
value: 50.0,
}),
);
resolver.register_transform(atk_id.clone(), Box::new(MultiplicativeTransform::new(2.0)));
let context = StatContext::new();
let resolved = resolver.resolve(&atk_id, &context).unwrap();
assert_eq!(resolved.value.to_f64(), 250.0);
}
#[test]
fn test_missing_dependency() {
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
let missing_id = StatId::from("MISSING");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_transform(
atk_id.clone(),
Box::new(ScalingTransform::new(missing_id.clone(), 1.0)),
);
let context = StatContext::new();
let result = resolver.resolve(&atk_id, &context);
assert!(result.is_ok());
let resolved = result.unwrap();
assert_eq!(resolved.value.to_f64(), 100.0); }
#[test]
fn test_missing_source() {
let mut resolver = StatResolver::new();
let hp_id = StatId::from("HP");
let context = StatContext::new();
let result = resolver.resolve(&hp_id, &context);
assert!(result.is_err());
if let Err(StatError::MissingSource(id)) = result {
assert_eq!(id, hp_id);
} else {
panic!("Expected MissingSource error");
}
}
#[test]
fn test_buff_debuff_interaction() {
let mut resolver = StatResolver::new();
let def_id = StatId::from("DEF");
resolver.register_source(def_id.clone(), Box::new(ConstantSource(200.0)));
resolver.register_transform_with_rule(
def_id.clone(),
TransformPhase::Multiplicative,
StackRule::Multiplicative,
Box::new(MultiplicativeTransform::new(1.5)),
);
resolver.register_transform_with_rule(
def_id.clone(),
TransformPhase::Multiplicative,
StackRule::Multiplicative,
Box::new(MultiplicativeTransform::new(0.7)),
);
resolver.register_transform_with_rule(
def_id.clone(),
TransformPhase::Additive,
StackRule::Additive,
Box::new(AdditiveTransform::new(50.0)),
);
let context = StatContext::new();
let resolved = resolver.resolve(&def_id, &context).unwrap();
assert!((resolved.value.to_f64() - 262.5).abs() < 1e-9);
}
#[test]
fn test_conditional_buff_scenario() {
use zzstat::transform::ConditionalTransform;
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
let enrage_buff = ConditionalTransform::new(
|ctx| ctx.get::<bool>("is_enraged").unwrap_or(false),
Box::new(MultiplicativeTransform::new(2.0)),
"enrage damage double",
);
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Multiplicative,
StackRule::Multiplicative,
Box::new(enrage_buff),
);
let mut context_normal = StatContext::new();
context_normal.set("is_enraged", false);
let mut context_enraged = StatContext::new();
context_enraged.set("is_enraged", true);
let resolved_normal = resolver.resolve(&atk_id, &context_normal).unwrap();
resolver.invalidate_all();
let resolved_enraged = resolver.resolve(&atk_id, &context_enraged).unwrap();
assert_eq!(resolved_normal.value.to_f64(), 100.0);
assert_eq!(resolved_enraged.value.to_f64(), 200.0);
}
#[test]
fn test_stat_with_only_transforms() {
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_transform(atk_id.clone(), Box::new(AdditiveTransform::new(100.0)));
let context = StatContext::new();
let resolved = resolver.resolve(&atk_id, &context).unwrap();
assert_eq!(resolved.value.to_f64(), 100.0);
}
#[test]
fn test_multiple_forks() {
let mut base = StatResolver::new();
let hp_id = StatId::from("HP");
base.register_source(hp_id.clone(), Box::new(ConstantSource(100.0)));
let mut fork1 = base.fork();
let mut fork2 = base.fork();
fork1.register_source(hp_id.clone(), Box::new(ConstantSource(10.0)));
fork2.register_source(hp_id.clone(), Box::new(ConstantSource(20.0)));
let context = StatContext::new();
let base_resolved = base.resolve(&hp_id, &context).unwrap();
let fork1_resolved = fork1.resolve(&hp_id, &context).unwrap();
let fork2_resolved = fork2.resolve(&hp_id, &context).unwrap();
assert_eq!(base_resolved.value.to_f64(), 100.0);
assert_eq!(fork1_resolved.value.to_f64(), 110.0);
assert_eq!(fork2_resolved.value.to_f64(), 120.0);
}
#[test]
fn test_additive_stacking() {
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Additive,
StackRule::Additive,
Box::new(AdditiveTransform::new(50.0)),
);
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Additive,
StackRule::Additive,
Box::new(AdditiveTransform::new(30.0)),
);
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Additive,
StackRule::Additive,
Box::new(AdditiveTransform::new(20.0)),
);
let context = StatContext::new();
let resolved = resolver.resolve(&atk_id, &context).unwrap();
assert_eq!(resolved.value.to_f64(), 200.0);
}
#[test]
fn test_multiplicative_stacking() {
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Multiplicative,
StackRule::Multiplicative,
Box::new(MultiplicativeTransform::new(1.5)),
);
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Multiplicative,
StackRule::Multiplicative,
Box::new(MultiplicativeTransform::new(1.2)),
);
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Multiplicative,
StackRule::Multiplicative,
Box::new(MultiplicativeTransform::new(1.1)),
);
let context = StatContext::new();
let resolved = resolver.resolve(&atk_id, &context).unwrap();
assert!((resolved.value.to_f64() - 198.0).abs() < 0.01);
}
#[test]
fn test_additive_multiplicative_combination() {
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Additive,
StackRule::Additive,
Box::new(AdditiveTransform::new(50.0)),
);
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Additive,
StackRule::Additive,
Box::new(AdditiveTransform::new(30.0)),
);
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Multiplicative,
StackRule::Multiplicative,
Box::new(MultiplicativeTransform::new(1.5)),
);
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Multiplicative,
StackRule::Multiplicative,
Box::new(MultiplicativeTransform::new(1.2)),
);
let context = StatContext::new();
let resolved = resolver.resolve(&atk_id, &context).unwrap();
assert!((resolved.value.to_f64() - 324.0).abs() < 0.01);
}
#[test]
fn test_override_precedence() {
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Final,
StackRule::Override,
Box::new(AdditiveTransform::new(200.0)), );
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Final,
StackRule::Override,
Box::new(AdditiveTransform::new(300.0)), );
let context = StatContext::new();
let resolved = resolver.resolve(&atk_id, &context).unwrap();
assert_eq!(resolved.value.to_f64(), 400.0);
}
#[test]
fn test_diminishing_returns() {
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
}
#[test]
fn test_min_max_clamping() {
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Final,
StackRule::Max,
Box::new(ClampTransform::new(0.0, 150.0)),
);
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Final,
StackRule::Max,
Box::new(ClampTransform::new(0.0, 120.0)), );
let context = StatContext::new();
let resolved = resolver.resolve(&atk_id, &context).unwrap();
assert_eq!(resolved.value.to_f64(), 100.0);
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(200.0)));
resolver.invalidate(&atk_id);
let resolved2 = resolver.resolve(&atk_id, &context).unwrap();
assert_eq!(resolved2.value.to_f64(), 120.0);
}
#[test]
fn test_min_clamping() {
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(50.0)));
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Final,
StackRule::Min,
Box::new(ClampTransform::new(10.0, 1000.0)),
);
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Final,
StackRule::Min,
Box::new(ClampTransform::new(30.0, 1000.0)), );
let context = StatContext::new();
let resolved = resolver.resolve(&atk_id, &context).unwrap();
assert_eq!(resolved.value.to_f64(), 50.0);
let mut resolver2 = StatResolver::new();
resolver2.register_source(atk_id.clone(), Box::new(ConstantSource(20.0)));
resolver2.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Final,
StackRule::Min,
Box::new(ClampTransform::new(10.0, 1000.0)),
);
resolver2.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Final,
StackRule::Min,
Box::new(ClampTransform::new(30.0, 1000.0)), );
let resolved2 = resolver2.resolve(&atk_id, &context).unwrap();
assert_eq!(resolved2.value.to_f64(), 30.0);
}
#[test]
fn test_additive_stacking_zero_base() {
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(0.0)));
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Additive,
StackRule::Additive,
Box::new(AdditiveTransform::new(10.0)),
);
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Additive,
StackRule::Additive,
Box::new(AdditiveTransform::new(20.0)),
);
let context = StatContext::new();
let resolved = resolver.resolve(&atk_id, &context).unwrap();
assert_eq!(resolved.value.to_f64(), 30.0);
}
#[test]
fn test_additive_stacking_negative() {
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Additive,
StackRule::Additive,
Box::new(AdditiveTransform::new(50.0)),
);
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Additive,
StackRule::Additive,
Box::new(AdditiveTransform::new(-30.0)), );
let context = StatContext::new();
let resolved = resolver.resolve(&atk_id, &context).unwrap();
assert_eq!(resolved.value.to_f64(), 120.0);
}
#[test]
fn test_minmax_stack_rule() {
use zzstat::numeric::StatValue;
use zzstat::transform::StackRule;
let mut resolver = StatResolver::new();
let crit_id = StatId::from("CRIT");
resolver.register_source(crit_id.clone(), Box::new(ConstantSource(0.9)));
resolver.register_transform_with_rule(
crit_id.clone(),
TransformPhase::Final,
StackRule::MinMax,
Box::new(ClampTransform::with_bounds(
Some(StatValue::from_f64(0.0)),
Some(StatValue::from_f64(0.75)), )),
);
resolver.register_transform_with_rule(
crit_id.clone(),
TransformPhase::Final,
StackRule::MinMax,
Box::new(ClampTransform::with_bounds(
Some(StatValue::from_f64(0.0)),
Some(StatValue::from_f64(0.80)), )),
);
let context = StatContext::new();
let resolved = resolver.resolve(&crit_id, &context).unwrap();
assert_eq!(resolved.value.to_f64(), 0.75);
}
#[test]
fn test_minmax_stack_rule_mixed_bounds() {
use zzstat::numeric::StatValue;
use zzstat::transform::StackRule;
let mut resolver = StatResolver::new();
let move_speed_id = StatId::from("MOVE_SPEED");
resolver.register_source(move_speed_id.clone(), Box::new(ConstantSource(50.0)));
resolver.register_transform_with_rule(
move_speed_id.clone(),
TransformPhase::Final,
StackRule::MinMax,
Box::new(ClampTransform::with_min(StatValue::from_f64(100.0))), );
resolver.register_transform_with_rule(
move_speed_id.clone(),
TransformPhase::Final,
StackRule::MinMax,
Box::new(ClampTransform::with_max(StatValue::from_f64(200.0))), );
let context = StatContext::new();
let resolved = resolver.resolve(&move_speed_id, &context).unwrap();
assert_eq!(resolved.value.to_f64(), 100.0);
resolver.register_source(move_speed_id.clone(), Box::new(ConstantSource(250.0)));
resolver.invalidate(&move_speed_id);
let resolved2 = resolver.resolve(&move_speed_id, &context).unwrap();
assert_eq!(resolved2.value.to_f64(), 200.0);
}
#[test]
fn test_clamp_with_additive() {
use zzstat::numeric::StatValue;
use zzstat::transform::StackRule;
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Additive,
StackRule::Additive,
Box::new(AdditiveTransform::new(50.0)),
);
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Final,
StackRule::MinMax,
Box::new(ClampTransform::with_bounds(
Some(StatValue::from_f64(0.0)),
Some(StatValue::from_f64(120.0)),
)),
);
let context = StatContext::new();
let resolved = resolver.resolve(&atk_id, &context).unwrap();
assert_eq!(resolved.value.to_f64(), 120.0);
}
#[test]
fn test_clamp_with_multiplicative() {
use zzstat::numeric::StatValue;
use zzstat::transform::StackRule;
let mut resolver = StatResolver::new();
let atk_id = StatId::from("ATK");
resolver.register_source(atk_id.clone(), Box::new(ConstantSource(100.0)));
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Multiplicative,
StackRule::Multiplicative,
Box::new(MultiplicativeTransform::new(2.0)),
);
resolver.register_transform_with_rule(
atk_id.clone(),
TransformPhase::Final,
StackRule::MinMax,
Box::new(ClampTransform::with_bounds(
Some(StatValue::from_f64(0.0)),
Some(StatValue::from_f64(150.0)),
)),
);
let context = StatContext::new();
let resolved = resolver.resolve(&atk_id, &context).unwrap();
assert_eq!(resolved.value.to_f64(), 150.0);
}
#[test]
fn test_multiple_clamps_minmax_composition() {
use zzstat::numeric::StatValue;
use zzstat::transform::StackRule;
let mut resolver = StatResolver::new();
let stat_id = StatId::from("STAT");
resolver.register_source(stat_id.clone(), Box::new(ConstantSource(150.0)));
resolver.register_transform_with_rule(
stat_id.clone(),
TransformPhase::Final,
StackRule::MinMax,
Box::new(ClampTransform::with_bounds(
Some(StatValue::from_f64(10.0)),
Some(StatValue::from_f64(200.0)),
)),
);
resolver.register_transform_with_rule(
stat_id.clone(),
TransformPhase::Final,
StackRule::MinMax,
Box::new(ClampTransform::with_bounds(
Some(StatValue::from_f64(30.0)), Some(StatValue::from_f64(120.0)), )),
);
let context = StatContext::new();
let resolved = resolver.resolve(&stat_id, &context).unwrap();
assert_eq!(resolved.value.to_f64(), 120.0);
let mut resolver2 = StatResolver::new();
resolver2.register_source(stat_id.clone(), Box::new(ConstantSource(20.0)));
resolver2.register_transform_with_rule(
stat_id.clone(),
TransformPhase::Final,
StackRule::MinMax,
Box::new(ClampTransform::with_bounds(
Some(StatValue::from_f64(10.0)),
Some(StatValue::from_f64(200.0)),
)),
);
resolver2.register_transform_with_rule(
stat_id.clone(),
TransformPhase::Final,
StackRule::MinMax,
Box::new(ClampTransform::with_bounds(
Some(StatValue::from_f64(30.0)), Some(StatValue::from_f64(120.0)), )),
);
let resolved2 = resolver2.resolve(&stat_id, &context).unwrap();
assert_eq!(resolved2.value.to_f64(), 30.0);
}
#[test]
fn test_clamp_deterministic_ordering() {
use zzstat::numeric::StatValue;
use zzstat::transform::StackRule;
let mut resolver1 = StatResolver::new();
let mut resolver2 = StatResolver::new();
let stat_id = StatId::from("STAT");
resolver1.register_source(stat_id.clone(), Box::new(ConstantSource(150.0)));
resolver2.register_source(stat_id.clone(), Box::new(ConstantSource(150.0)));
resolver1.register_transform_with_rule(
stat_id.clone(),
TransformPhase::Final,
StackRule::MinMax,
Box::new(ClampTransform::with_bounds(
Some(StatValue::from_f64(10.0)),
Some(StatValue::from_f64(200.0)),
)),
);
resolver1.register_transform_with_rule(
stat_id.clone(),
TransformPhase::Final,
StackRule::MinMax,
Box::new(ClampTransform::with_bounds(
Some(StatValue::from_f64(30.0)),
Some(StatValue::from_f64(120.0)),
)),
);
resolver2.register_transform_with_rule(
stat_id.clone(),
TransformPhase::Final,
StackRule::MinMax,
Box::new(ClampTransform::with_bounds(
Some(StatValue::from_f64(30.0)),
Some(StatValue::from_f64(120.0)),
)),
);
resolver2.register_transform_with_rule(
stat_id.clone(),
TransformPhase::Final,
StackRule::MinMax,
Box::new(ClampTransform::with_bounds(
Some(StatValue::from_f64(10.0)),
Some(StatValue::from_f64(200.0)),
)),
);
let context = StatContext::new();
let resolved1 = resolver1.resolve(&stat_id, &context).unwrap();
let resolved2 = resolver2.resolve(&stat_id, &context).unwrap();
assert_eq!(resolved1.value.to_f64(), 120.0);
assert_eq!(resolved2.value.to_f64(), 120.0);
assert_eq!(resolved1.value, resolved2.value);
}