Issue with AttitudesSequence

Hello Orekit community,
I am quite new to the Orekit python wrapper. I am working on a satellite attitude control simulation using the Orekit Python Wrapper, usingAttitudesSequence to switch between different pointing modes.
I have encountered a very strange behavior when mixing different attitude providers in the same sequence.
I have a list of targets on the ground. For each target, I want the satellite to maneuver from NadirPointing, perform the observation, and return to NadirPointing. I have two types of observation modes:

  1. SPOTLIGHT: Uses TargetPointing to track a specific ground intersection.

  2. STRIPMAP: Uses LofOffset (w.r.t. VVLH) to maintain a constant roll/yaw angle offset.

The Problem:

  • If I generate a sequence of only TargetPointing maneuvers, everything works perfectly (up-left plot).

  • If I generate a sequence of only LofOffset maneuvers, everything works perfectly (up-right plot).

  • The bug: If I alternate them (e.g., Target 1 is SPOTLIGHT, Target 2 is STRIPMAP), the STRIPMAP maneuver fails (bottom plot).

You can clearly observe this behavior in the attached plots showing the attitude evolution. Here are the relevant snippets of how I’ve built the sequence:



#targets = target_generation_spotlight(sgp4_propagator, init_date, end_date, utc, inertial_frame, earth, along_track_separation=900.0, step=300.0)

#targets = target_generation_stripmap(sgp4_propagator, init_date, end_date, utc, inertial_frame, earth, along_track_separation=900.0, step=300.0)

targets = target_generation_spotlight_stripmap(sgp4_propagator, init_date, end_date, utc, inertial_frame, earth, along_track_separation=900.0, step=300.0)

print("\nComputing maneuvering time for each target...")
for tgt in targets:

    if tgt["mode"] == "SPOTLIGHT":
        target_pointing = TargetPointing(inertial_frame, tgt["geodetic point"], earth)
        
        t_start = 0.0

        while True:

            state = sgp4_propagator.propagate(search_date)
            
            att = target_pointing.getAttitude(state.getOrbit(), search_date, inertial_frame)
            rot_ECI_BRF = att.getRotation()
            rot_ECI_VVLH = vvlh_frame.rotationFromInertial(search_date, state.getPVCoordinates(inertial_frame))
            rot_VVLH_BRF = rot_ECI_BRF.compose(rot_ECI_VVLH.revert(), RotationConvention.VECTOR_OPERATOR)
            angles_lof_offset = rot_VVLH_BRF.getAngles(RotationOrder.ZYX, RotationConvention.FRAME_TRANSFORM)

            rot_VVLH_BRF_ft = rot_ECI_VVLH.revert().compose(rot_ECI_BRF, RotationConvention.FRAME_TRANSFORM)
            pitch_start_deg = math.degrees(rot_VVLH_BRF_ft.getAngles(RotationOrder.ZYX, RotationConvention.FRAME_TRANSFORM)[1])
            
            if pitch_start_deg >= 30.0:
                tgt["start_yaw"] = angles_lof_offset[0]
                tgt["start_pitch"] = angles_lof_offset[1]
                tgt["start_roll"] = angles_lof_offset[2]  
                break
            t_start -= 1.0  

        J_pitch = diag_J[1]
        alpha_max_pitch = max_torque / J_pitch

        delta_theta_rad = math.radians(30.0)
        t_slew = math.sqrt((2.0 * delta_theta_rad) / alpha_max_pitch)
        

        t_end = 0.0

        while True:
            search_date = tgt["date"].shiftedBy(t_end)
            state = sgp4_propagator.propagate(search_date)
            
            att = target_pointing.getAttitude(state.getOrbit(), search_date, inertial_frame)
            rot_ECI_BRF = att.getRotation()
            rot_ECI_VVLH = vvlh_frame.rotationFromInertial(search_date, state.getPVCoordinates(inertial_frame))            
            #rot_VVLH_BRF_ft = rot_ECI_BRF.compose(rot_ECI_VVLH.revert(), RotationConvention.FRAME_TRANSFORM)                
            rot_VVLH_BRF_ft = rot_ECI_VVLH.revert().compose(rot_ECI_BRF, RotationConvention.FRAME_TRANSFORM)

            pitch_end_deg = math.degrees(rot_VVLH_BRF_ft.getAngles(RotationOrder.ZYX, RotationConvention.FRAME_TRANSFORM)[1])
            
            if pitch_end_deg <= -30.0: 
                break
            t_end += 1.0  
            
        tgt["t_start"] = t_start                                
        tgt["t_maneuver"] = t_slew + 2*t_r                      
        tgt["t_pre_pointing"] = t_start - tgt["t_maneuver"]        
        tgt["t_end"] = t_end                                    

        print(f"Target at {tgt['date']} | Mode: SPOTLIGHT | Roll Angle: {tgt['roll_deg']} deg | Window: {tgt['date'].shiftedBy(tgt['t_pre_pointing'])} UTC to {tgt['date'].shiftedBy(tgt['t_end'] + tgt['t_maneuver'])} UTC")
        print(f"Total maneuvering time: {tgt['t_end'] + tgt['t_maneuver'] - tgt['t_pre_pointing']} s")
        #print(f"Starting Pitch: {pitch_start_deg}")

    elif tgt["mode"] == "STRIPMAP":
        
        J_roll = diag_J[0]

        alpha_max_roll = max_torque / J_roll
        
        delta_theta_rad = math.radians(25.0)      
        
        t_slew = math.sqrt((2.0*delta_theta_rad) / alpha_max_roll)

        tgt["t_maneuver"] = t_slew + 2.0*t_r
        tgt["t_start"] = -tgt["t_maneuver"]
        tgt["t_pre_pointing"] = -tgt["t_maneuver"]
        tgt["t_end"] = tgt["duration"]

        print(f"Target at {tgt['date']} | Mode: STRIPMAP | Azimuth Angle: {tgt['azimuth_deg']} deg | Roll Angle: {tgt['roll_deg']} deg | Window: {tgt['date'].shiftedBy(tgt['t_pre_pointing'])} UTC to {tgt['date'].shiftedBy(tgt['t_end'] + tgt['t_maneuver'])} UTC")
        print(f"Total maneuvering time: {tgt['t_end'] + tgt['t_maneuver'] - tgt['t_pre_pointing']} s")

