@@ -4170,7 +4170,6 @@ STATIC int tpUpdateCycle(TP_STRUCT * const tp,
41704170 // This provides time-optimal jerk-limited deceleration
41714171 use_ruckig = 1 ;
41724172 }
4173-
41744173 if (use_ruckig_stopping ) {
41754174 // LEGACY: Cycle-by-cycle jerk-limited stopping (dead code, kept for rollback)
41764175 // Now pause/abort uses the same Ruckig branch path as feed hold for
@@ -4786,7 +4785,12 @@ STATIC int tpCheckEndCondition(TP_STRUCT const * const tp, TC_STRUCT * const tc,
47864785}
47874786
47884787
4789- // DEBUG: track v0 correction for next-cycle velocity logging
4788+ // Maximum chain-split depth: how many consecutive short segments can
4789+ // complete within a single split cycle's remaining time and be chained
4790+ // through in one servo cycle. 10 is very conservative — in practice
4791+ // at 1 kHz with ~0.03 mm arcs at 30 mm/s, at most 1-2 iterations.
4792+ #define MAX_CHAIN_DEPTH 10
4793+
47904794// Previous-cycle commanded velocity for junction_vel clamping.
47914795// Saved before tpHandleSplitCycle so the crossing-point velocity
47924796// (from a potentially stale/swapped profile) can be clamped to
@@ -5018,6 +5022,112 @@ STATIC int tpHandleSplitCycle(TP_STRUCT * const tp, TC_STRUCT * const tc,
50185022 int mode = 0 ;
50195023 tpUpdateCycle (tp , nexttc , next2tc , & mode );
50205024
5025+ // === CHAIN-SPLIT: Forward leftover time through short segments ===
5026+ // When nexttc completes within remain_time (its Ruckig profile duration
5027+ // fits inside the split budget), tpCheckEndCondition inside tpUpdateCycle
5028+ // sets nexttc->splitting=1. Instead of losing the leftover time as zero
5029+ // displacement, chain into the next segment(s) to recover it.
5030+ // This eliminates the 1-sample velocity dip / jerk spike caused by
5031+ // short arcs completing within the split cycle.
5032+ int chain_depth = 0 ;
5033+ TC_STRUCT * chain_last = nexttc ; // last segment in chain (for DIO/status)
5034+ if (GET_TRAJ_PLANNER_TYPE () == 2 ) {
5035+ double chain_remain = nexttc_remain_time ;
5036+ TC_STRUCT * chain_prev = nexttc ;
5037+ int chain_queue_idx = queue_dir_step * 2 ; // next after nexttc
5038+
5039+ while (chain_depth < MAX_CHAIN_DEPTH &&
5040+ chain_prev -> splitting &&
5041+ chain_prev -> term_cond == TC_TERM_COND_TANGENT &&
5042+ !chain_prev -> remove )
5043+ {
5044+ // Check that chain_prev has a valid profile so we can read duration
5045+ int cpv = __atomic_load_n (& chain_prev -> shared_9d .profile .valid ,
5046+ __ATOMIC_ACQUIRE );
5047+ if (!cpv ) break ;
5048+
5049+ double chain_dur = chain_prev -> shared_9d .profile .duration ;
5050+
5051+ // Duration check: if chain_dur > chain_remain, the segment did NOT
5052+ // complete within the current budget — tpCheckEndCondition flagged
5053+ // splitting for the NEXT regular cycle. Don't chain.
5054+ if (chain_dur > chain_remain + 1e-9 ) break ;
5055+
5056+ double chain_leftover = chain_remain - chain_dur ;
5057+ if (chain_leftover < 1e-9 ) break ; // no meaningful leftover
5058+
5059+ // chain_prev completed within budget. Its displacement was already
5060+ // committed by tpUpdateCycle. Mark for removal.
5061+ chain_prev -> remove = 1 ;
5062+
5063+ // Get the next segment to chain into
5064+ TC_STRUCT * chain_tc = tcqItem (& tp -> queue , chain_queue_idx );
5065+ if (!chain_tc ) break ;
5066+
5067+ // Must have a valid Ruckig profile to chain
5068+ int ctv = __atomic_load_n (& chain_tc -> shared_9d .profile .valid ,
5069+ __ATOMIC_ACQUIRE );
5070+ if (!ctv ) break ;
5071+
5072+ // --- Velocity / acceleration inheritance (same as lines 4958-4963) ---
5073+ chain_tc -> currentvel = chain_prev -> currentvel ;
5074+ double maxacc_chain = tcGetTangentialMaxAccel (chain_tc );
5075+ chain_tc -> currentacc = saturate (chain_prev -> currentacc , maxacc_chain );
5076+
5077+ // --- Full Ruckig activation (replicates lines 4993-5024) ---
5078+ chain_tc -> active = 1 ;
5079+ chain_tc -> on_final_decel = 0 ;
5080+ chain_tc -> blending_next = 0 ;
5081+ chain_tc -> position_base = 0.0 ;
5082+
5083+ chain_tc -> last_profile_generation = __atomic_load_n (
5084+ & chain_tc -> shared_9d .profile .generation , __ATOMIC_ACQUIRE );
5085+
5086+ __atomic_store_n (& chain_tc -> shared_9d .branch .valid , 0 ,
5087+ __ATOMIC_RELEASE );
5088+ __atomic_store_n (& chain_tc -> shared_9d .branch .taken , 0 ,
5089+ __ATOMIC_RELEASE );
5090+
5091+ double chain_feed = 1.0 ;
5092+ if (emcmotStatus ) {
5093+ if (chain_tc -> canon_motion_type == EMC_MOTION_TYPE_TRAVERSE ) {
5094+ chain_feed = emcmotStatus -> rapid_scale ;
5095+ } else {
5096+ chain_feed = emcmotStatus -> feed_scale ;
5097+ }
5098+ if (chain_feed < 0.0 ) chain_feed = 0.0 ;
5099+ if (chain_feed > 10.0 ) chain_feed = 10.0 ;
5100+ }
5101+ chain_tc -> shared_9d .canonical_feed_scale = chain_feed ;
5102+ chain_tc -> shared_9d .requested_feed_scale = chain_feed ;
5103+ chain_tc -> shared_9d .achieved_exit_vel = 0.0 ;
5104+ chain_tc -> shared_9d .reachability_exit_cap = -1.0 ;
5105+
5106+ // Pre-advance elapsed_time to leftover so Ruckig samples there
5107+ chain_tc -> elapsed_time = chain_leftover ;
5108+ chain_tc -> cycle_time = tp -> cycleTime ; // reset for tpUpdateCycle
5109+
5110+ // --- Run tpUpdateCycle on chained segment ---
5111+ TC_STRUCT * chain_next2 = tcqItem (& tp -> queue ,
5112+ chain_queue_idx + queue_dir_step );
5113+ int chain_mode = 0 ;
5114+ tpUpdateCycle (tp , chain_tc , chain_next2 , & chain_mode );
5115+
5116+ // Post-correct elapsed_time (same pattern as line 5176)
5117+ if (__atomic_load_n (& chain_tc -> shared_9d .profile .valid ,
5118+ __ATOMIC_ACQUIRE )) {
5119+ chain_tc -> elapsed_time = chain_leftover + tp -> cycleTime ;
5120+ }
5121+
5122+ // Iterate
5123+ chain_remain = chain_leftover ;
5124+ chain_prev = chain_tc ;
5125+ chain_last = chain_tc ;
5126+ chain_queue_idx += queue_dir_step ;
5127+ chain_depth ++ ;
5128+ }
5129+ }
5130+
50215131 // Correct profile v0 mismatch at split junction.
50225132 // When feed override changes between profile computation and junction
50235133 // arrival, the profile's v[0] doesn't match the actual junction velocity.
@@ -5062,22 +5172,25 @@ STATIC int tpHandleSplitCycle(TP_STRUCT * const tp, TC_STRUCT * const tc,
50625172 // (because cycle_time was remain_time at sample time), giving elapsed = 2*remain_time.
50635173 // But tpCheckEndCondition inside tpUpdateCycle then overwrote cycle_time to cycleTime.
50645174 // The next regular cycle should sample at remain_time + cycleTime.
5065- if (GET_TRAJ_PLANNER_TYPE () == 2 &&
5175+ // Skip if chain-split happened — nexttc is marked for removal, and
5176+ // the chained segment's elapsed_time was already corrected in the loop.
5177+ if (chain_depth == 0 &&
5178+ GET_TRAJ_PLANNER_TYPE () == 2 &&
50665179 __atomic_load_n (& nexttc -> shared_9d .profile .valid , __ATOMIC_ACQUIRE )) {
50675180 nexttc -> elapsed_time = nexttc_remain_time + tp -> cycleTime ;
50685181 }
50695182
50705183
5071- // Update status for the split portion
5072- // FIXME redundant tangent check, refactor to switch
5184+ // Update status for the split portion.
5185+ // Use chain_last (== nexttc if no chain, or last chained segment).
50735186 if (tc -> cycle_time > nexttc -> cycle_time && tc -> term_cond == TC_TERM_COND_TANGENT ) {
50745187 //Majority of time spent in current segment
50755188 tpToggleDIOs (tc );
50765189 tpUpdateMovementStatus (tp , tc );
50775190 } else {
5078- tpToggleDIOs (nexttc );
5191+ tpToggleDIOs (chain_last );
50795192 }
5080- tpUpdateMovementStatus (tp , nexttc );
5193+ tpUpdateMovementStatus (tp , chain_last );
50815194
50825195 return TP_ERR_OK ;
50835196}
@@ -5330,8 +5443,18 @@ int tpRunCycle(TP_STRUCT * const tp, long period)
53305443
53315444
53325445 // If TC is complete, remove it from the queue.
5446+ // Chain-split may have marked multiple consecutive segments for removal
5447+ // (tc + any chained short segments). Pop all of them in one servo cycle.
5448+ // Without this loop, each remove'd segment would occupy a "dead" cycle
5449+ // with zero displacement, causing the same velocity dip we're fixing.
53335450 if (tc -> remove ) {
5334- tpCompleteSegment (tp , tc );
5451+ int pop_count = 0 ;
5452+ while (pop_count < MAX_CHAIN_DEPTH + 2 ) {
5453+ TC_STRUCT * front = tcqItem (& tp -> queue , 0 );
5454+ if (!front || !front -> remove ) break ;
5455+ if (tpCompleteSegment (tp , front ) != TP_ERR_OK ) break ;
5456+ pop_count ++ ;
5457+ }
53355458 }
53365459
53375460 return TP_ERR_OK ;
0 commit comments