@@ -3735,6 +3735,247 @@ class Transition(Operator):
37353735 name : ClassVar [str ] = TRANSITION
37363736 glyph : ClassVar [Glyph ] = Glyph .NAV
37373737
3738+ def __call__ (self , G : TNFRGraph , node : Any , ** kw : Any ) -> None :
3739+ """Apply NAV with regime detection and controlled transition.
3740+
3741+ Implements TNFR.pdf §2.3.11 canonical transition logic:
3742+ 1. Detect current structural regime (latent/active/resonant)
3743+ 2. Handle latency reactivation if node was in silence (SHA → NAV)
3744+ 3. Apply grammar via parent __call__
3745+ 4. Execute regime-specific structural transformation (θ, νf, ΔNFR)
3746+
3747+ Parameters
3748+ ----------
3749+ G : TNFRGraph
3750+ Graph storing TNFR nodes and structural operator history.
3751+ node : Any
3752+ Identifier or object representing the target node within ``G``.
3753+ **kw : Any
3754+ Additional keyword arguments:
3755+ - phase_shift (float): Override default phase shift per regime
3756+ - vf_factor (float): Override νf scaling for active regime (default: 1.0)
3757+ - Other args forwarded to grammar layer via parent __call__
3758+
3759+ Notes
3760+ -----
3761+ Regime-specific transformations (TNFR.pdf §2.3.11):
3762+
3763+ **Latent → Active** (νf < 0.05 or latent flag):
3764+ - νf × 1.2 (20% increase for gradual reactivation)
3765+ - θ + 0.1 rad (small phase shift)
3766+ - ΔNFR × 0.7 (30% reduction for smooth transition)
3767+
3768+ **Active** (baseline state):
3769+ - νf × vf_factor (default 1.0, configurable)
3770+ - θ + 0.2 rad (standard phase shift)
3771+ - ΔNFR × 0.8 (20% reduction)
3772+
3773+ **Resonant → Active** (EPI > 0.5 AND νf > 0.8):
3774+ - νf × 0.95 (5% reduction for stability)
3775+ - θ + 0.15 rad (careful phase shift)
3776+ - ΔNFR × 0.9 (10% reduction, gentle)
3777+
3778+ Telemetry stored in G.graph["_nav_transitions"] tracks:
3779+ - regime_origin, vf_before/after, theta_before/after, dnfr_before/after
3780+ """
3781+ from ..alias import get_attr , set_attr
3782+ from ..constants .aliases import ALIAS_DNFR , ALIAS_EPI , ALIAS_THETA , ALIAS_VF
3783+
3784+ # 1. Detect current regime
3785+ current_regime = self ._detect_regime (G , node )
3786+
3787+ # 2. Handle latency reactivation if applicable
3788+ if G .nodes [node ].get ("latent" , False ):
3789+ self ._handle_latency_transition (G , node )
3790+
3791+ # 3. Apply grammar base (delegates to parent which calls apply_glyph_with_grammar)
3792+ super ().__call__ (G , node , ** kw )
3793+
3794+ # 4. Execute structural transition
3795+ self ._apply_structural_transition (G , node , current_regime , ** kw )
3796+
3797+ def _detect_regime (self , G : TNFRGraph , node : Any ) -> str :
3798+ """Detect current structural regime: latent/active/resonant.
3799+
3800+ Parameters
3801+ ----------
3802+ G : TNFRGraph
3803+ Graph containing the node.
3804+ node : Any
3805+ Target node.
3806+
3807+ Returns
3808+ -------
3809+ str
3810+ Regime classification: "latent", "active", or "resonant"
3811+
3812+ Notes
3813+ -----
3814+ Classification criteria:
3815+ - **Latent**: latent flag set OR νf < 0.05 (minimal reorganization capacity)
3816+ - **Resonant**: EPI > 0.5 AND νf > 0.8 (high form + high frequency)
3817+ - **Active**: Default (baseline operational state)
3818+ """
3819+ from ..alias import get_attr
3820+ from ..constants .aliases import ALIAS_EPI , ALIAS_VF
3821+
3822+ epi = float (get_attr (G .nodes [node ], ALIAS_EPI , 0.0 ))
3823+ vf = float (get_attr (G .nodes [node ], ALIAS_VF , 0.0 ))
3824+ latent = G .nodes [node ].get ("latent" , False )
3825+
3826+ if latent or vf < 0.05 :
3827+ return "latent"
3828+ elif epi > 0.5 and vf > 0.8 :
3829+ return "resonant"
3830+ else :
3831+ return "active"
3832+
3833+ def _handle_latency_transition (self , G : TNFRGraph , node : Any ) -> None :
3834+ """Handle transition from latent state (SHA → NAV flow).
3835+
3836+ Similar to Emission._check_reactivation but for NAV-specific transitions.
3837+ Validates silence duration and clears latency attributes.
3838+
3839+ Parameters
3840+ ----------
3841+ G : TNFRGraph
3842+ Graph containing the node.
3843+ node : Any
3844+ Target node being reactivated.
3845+
3846+ Warnings
3847+ --------
3848+ - Warns if node transitioning after extended silence (duration > MAX_SILENCE_DURATION)
3849+ - Warns if EPI drifted significantly during silence (> 1% tolerance)
3850+
3851+ Notes
3852+ -----
3853+ Clears latency-related attributes:
3854+ - latent (flag)
3855+ - latency_start_time (ISO timestamp)
3856+ - preserved_epi (EPI snapshot from SHA)
3857+ - silence_duration (computed duration)
3858+ """
3859+ from datetime import datetime , timezone
3860+
3861+ # Verify silence duration if timestamp available
3862+ if "latency_start_time" in G .nodes [node ]:
3863+ start = datetime .fromisoformat (G .nodes [node ]["latency_start_time" ])
3864+ duration = (datetime .now (timezone .utc ) - start ).total_seconds ()
3865+ G .nodes [node ]["silence_duration" ] = duration
3866+
3867+ max_silence = G .graph .get ("MAX_SILENCE_DURATION" , float ("inf" ))
3868+ if duration > max_silence :
3869+ warnings .warn (
3870+ f"Node { node } transitioning after extended silence "
3871+ f"(duration: { duration :.2f} s, max: { max_silence :.2f} s)" ,
3872+ stacklevel = 4 ,
3873+ )
3874+
3875+ # Check EPI preservation integrity
3876+ preserved_epi = G .nodes [node ].get ("preserved_epi" )
3877+ if preserved_epi is not None :
3878+ from ..alias import get_attr
3879+ from ..constants .aliases import ALIAS_EPI
3880+
3881+ current_epi = float (get_attr (G .nodes [node ], ALIAS_EPI , 0.0 ))
3882+ epi_drift = abs (current_epi - preserved_epi )
3883+
3884+ # Allow small numerical drift (1% tolerance)
3885+ if epi_drift > 0.01 * abs (preserved_epi ):
3886+ warnings .warn (
3887+ f"Node { node } EPI drifted during silence "
3888+ f"(preserved: { preserved_epi :.3f} , current: { current_epi :.3f} , "
3889+ f"drift: { epi_drift :.3f} )" ,
3890+ stacklevel = 4 ,
3891+ )
3892+
3893+ # Clear latency state
3894+ del G .nodes [node ]["latent" ]
3895+ if "latency_start_time" in G .nodes [node ]:
3896+ del G .nodes [node ]["latency_start_time" ]
3897+ if "preserved_epi" in G .nodes [node ]:
3898+ del G .nodes [node ]["preserved_epi" ]
3899+ if "silence_duration" in G .nodes [node ]:
3900+ # Keep silence_duration for telemetry
3901+ pass
3902+
3903+ def _apply_structural_transition (
3904+ self , G : TNFRGraph , node : Any , regime : str , ** kw : Any
3905+ ) -> None :
3906+ """Apply structural transformation based on regime origin.
3907+
3908+ Parameters
3909+ ----------
3910+ G : TNFRGraph
3911+ Graph containing the node.
3912+ node : Any
3913+ Target node.
3914+ regime : str
3915+ Origin regime: "latent", "active", or "resonant"
3916+ **kw : Any
3917+ Optional overrides:
3918+ - phase_shift (float): Custom phase shift
3919+ - vf_factor (float): Custom νf scaling for active regime
3920+
3921+ Notes
3922+ -----
3923+ Applies regime-specific transformations to θ, νf, and ΔNFR following
3924+ TNFR.pdf §2.3.11. All changes use canonical alias system (set_attr)
3925+ to ensure proper attribute resolution.
3926+
3927+ Telemetry appended to G.graph["_nav_transitions"] for analysis.
3928+ """
3929+ from ..alias import get_attr , set_attr
3930+ from ..constants .aliases import ALIAS_DNFR , ALIAS_THETA , ALIAS_VF
3931+
3932+ # Get current state
3933+ theta = float (get_attr (G .nodes [node ], ALIAS_THETA , 0.0 ))
3934+ vf = float (get_attr (G .nodes [node ], ALIAS_VF , 1.0 ))
3935+ dnfr = float (get_attr (G .nodes [node ], ALIAS_DNFR , 0.0 ))
3936+
3937+ # Apply regime-specific adjustments
3938+ if regime == "latent" :
3939+ # Latent → Active: gradual reactivation
3940+ vf_new = vf * 1.2 # 20% increase
3941+ theta_shift = kw .get ("phase_shift" , 0.1 ) # Small phase shift
3942+ theta_new = (theta + theta_shift ) % (2 * math .pi )
3943+ dnfr_new = dnfr * 0.7 # 30% reduction for smooth transition
3944+ elif regime == "active" :
3945+ # Active: standard transition
3946+ vf_new = vf * kw .get ("vf_factor" , 1.0 ) # Configurable
3947+ theta_shift = kw .get ("phase_shift" , 0.2 ) # Standard shift
3948+ theta_new = (theta + theta_shift ) % (2 * math .pi )
3949+ dnfr_new = dnfr * 0.8 # 20% reduction
3950+ else : # resonant
3951+ # Resonant → Active: careful transition (high energy state)
3952+ vf_new = vf * 0.95 # 5% reduction for stability
3953+ theta_shift = kw .get ("phase_shift" , 0.15 ) # Careful phase shift
3954+ theta_new = (theta + theta_shift ) % (2 * math .pi )
3955+ dnfr_new = dnfr * 0.9 # 10% reduction, gentle
3956+
3957+ # Apply changes via canonical alias system
3958+ set_attr (G .nodes [node ], ALIAS_VF , vf_new )
3959+ set_attr (G .nodes [node ], ALIAS_THETA , theta_new )
3960+ set_attr (G .nodes [node ], ALIAS_DNFR , dnfr_new )
3961+
3962+ # Telemetry tracking
3963+ if "_nav_transitions" not in G .graph :
3964+ G .graph ["_nav_transitions" ] = []
3965+ G .graph ["_nav_transitions" ].append (
3966+ {
3967+ "node" : node ,
3968+ "regime_origin" : regime ,
3969+ "vf_before" : vf ,
3970+ "vf_after" : vf_new ,
3971+ "theta_before" : theta ,
3972+ "theta_after" : theta_new ,
3973+ "dnfr_before" : dnfr ,
3974+ "dnfr_after" : dnfr_new ,
3975+ "phase_shift" : theta_new - theta ,
3976+ }
3977+ )
3978+
37383979 def _validate_preconditions (self , G : TNFRGraph , node : Any ) -> None :
37393980 """Validate NAV-specific preconditions."""
37403981 from .preconditions import validate_transition
0 commit comments