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