attitude_sequence = AttitudesSequence()
attitude_sequence.resetActiveProvider(nadir_pointing)

for tgt in targets:

    if tgt["mode"] == "SPOTLIGHT":
        target_provider = TargetPointing(inertial_frame, tgt["geodetic point"], earth)
        t_switch_to_target = tgt["t_pre_pointing"]
        t_slerp = tgt["t_maneuver"]
        t_switch_to_nadir = tgt["t_end"]

    elif tgt["mode"] == "STRIPMAP":
        target_provider = LofOffset(inertial_frame, LOFType.VVLH, RotationOrder.ZYX, math.radians(tgt["azimuth_deg"]), 0.0, math.radians(tgt["roll_deg"]))

        t_switch_to_target = tgt["t_pre_pointing"]
        t_slerp = tgt["t_maneuver"]
        t_switch_to_nadir = tgt["t_end"]

    switch_to_target_date = tgt["date"].shiftedBy(t_switch_to_target)
    switch_to_nadir_date = tgt["date"].shiftedBy(t_switch_to_nadir)

    target_detector = DateDetector(switch_to_target_date).withMaxCheck(10.0).withThreshold(1e-3).withHandler(ContinueOnEvent())
    nadir_detector = DateDetector(switch_to_nadir_date).withMaxCheck(10.0).withThreshold(1e-3).withHandler(ContinueOnEvent())

    attitude_sequence.addSwitchingCondition(nadir_pointing, target_provider, target_detector, True, False, t_slerp, AngularDerivativesFilter.USE_RR, None)     
    attitude_sequence.addSwitchingCondition(target_provider, nadir_pointing, nadir_detector, True, False, t_slerp, AngularDerivativesFilter.USE_RR, None)

To avoid state machine ambiguity, I also tried instantiating a new NadirPointing object for every return phase, but the result remains exactly the same
Any help would be greatly appreciated. Thank you!