@@ -1177,9 +1177,10 @@ static double computeChainExitCap(TP_STRUCT *tp, TC_STRUCT *active_tc,
11771177
11781178 if (seg->kink_vel > 0 ) seg_exit = fmin (seg_exit, seg->kink_vel );
11791179
1180+ double seg_acc = tcGetTangentialMaxAccel_9D_user (seg);
11801181 double brake_dist = jerkLimitedBrakingDistance (
1181- seg_max_vel, seg_exit, seg-> maxaccel , seg_jrk);
1182-
1182+ seg_max_vel, seg_exit, seg_acc , seg_jrk);
1183+
11831184 if (seg->target > brake_dist * 1.2 ) break ; // safe: can absorb any entry
11841185 }
11851186
@@ -1210,8 +1211,9 @@ static double computeChainExitCap(TP_STRUCT *tp, TC_STRUCT *active_tc,
12101211 seg_exit = fmin (seg_exit, next_entry_cap);
12111212
12121213 // Max entry this segment can accept
1214+ double seg_acc = tcGetTangentialMaxAccel_9D_user (seg);
12131215 double max_entry = jerkLimitedMaxEntryVelocity (
1214- seg_exit, seg->target , seg-> maxaccel , seg_jrk);
1216+ seg_exit, seg->target , seg_acc , seg_jrk);
12151217 max_entry = fmin (max_entry, seg_max_vel);
12161218
12171219 // Apply kink at the junction before this segment
@@ -2558,29 +2560,30 @@ static int replanForward(TP_STRUCT *tp, double v0_override, double budget_sec)
25582560 // Cross-feed deceleration corridor detection
25592561 bool cross_feed_corridor = (scaled_v_entry > max_vel * 1.01 );
25602562
2561- // Forward-lookahead: cap exit by what the next segment can actually
2562- // enter (jerk-limited braking to its exit vel). The backward pass
2563- // uses trapezoidal for v_f≈0 which overestimates; this corrects the
2564- // junction before the profile is committed .
2563+ // Forward-lookahead: cap exit by what downstream segments can
2564+ // actually handle. Walks forward until a segment is long enough
2565+ // to absorb any entry (physics-based stop), then cascades
2566+ // reachability backward — same algorithm as computeChainExitCap .
25652567 // Skip when in cross-feed corridor — the lookahead uses backward-pass
25662568 // exits at the wrong feed scale, producing overly tight caps that
25672569 // cascade forward and crush entry velocities on short segments.
25682570 if (!cross_feed_corridor && scaled_v_exit > 0.0 && i + 1 < queue_len) {
2569- TC_STRUCT *next_tc = tcqItem_user (queue, i + 1 );
2570- if (next_tc && next_tc->target > 1e-9 ) {
2571- double nf = snap.forSegment (next_tc);
2572- double nv_exit = (next_tc->term_cond == TC_TERM_COND_TANGENT)
2573- ? readFinalVelCapped (next_tc) * nf : 0.0 ;
2574- double na = tcGetTangentialMaxAccel_9D_user (next_tc);
2575- double nj = next_tc->maxjerk > 0 ? next_tc->maxjerk : default_jerk;
2576- double reach = jerkLimitedMaxEntryVelocity (nv_exit, next_tc->target , na, nj);
2577- double nvel_limit = getEffectiveVelLimit (tp, next_tc);
2578- double next_max = applyVLimit (tp, next_tc, nvel_limit * nf);
2579- reach = fmin (reach, next_max);
2580- if (reach < scaled_v_exit) {
2581- scaled_v_exit = reach;
2582- desired_fvel = reach;
2583- }
2571+ double chain_cap = computeChainExitCap (
2572+ tp, tc, feed_scale, default_jerk, /* max_depth=*/ 16 ,
2573+ /* start_index=*/ i + 1 );
2574+ if (FO_TRACE && chain_cap < 1e9 ) {
2575+ TC_STRUCT *next_tc = tcqItem_user (queue, i + 1 );
2576+ rtapi_print_msg (RTAPI_MSG_ERR,
2577+ " FO_CHAIN i=%d seg=%d chain_cap=%.3f scaled_exit=%.3f "
2578+ " capped=%d next_type=%d next_term=%d\n " ,
2579+ i, tc->id , chain_cap, scaled_v_exit,
2580+ chain_cap < scaled_v_exit ? 1 : 0 ,
2581+ next_tc ? next_tc->motion_type : -1 ,
2582+ next_tc ? next_tc->term_cond : -1 );
2583+ }
2584+ if (chain_cap < scaled_v_exit) {
2585+ scaled_v_exit = chain_cap;
2586+ desired_fvel = chain_cap;
25842587 }
25852588 }
25862589
@@ -2612,6 +2615,16 @@ static int replanForward(TP_STRUCT *tp, double v0_override, double budget_sec)
26122615 if (tc->term_cond == TC_TERM_COND_TANGENT) {
26132616 double pu = profileExitVelUnscaled (&tc->shared_9d .profile );
26142617 prev_exit_vel = pu * tc->shared_9d .profile .computed_feed_scale ;
2618+ if (FO_TRACE && fabs (prev_exit_vel - desired_fvel) > 0.1 ) {
2619+ rtapi_print_msg (RTAPI_MSG_ERR,
2620+ " FO_SKIP_EXIT_GAP i=%d seg=%d type=%d "
2621+ " desired_fvel=%.3f prof_exit=%.3f gap=%.3f "
2622+ " entry=%.3f target=%.4f\n " ,
2623+ i, tc->id , tc->motion_type ,
2624+ desired_fvel, prev_exit_vel,
2625+ desired_fvel - prev_exit_vel,
2626+ scaled_v_entry, tc->target );
2627+ }
26152628 } else {
26162629 prev_exit_vel = 0.0 ;
26172630 }
@@ -2677,6 +2690,57 @@ static int replanForward(TP_STRUCT *tp, double v0_override, double budget_sec)
26772690 applyBidirectionalReachability (scaled_v_entry, scaled_v_exit,
26782691 tc->target , max_acc, max_jrk);
26792692
2693+ // Backward propagation: if bidir reduced our entry, the predecessor's
2694+ // profile exit is too high. Rewrite it with the bidir-capped exit
2695+ // so RT sees a consistent junction velocity.
2696+ if (pre_bidir_v_entry - scaled_v_entry > 0.1 && i > 0 ) {
2697+ TC_STRUCT *prev_tc = tcqItem_user (queue, i - 1 );
2698+ if (prev_tc && prev_tc->term_cond == TC_TERM_COND_TANGENT &&
2699+ prev_tc->shared_9d .profile .valid && !(i == 1 && prev_tc->active )) {
2700+ double prev_feed = snap.forSegment (prev_tc);
2701+ double prev_vel_limit = getEffectiveVelLimit (tp, prev_tc);
2702+ double prev_max_vel = applyVLimit (tp, prev_tc, prev_vel_limit * prev_feed);
2703+ double prev_max_acc = tcGetTangentialMaxAccel_9D_user (prev_tc);
2704+ double prev_max_jrk = prev_tc->maxjerk > 0 ? prev_tc->maxjerk : default_jerk;
2705+ double prev_v_entry = prev_tc->shared_9d .profile .v [0 ];
2706+ double prev_v_exit = scaled_v_entry;
2707+ applyBidirectionalReachability (prev_v_entry, prev_v_exit,
2708+ prev_tc->target , prev_max_acc, prev_max_jrk);
2709+ double ruckig_max = fmax (prev_v_entry, prev_max_vel);
2710+ RuckigProfileParams rp = {prev_v_entry, prev_v_exit,
2711+ ruckig_max, prev_max_acc, prev_max_jrk, prev_tc->target ,
2712+ prev_feed, prev_vel_limit, tp->vLimit , prev_v_exit};
2713+ if (!computeAndStoreProfile (prev_tc, rp)) {
2714+ ruckig_max *= 1.001 ;
2715+ rp.max_vel = ruckig_max;
2716+ computeAndStoreProfile (prev_tc, rp);
2717+ }
2718+ if (prev_tc->shared_9d .profile .valid ) {
2719+ prev_tc->shared_9d .profile .dbg_src = 2 ;
2720+ atomicStoreInt ((int *)&prev_tc->shared_9d .optimization_state ,
2721+ TC_PLAN_FINALIZED);
2722+ }
2723+ }
2724+ }
2725+
2726+ if (FO_TRACE && (pre_bidir_v_entry - scaled_v_entry > 0.1 ||
2727+ pre_bidir_v_exit - scaled_v_exit > 0.1 )) {
2728+ TC_STRUCT *prev_tc = (i > 0 ) ? tcqItem_user (queue, i - 1 ) : NULL ;
2729+ rtapi_print_msg (RTAPI_MSG_ERR,
2730+ " FO_BIDIR_GAP i=%d seg=%d type=%d "
2731+ " entry=%.3f->%.3f exit=%.3f->%.3f "
2732+ " target=%.4f prev_exit=%.3f "
2733+ " prev_seg=%d prev_type=%d "
2734+ " max_a=%.1f max_j=%.1f kink=%.3f\n " ,
2735+ i, tc->id , tc->motion_type ,
2736+ pre_bidir_v_entry, scaled_v_entry,
2737+ pre_bidir_v_exit, scaled_v_exit,
2738+ tc->target , prev_exit_vel,
2739+ prev_tc ? prev_tc->id : -999 ,
2740+ prev_tc ? prev_tc->motion_type : -1 ,
2741+ max_acc, max_jrk, tc->kink_vel );
2742+ }
2743+
26802744 // Log cross-feed or zero-entry profile writes (first 5 per pass)
26812745 if (FO_TRACE && fo_dirty_count < 5 &&
26822746 (fabs (pre_bidir_v_entry) < 0.01 ||
@@ -2698,15 +2762,19 @@ static int replanForward(TP_STRUCT *tp, double v0_override, double budget_sec)
26982762 // Raise Ruckig max_velocity when entry exceeds feed-scaled limit
26992763 // (cross-feed junction). Same pattern as computeBranch() two-stage
27002764 // brake: fmax(state.velocity, new_max_vel).
2701- // The 0.1% margin avoids Ruckig ErrorExecutionTimeCalculation
2702- // (-110) which fires when max_velocity == current_velocity
2703- // exactly — a degenerate case that hits every cross-feed
2704- // corridor segment since fmax(v0, max_vel) == v0 by definition.
2705- double ruckig_max_vel = fmax (scaled_v_entry, max_vel) * 1.001 ;
2765+ double ruckig_max_vel = fmax (scaled_v_entry, max_vel);
27062766 RuckigProfileParams rp = {scaled_v_entry, scaled_v_exit,
27072767 ruckig_max_vel, max_acc, max_jrk, tc->target ,
27082768 feed_scale, vel_limit, tp->vLimit , desired_fvel};
2709- if (computeAndStoreProfile (tc, rp)) {
2769+ if (!computeAndStoreProfile (tc, rp)) {
2770+ // Retry with 0.1% margin: Ruckig ErrorExecutionTimeCalculation
2771+ // (-110) fires when max_velocity == current_velocity exactly —
2772+ // a degenerate case at cross-feed corridor boundaries.
2773+ ruckig_max_vel *= 1.001 ;
2774+ rp.max_vel = ruckig_max_vel;
2775+ computeAndStoreProfile (tc, rp);
2776+ }
2777+ if (tc->shared_9d .profile .valid ) {
27102778 fo_dirty_count++;
27112779 tc->shared_9d .profile .dbg_src = 2 ; // forward pass
27122780 tc->shared_9d .profile .dbg_v0_req = scaled_v_entry;
0 commit comments