@@ -758,12 +758,220 @@ impl AntialiasedSigma {
758758}
759759
760760// =============================================================================
761- // PHASE 7: GESTALT ENGINE — Connects harvest pipeline to decision lifecycle
761+ // TACTIC #23 AMP: GestaltState → InferenceRuleKind bias
762762// =============================================================================
763763
764- use crate :: nars:: TruthValue ;
764+ use crate :: nars:: { InferenceRuleKind , TruthValue } ;
765765use super :: spo_harvest:: AccumulatedHarvest ;
766766
767+ /// Per-rule bias vector derived from GestaltState.
768+ ///
769+ /// Tactic #23 (Adaptive Meta-Prompting): the gestalt state of the evidence
770+ /// influences which NARS inference rules are preferred.
771+ ///
772+ /// ```text
773+ /// Crystallizing → prefer Deduction (commit forward chains)
774+ /// Contested → prefer Analogy + Comparison (seek alternatives)
775+ /// Dissolving → prefer Abduction (find alternative explanations)
776+ /// Epiphany → prefer Induction (generalize from new evidence)
777+ /// ```
778+ impl GestaltState {
779+ /// Produce a 5-element bias vector `[(rule, weight); 5]` for NARS inference.
780+ ///
781+ /// Positive weight = prefer this rule, negative = suppress.
782+ /// Magnitudes are normalized to [-1.0, +1.0].
783+ pub fn inference_biases ( & self ) -> [ ( InferenceRuleKind , f32 ) ; 5 ] {
784+ match self {
785+ GestaltState :: Crystallizing => [
786+ ( InferenceRuleKind :: Deduction , 0.8 ) , // commit forward chains
787+ ( InferenceRuleKind :: Revision , 0.5 ) , // consolidate evidence
788+ ( InferenceRuleKind :: Induction , 0.0 ) , // neutral
789+ ( InferenceRuleKind :: Abduction , -0.3 ) , // suppress speculation
790+ ( InferenceRuleKind :: Analogy , -0.2 ) , // suppress lateral moves
791+ ] ,
792+ GestaltState :: Contested => [
793+ ( InferenceRuleKind :: Analogy , 0.7 ) , // seek parallel structures
794+ ( InferenceRuleKind :: Abduction , 0.5 ) , // seek alternative causes
795+ ( InferenceRuleKind :: Revision , 0.3 ) , // combine conflicting evidence
796+ ( InferenceRuleKind :: Deduction , -0.4 ) , // don't commit yet
797+ ( InferenceRuleKind :: Induction , 0.0 ) , // neutral
798+ ] ,
799+ GestaltState :: Dissolving => [
800+ ( InferenceRuleKind :: Abduction , 0.8 ) , // find what went wrong
801+ ( InferenceRuleKind :: Analogy , 0.4 ) , // find similar patterns
802+ ( InferenceRuleKind :: Induction , 0.2 ) , // re-generalize
803+ ( InferenceRuleKind :: Deduction , -0.6 ) , // don't chain from dissolving base
804+ ( InferenceRuleKind :: Revision , -0.3 ) , // old evidence is suspect
805+ ] ,
806+ GestaltState :: Epiphany => [
807+ ( InferenceRuleKind :: Induction , 0.8 ) , // generalize from new evidence
808+ ( InferenceRuleKind :: Revision , 0.6 ) , // integrate with existing
809+ ( InferenceRuleKind :: Analogy , 0.4 ) , // find analogies
810+ ( InferenceRuleKind :: Deduction , 0.0 ) , // neutral
811+ ( InferenceRuleKind :: Abduction , -0.2 ) , // new evidence, not explaining failure
812+ ] ,
813+ }
814+ }
815+
816+ /// Confidence modifier: how much to trust current evidence.
817+ ///
818+ /// Crystallizing = boost, Contested = dampen, Dissolving = heavily dampen.
819+ pub fn confidence_modifier ( & self ) -> f32 {
820+ match self {
821+ GestaltState :: Crystallizing => 1.2 ,
822+ GestaltState :: Contested => 0.8 ,
823+ GestaltState :: Dissolving => 0.5 ,
824+ GestaltState :: Epiphany => 1.0 ,
825+ }
826+ }
827+
828+ /// Chain depth delta: how deep inference chains should go.
829+ ///
830+ /// Crystallizing = shallow (already converging), Contested = deep (explore).
831+ pub fn chain_depth_delta ( & self ) -> i8 {
832+ match self {
833+ GestaltState :: Crystallizing => -1 ,
834+ GestaltState :: Contested => 2 ,
835+ GestaltState :: Dissolving => 3 ,
836+ GestaltState :: Epiphany => 1 ,
837+ }
838+ }
839+ }
840+
841+ // =============================================================================
842+ // TACTIC #12 TCA: Temporal ordering on TruthTrajectory
843+ // =============================================================================
844+
845+ impl TruthTrajectory {
846+ /// Granger-style temporal ordering: does confidence monotonically increase?
847+ ///
848+ /// Returns the fraction of consecutive events where confidence increased.
849+ /// 1.0 = perfectly monotone (strong causal signal).
850+ /// 0.0 = no increase at all (no causal direction).
851+ pub fn temporal_monotonicity ( & self ) -> f32 {
852+ if self . events . len ( ) < 2 {
853+ return 0.0 ;
854+ }
855+ let increases = self
856+ . events
857+ . windows ( 2 )
858+ . filter ( |w| w[ 1 ] . nars_confidence > w[ 0 ] . nars_confidence )
859+ . count ( ) ;
860+ increases as f32 / ( self . events . len ( ) - 1 ) as f32
861+ }
862+
863+ /// Temporal acceleration: is confidence gain speeding up or slowing down?
864+ ///
865+ /// Positive = accelerating (evidence snowball), negative = decelerating.
866+ /// Uses finite difference of consecutive confidence deltas.
867+ pub fn temporal_acceleration ( & self ) -> f32 {
868+ if self . events . len ( ) < 3 {
869+ return 0.0 ;
870+ }
871+ let n = self . events . len ( ) ;
872+ let delta_recent = self . events [ n - 1 ] . nars_confidence - self . events [ n - 2 ] . nars_confidence ;
873+ let delta_prev = self . events [ n - 2 ] . nars_confidence - self . events [ n - 3 ] . nars_confidence ;
874+ delta_recent - delta_prev
875+ }
876+
877+ /// Whether the trajectory shows causal temporal precedence:
878+ /// evidence consistently arrives before confidence rises (Granger criterion).
879+ ///
880+ /// Returns true if at least 60% of evidence events are followed by
881+ /// confidence increases within the next 2 events.
882+ pub fn has_temporal_precedence ( & self ) -> bool {
883+ if self . events . len ( ) < 3 {
884+ return false ;
885+ }
886+ let mut precedence_count = 0 ;
887+ let mut evidence_count = 0 ;
888+
889+ for i in 0 ..self . events . len ( ) . saturating_sub ( 2 ) {
890+ if matches ! (
891+ self . events[ i] . event_type,
892+ EvidenceEventType :: MatchesAdded ( _)
893+ ) {
894+ evidence_count += 1 ;
895+ // Check if confidence rises in next 1-2 events
896+ let base_conf = self . events [ i] . nars_confidence ;
897+ let rises = ( i + 1 ..=( i + 2 ) . min ( self . events . len ( ) - 1 ) )
898+ . any ( |j| self . events [ j] . nars_confidence > base_conf) ;
899+ if rises {
900+ precedence_count += 1 ;
901+ }
902+ }
903+ }
904+
905+ evidence_count > 0 && ( precedence_count as f32 / evidence_count as f32 ) >= 0.6
906+ }
907+ }
908+
909+ // =============================================================================
910+ // TACTIC #21 SSR: Skepticism Schedule from confidence trend
911+ // =============================================================================
912+
913+ /// Skepticism level derived from TruthTrajectory dynamics.
914+ ///
915+ /// Tactic #21 (Self-Skeptical Reasoning): when confidence rises too fast,
916+ /// inject skepticism to prevent premature crystallization.
917+ #[ derive( Debug , Clone , Copy , PartialEq ) ]
918+ pub enum SkepticismLevel {
919+ /// Trust the evidence: confidence is rising steadily from many events.
920+ Trust ,
921+ /// Mild caution: confidence is rising but from few events.
922+ Cautious ,
923+ /// Active skepticism: confidence jumped suddenly (possible artifact).
924+ Skeptical ,
925+ /// Strong doubt: confidence is oscillating or contradictory.
926+ Doubting ,
927+ }
928+
929+ impl SkepticismLevel {
930+ /// Derive skepticism from a truth trajectory.
931+ pub fn from_trajectory ( trajectory : & TruthTrajectory ) -> Self {
932+ let n = trajectory. events . len ( ) ;
933+ if n < 2 {
934+ return SkepticismLevel :: Cautious ; // not enough data
935+ }
936+
937+ let monotonicity = trajectory. temporal_monotonicity ( ) ;
938+ let acceleration = trajectory. temporal_acceleration ( ) ;
939+ let ( _, confidence) = trajectory. current_truth ( ) ;
940+
941+ // High confidence from very few events → skeptical
942+ if confidence > 0.9 && n < 5 {
943+ return SkepticismLevel :: Skeptical ;
944+ }
945+
946+ // Oscillating confidence (low monotonicity) → doubting
947+ if monotonicity < 0.3 && n > 4 {
948+ return SkepticismLevel :: Doubting ;
949+ }
950+
951+ // Sharp acceleration + high confidence → skeptical (too fast)
952+ if acceleration > 0.1 && confidence > 0.8 {
953+ return SkepticismLevel :: Skeptical ;
954+ }
955+
956+ // Steady monotone rise from many events → trust
957+ if monotonicity > 0.7 && n > 5 {
958+ return SkepticismLevel :: Trust ;
959+ }
960+
961+ SkepticismLevel :: Cautious
962+ }
963+
964+ /// Confidence damping factor: multiply with NARS confidence before gating.
965+ pub fn damping_factor ( & self ) -> f32 {
966+ match self {
967+ SkepticismLevel :: Trust => 1.0 ,
968+ SkepticismLevel :: Cautious => 0.9 ,
969+ SkepticismLevel :: Skeptical => 0.7 ,
970+ SkepticismLevel :: Doubting => 0.5 ,
971+ }
972+ }
973+ }
974+
767975/// The engine that bridges AccumulatedHarvest → BundlingProposal → TruthTrajectory.
768976///
769977/// Phase 7 integration: takes evidence from the harvest pipeline (Phases 2-6)
@@ -1246,6 +1454,129 @@ mod tests {
12461454 assert ! ( engine. trajectories[ idx] . proposal. is_committed( ) ) ;
12471455 }
12481456
1457+ // =========================================================================
1458+ // Tactic #23 AMP: inference biases from gestalt state
1459+ // =========================================================================
1460+
1461+ #[ test]
1462+ fn test_gestalt_inference_biases ( ) {
1463+ let biases = GestaltState :: Crystallizing . inference_biases ( ) ;
1464+ // Crystallizing should prefer Deduction
1465+ let deduction_bias = biases. iter ( ) . find ( |( r, _) | * r == InferenceRuleKind :: Deduction ) . unwrap ( ) . 1 ;
1466+ let abduction_bias = biases. iter ( ) . find ( |( r, _) | * r == InferenceRuleKind :: Abduction ) . unwrap ( ) . 1 ;
1467+ assert ! ( deduction_bias > 0.0 ) ;
1468+ assert ! ( abduction_bias < 0.0 ) ;
1469+
1470+ let biases = GestaltState :: Dissolving . inference_biases ( ) ;
1471+ // Dissolving should prefer Abduction
1472+ let abduction_bias = biases. iter ( ) . find ( |( r, _) | * r == InferenceRuleKind :: Abduction ) . unwrap ( ) . 1 ;
1473+ let deduction_bias = biases. iter ( ) . find ( |( r, _) | * r == InferenceRuleKind :: Deduction ) . unwrap ( ) . 1 ;
1474+ assert ! ( abduction_bias > 0.0 ) ;
1475+ assert ! ( deduction_bias < 0.0 ) ;
1476+ }
1477+
1478+ #[ test]
1479+ fn test_gestalt_confidence_modifier ( ) {
1480+ assert ! ( GestaltState :: Crystallizing . confidence_modifier( ) > 1.0 ) ;
1481+ assert ! ( GestaltState :: Contested . confidence_modifier( ) < 1.0 ) ;
1482+ assert ! ( GestaltState :: Dissolving . confidence_modifier( ) < GestaltState :: Contested . confidence_modifier( ) ) ;
1483+ }
1484+
1485+ // =========================================================================
1486+ // Tactic #12 TCA: temporal ordering tests
1487+ // =========================================================================
1488+
1489+ #[ test]
1490+ fn test_temporal_monotonicity ( ) {
1491+ let proposal = BundlingProposal :: new_tentative (
1492+ "a" . to_string ( ) , "b" . to_string ( ) ,
1493+ BundlingType :: PredicateInversion ,
1494+ 200 , 7500 , 300 , 0.5 , 0.5 ,
1495+ rustynum_core:: SignificanceLevel :: Evidence , 10 ,
1496+ ) ;
1497+ let mut trajectory = TruthTrajectory :: new ( proposal) ;
1498+
1499+ // Add monotonically increasing confidence events
1500+ for i in 1 ..=5 {
1501+ trajectory. record_event ( EvidenceEvent {
1502+ timestamp_ms : i * 1000 ,
1503+ event_type : EvidenceEventType :: MatchesAdded ( 10 ) ,
1504+ nars_frequency : 0.5 + i as f32 * 0.08 ,
1505+ nars_confidence : 0.5 + i as f32 * 0.08 ,
1506+ significance : rustynum_core:: SignificanceLevel :: Evidence ,
1507+ evidence_count : i as u32 * 10 ,
1508+ gestalt : GestaltState :: Crystallizing ,
1509+ } ) ;
1510+ }
1511+
1512+ // 5 increases out of 5 windows = 1.0 (initial event confidence was 0.5)
1513+ let mono = trajectory. temporal_monotonicity ( ) ;
1514+ assert ! ( mono > 0.8 , "Expected high monotonicity, got {}" , mono) ;
1515+ assert ! ( trajectory. has_temporal_precedence( ) ) ;
1516+ }
1517+
1518+ // =========================================================================
1519+ // Tactic #21 SSR: skepticism schedule tests
1520+ // =========================================================================
1521+
1522+ #[ test]
1523+ fn test_skepticism_too_fast ( ) {
1524+ let proposal = BundlingProposal :: new_tentative (
1525+ "a" . to_string ( ) , "b" . to_string ( ) ,
1526+ BundlingType :: PredicateInversion ,
1527+ 200 , 7500 , 300 , 0.95 , 0.95 ,
1528+ rustynum_core:: SignificanceLevel :: Discovery , 3 ,
1529+ ) ;
1530+ let mut trajectory = TruthTrajectory :: new ( proposal) ;
1531+
1532+ // Only 2 events but very high confidence → skeptical
1533+ trajectory. record_event ( EvidenceEvent {
1534+ timestamp_ms : 1000 ,
1535+ event_type : EvidenceEventType :: MatchesAdded ( 10 ) ,
1536+ nars_frequency : 0.96 ,
1537+ nars_confidence : 0.96 ,
1538+ significance : rustynum_core:: SignificanceLevel :: Discovery ,
1539+ evidence_count : 13 ,
1540+ gestalt : GestaltState :: Crystallizing ,
1541+ } ) ;
1542+
1543+ let skepticism = SkepticismLevel :: from_trajectory ( & trajectory) ;
1544+ assert_eq ! ( skepticism, SkepticismLevel :: Skeptical ) ;
1545+ assert ! ( skepticism. damping_factor( ) < 1.0 ) ;
1546+ }
1547+
1548+ #[ test]
1549+ fn test_skepticism_steady_rise ( ) {
1550+ let proposal = BundlingProposal :: new_tentative (
1551+ "a" . to_string ( ) , "b" . to_string ( ) ,
1552+ BundlingType :: PredicateInversion ,
1553+ 200 , 7500 , 300 , 0.4 , 0.4 ,
1554+ rustynum_core:: SignificanceLevel :: Hint , 5 ,
1555+ ) ;
1556+ let mut trajectory = TruthTrajectory :: new ( proposal) ;
1557+
1558+ // Many events with steady monotone rise → trust
1559+ for i in 1 ..=8 {
1560+ trajectory. record_event ( EvidenceEvent {
1561+ timestamp_ms : i * 1000 ,
1562+ event_type : EvidenceEventType :: MatchesAdded ( 10 ) ,
1563+ nars_frequency : 0.4 + i as f32 * 0.06 ,
1564+ nars_confidence : 0.4 + i as f32 * 0.06 ,
1565+ significance : rustynum_core:: SignificanceLevel :: Evidence ,
1566+ evidence_count : ( 5 + i * 10 ) as u32 ,
1567+ gestalt : GestaltState :: Crystallizing ,
1568+ } ) ;
1569+ }
1570+
1571+ let skepticism = SkepticismLevel :: from_trajectory ( & trajectory) ;
1572+ assert_eq ! ( skepticism, SkepticismLevel :: Trust ) ;
1573+ assert ! ( ( skepticism. damping_factor( ) - 1.0 ) . abs( ) < f32 :: EPSILON ) ;
1574+ }
1575+
1576+ // =========================================================================
1577+ // Phase 7: GestaltEngine tests
1578+ // =========================================================================
1579+
12491580 #[ test]
12501581 fn test_engine_gestalt_summary ( ) {
12511582 let gate = rustynum_core:: SigmaGate :: sku_16k ( ) ;
0 commit comments