use crate::cognition::adaptive_thresholds::{
build_threshold_context, get_adaptive_threshold, threshold_body_budget_conservation,
threshold_delta_arousal_emergency, threshold_delta_volatility,
};
use crate::math::vector::clamp;
use crate::types::gate::GateType;
use crate::types::intervention::{
CognitiveState, DeltaModulation, DeltaModulationSource, LayerTarget,
};
use crate::types::world::GainMode;
const TARGET_DEPTH_START: f64 = 0.40;
const TARGET_DEPTH_END: f64 = 0.60;
const GAIN_PHASIC: f64 = 0.90;
const GAIN_TONIC: f64 = 0.80;
const GAIN_NEUTRAL: f64 = 1.0;
const BUDGET_DELTA_REDUCTION: f64 = 0.10;
const VOLATILITY_DELTA_REDUCTION: f64 = 0.08;
const AROUSAL_DELTA_REDUCTION: f64 = 0.10;
const GAIN_MIN: f64 = 0.5;
const GAIN_MAX: f64 = 2.0;
pub fn compute_delta_modulation(
state: &CognitiveState,
num_layers: usize,
) -> DeltaModulation {
if matches!(state.gate_type, GateType::Routine) {
return DeltaModulation {
gain_factor: GAIN_NEUTRAL,
target: compute_layer_targets(num_layers),
source: DeltaModulationSource::Combined,
};
}
let threshold_ctx = build_threshold_context(
state.sensory_pe,
state.arousal,
state.gate_confidence,
state.pe_volatility,
Some(state.valence),
Some(state.body_budget),
);
let budget_threshold =
get_adaptive_threshold(&threshold_body_budget_conservation(), &threshold_ctx);
let volatility_threshold =
get_adaptive_threshold(&threshold_delta_volatility(), &threshold_ctx);
let arousal_emergency_threshold =
get_adaptive_threshold(&threshold_delta_arousal_emergency(), &threshold_ctx);
let mut gain = match state.gain_mode {
GainMode::Phasic => GAIN_PHASIC,
GainMode::Tonic => GAIN_TONIC,
GainMode::Neutral => GAIN_NEUTRAL,
};
if state.body_budget < budget_threshold {
let factor =
(budget_threshold - state.body_budget) / budget_threshold.max(0.01);
gain -= factor * BUDGET_DELTA_REDUCTION;
}
if state.pe_volatility > volatility_threshold {
let factor = (state.pe_volatility - volatility_threshold)
/ (1.0 - volatility_threshold).max(0.01);
gain -= factor * VOLATILITY_DELTA_REDUCTION;
}
if state.arousal > arousal_emergency_threshold {
let factor = (state.arousal - arousal_emergency_threshold)
/ (1.0 - arousal_emergency_threshold).max(0.01);
gain -= factor * AROUSAL_DELTA_REDUCTION;
}
gain = clamp(gain, GAIN_MIN, GAIN_MAX);
let target = compute_layer_targets(num_layers);
DeltaModulation {
gain_factor: gain,
target,
source: DeltaModulationSource::Combined,
}
}
pub fn compute_layer_targets(num_layers: usize) -> LayerTarget {
let start = (num_layers as f64 * TARGET_DEPTH_START).round() as usize;
let end = ((num_layers as f64 * TARGET_DEPTH_END).round() as usize).min(num_layers.saturating_sub(1));
LayerTarget {
start_layer: start,
end_layer: end,
total_layers: num_layers,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::belief::AffectValence;
#[test]
fn phasic_gain_below_neutral() {
let state = CognitiveState {
gain_mode: GainMode::Phasic,
..CognitiveState::default()
};
let dm = compute_delta_modulation(&state, 64);
assert!(
dm.gain_factor < 1.0,
"Phasic mode should reduce delta (compensatory retention, data: gain<1.0 helps)"
);
}
#[test]
fn tonic_gain_below_neutral() {
let state = CognitiveState {
gain_mode: GainMode::Tonic,
..CognitiveState::default()
};
let dm = compute_delta_modulation(&state, 64);
assert!(
dm.gain_factor < 1.0,
"Tonic mode should decrease delta (broad exploration)"
);
}
#[test]
fn neutral_gain_is_unity() {
let state = CognitiveState::default();
let dm = compute_delta_modulation(&state, 64);
assert_eq!(
dm.gain_factor, 1.0,
"Neutral mode with no signals should be pass-through"
);
}
#[test]
fn low_budget_reduces_gain() {
let normal = CognitiveState::default();
let depleted = CognitiveState {
body_budget: 0.1,
..CognitiveState::default()
};
let dm_normal = compute_delta_modulation(&normal, 64);
let dm_depleted = compute_delta_modulation(&depleted, 64);
assert!(
dm_depleted.gain_factor < dm_normal.gain_factor,
"Low body budget should reduce delta (conservation)"
);
}
#[test]
fn full_budget_no_reduction() {
let state = CognitiveState {
body_budget: 1.0,
..CognitiveState::default()
};
let dm = compute_delta_modulation(&state, 64);
assert_eq!(dm.gain_factor, 1.0);
}
#[test]
fn high_volatility_reduces_gain() {
let stable = CognitiveState::default();
let volatile = CognitiveState {
pe_volatility: 0.9,
..CognitiveState::default()
};
let dm_stable = compute_delta_modulation(&stable, 64);
let dm_volatile = compute_delta_modulation(&volatile, 64);
assert!(
dm_volatile.gain_factor < dm_stable.gain_factor,
"High volatility should reduce delta (stabilize, preserve known state)"
);
}
#[test]
fn emergency_arousal_reduces_gain() {
let calm = CognitiveState::default();
let threat = CognitiveState {
arousal: 0.95,
valence: AffectValence::Negative,
..CognitiveState::default()
};
let dm_calm = compute_delta_modulation(&calm, 64);
let dm_threat = compute_delta_modulation(&threat, 64);
assert!(
dm_threat.gain_factor < dm_calm.gain_factor,
"High arousal should reduce delta (preserve emotional context)"
);
}
#[test]
fn moderate_arousal_no_override() {
let state = CognitiveState {
arousal: 0.5, ..CognitiveState::default()
};
let dm = compute_delta_modulation(&state, 64);
assert_eq!(
dm.gain_factor, 1.0,
"Moderate arousal should not trigger emergency override"
);
}
#[test]
fn gain_clamped_to_safe_range() {
let extreme = CognitiveState {
gain_mode: GainMode::Phasic,
pe_volatility: 1.0,
arousal: 1.0,
..CognitiveState::default()
};
let dm = compute_delta_modulation(&extreme, 64);
assert!(
dm.gain_factor <= GAIN_MAX,
"Gain must never exceed GAIN_MAX ({})",
GAIN_MAX
);
assert!(
dm.gain_factor >= GAIN_MIN,
"Gain must never go below GAIN_MIN ({})",
GAIN_MIN
);
}
#[test]
fn depleted_tonic_stays_above_floor() {
let extreme = CognitiveState {
gain_mode: GainMode::Tonic,
body_budget: 0.0,
..CognitiveState::default()
};
let dm = compute_delta_modulation(&extreme, 64);
assert!(
dm.gain_factor >= GAIN_MIN,
"Even extreme depletion must stay above GAIN_MIN"
);
}
#[test]
fn layer_targeting_midrange_64() {
let target = compute_layer_targets(64);
assert_eq!(target.total_layers, 64);
assert!(target.start_layer >= 25 && target.start_layer <= 26);
assert!(target.end_layer >= 38 && target.end_layer <= 39);
}
#[test]
fn layer_targeting_small_model() {
let target = compute_layer_targets(12);
assert!(target.start_layer >= 4 && target.start_layer <= 5);
assert!(target.end_layer >= 7 && target.end_layer <= 8);
assert_eq!(target.total_layers, 12);
}
#[test]
fn layer_targeting_single_layer() {
let target = compute_layer_targets(1);
assert_eq!(target.total_layers, 1);
assert!(target.end_layer < 1);
}
#[test]
fn signals_compose_additively() {
let phasic = CognitiveState {
gain_mode: GainMode::Phasic,
..CognitiveState::default()
};
let phasic_volatile = CognitiveState {
gain_mode: GainMode::Phasic,
pe_volatility: 0.9,
..CognitiveState::default()
};
let dm_p = compute_delta_modulation(&phasic, 64);
let dm_pv = compute_delta_modulation(&phasic_volatile, 64);
assert!(
dm_pv.gain_factor < dm_p.gain_factor,
"Volatility should compound with phasic reduction (both push gain down)"
);
}
#[test]
fn tonic_plus_budget_depletion_compounds() {
let tonic = CognitiveState {
gain_mode: GainMode::Tonic,
..CognitiveState::default()
};
let tonic_depleted = CognitiveState {
gain_mode: GainMode::Tonic,
body_budget: 0.1,
..CognitiveState::default()
};
let dm_t = compute_delta_modulation(&tonic, 64);
let dm_td = compute_delta_modulation(&tonic_depleted, 64);
assert!(
dm_td.gain_factor < dm_t.gain_factor,
"Budget depletion should compound with tonic reduction"
);
}
#[test]
fn source_is_combined() {
let state = CognitiveState::default();
let dm = compute_delta_modulation(&state, 64);
assert_eq!(dm.source, DeltaModulationSource::Combined);
}
#[test]
fn routine_gate_forces_passthrough_over_phasic() {
let state = CognitiveState {
gain_mode: GainMode::Phasic,
gate_type: GateType::Routine,
..CognitiveState::default()
};
let dm = compute_delta_modulation(&state, 64);
assert_eq!(
dm.gain_factor, 1.0,
"Routine gate must override Phasic mode to passthrough (P9: stay out when cortex sufficient)"
);
}
#[test]
fn routine_gate_ignores_high_arousal() {
let state = CognitiveState {
gain_mode: GainMode::Phasic,
arousal: 0.95,
gate_type: GateType::Routine,
..CognitiveState::default()
};
let dm = compute_delta_modulation(&state, 64);
assert_eq!(
dm.gain_factor, 1.0,
"Routine gate should pass through even when arousal scalar is high (churn ≠ salience)"
);
}
#[test]
fn novel_gate_preserves_compensatory_behavior() {
let state = CognitiveState {
gain_mode: GainMode::Phasic,
gate_type: GateType::Novel,
..CognitiveState::default()
};
let dm = compute_delta_modulation(&state, 64);
assert!(
dm.gain_factor < 1.0,
"Novel gate must NOT short-circuit — existing Phasic→0.90 reduction should still apply"
);
}
#[test]
fn urgent_gate_preserves_compensatory_behavior() {
let state = CognitiveState {
gain_mode: GainMode::Phasic,
arousal: 0.9,
gate_type: GateType::Urgent,
..CognitiveState::default()
};
let dm = compute_delta_modulation(&state, 64);
assert!(
dm.gain_factor < 1.0,
"Urgent gate must allow compensatory reduction — crisis content needs retention"
);
}
#[test]
fn routine_gate_still_targets_correct_layers() {
let state = CognitiveState {
gate_type: GateType::Routine,
..CognitiveState::default()
};
let dm = compute_delta_modulation(&state, 32);
assert_eq!(dm.target.total_layers, 32);
assert_eq!(dm.gain_factor, 1.0);
}
}