@@ -115,6 +115,9 @@ void arq_fsm_init(arq_session_t *sess)
115115 sess -> speed_level = 0 ;
116116 sess -> tx_success_count = 0 ;
117117 sess -> fast_ramp = true;
118+ sess -> rx_speed_level = 0 ;
119+ sess -> rx_success_count = 0 ;
120+ sess -> rx_fast_ramp = true;
118121}
119122
120123int arq_fsm_timeout_ms (const arq_session_t * sess , uint64_t now )
@@ -151,6 +154,9 @@ static void reset_session_data_state(arq_session_t *sess)
151154 sess -> speed_level = 0 ;
152155 sess -> tx_success_count = 0 ;
153156 sess -> fast_ramp = true;
157+ sess -> rx_speed_level = 0 ;
158+ sess -> rx_success_count = 0 ;
159+ sess -> rx_fast_ramp = true;
154160 sess -> payload_mode = arq_mode_ladder [0 ]; /* MFSK floor */
155161 sess -> peer_tx_mode = arq_mode_ladder [0 ];
156162 sess -> pending_disconnect = false;
@@ -196,6 +202,9 @@ static void sess_enter(arq_session_t *sess, arq_conn_state_t new_state,
196202 {
197203 sess -> dflow_state = ARQ_DFLOW_IDLE_ISS ;
198204 sess -> peer_tx_mode = sess -> initial_payload_mode ;
205+ sess -> rx_speed_level = 0 ; /* mirror back to the floor for a fresh session */
206+ sess -> rx_success_count = 0 ;
207+ sess -> rx_fast_ramp = true;
199208 sess -> tx_frame_present = false;
200209 sess -> tx_frame_len = 0 ;
201210 sess -> tx_frame_retx = false;
@@ -285,46 +294,72 @@ static void apply_speed_level(arq_session_t *sess)
285294 clamp_payload_mode_to_bandwidth (arq_mode_ladder [sess -> speed_level ]);
286295}
287296
288- /** Record the outcome of the retained TX frame once its fate is known.
289- * Delivery-driven, no SNR/OLLA/reverse-hold: clean == the frame was delivered
290- * with no retransmission; else it needed at least one retry. Any retry steps
291- * the ladder DOWN one rung immediately (toward MFSK) for reliability. A run
292- * of clean deliveries steps it UP: the initial fast ramp climbs one rung per
293- * clean delivery until the first retry, after which it settles to
294- * ARQ_LADDER_UP_SUCCESSES clean deliveries per step. */
295- static void record_tx_outcome (arq_session_t * sess , bool clean )
297+ /** Apply one delivery-driven ladder step to a (level, success_count, fast_ramp)
298+ * triple. Shared by the ISS (TX outcomes) and the IRS mode mirror (RX
299+ * outcomes) so both ends climb/drop by exactly the same rule and stay locked
300+ * in step without any on-wire mode negotiation.
301+ *
302+ * clean == the frame was delivered/received first try: a run of clean outcomes
303+ * climbs — the fast initial ramp climbs one rung per clean outcome until the
304+ * first miss, after which it settles to ARQ_LADDER_UP_SUCCESSES clean outcomes
305+ * per step. A miss/retry steps DOWN one rung immediately (toward the MFSK
306+ * floor) and ends the fast ramp, so a deep fade parks at the floor with no
307+ * over-climb oscillation. Returns the signed level change (for logging). */
308+ static int ladder_step (int * level , int * success_count , bool * fast_ramp , bool clean )
296309{
310+ int before = * level ;
297311 if (!clean )
298312 {
299- sess -> fast_ramp = false; /* first retry ends the fast initial ramp */
300- if (sess -> speed_level > 0 )
313+ * fast_ramp = false;
314+ if (* level > 0 )
315+ (* level )-- ;
316+ * success_count = 0 ;
317+ }
318+ else
319+ {
320+ (* success_count )++ ;
321+ int need = * fast_ramp ? 1 : ARQ_LADDER_UP_SUCCESSES ;
322+ if (* success_count >= need && * level < ARQ_LADDER_LEVELS - 1 )
301323 {
302- sess -> speed_level -- ;
303- HLOGD ( LOG_COMP , "Ladder step-down to %d (retry)" , sess -> speed_level ) ;
324+ ( * level ) ++ ;
325+ * success_count = 0 ;
304326 }
305- sess -> tx_success_count = 0 ;
306- apply_speed_level (sess );
307- return ;
308327 }
328+ return * level - before ;
329+ }
309330
310- sess -> tx_success_count ++ ;
311- /* Step-up cadence (the single tuning knob, confirmed at OTA): the fast
312- * initial ramp climbs one rung per clean delivery until the first retry,
313- * after which it settles to ARQ_LADDER_UP_SUCCESSES clean deliveries per
314- * step. A retry always steps down one rung, so a deep fade parks at the
315- * MFSK floor (no over-climb oscillation) while a clean link ramps quickly. */
316- int need = sess -> fast_ramp ? 1 : ARQ_LADDER_UP_SUCCESSES ;
317- if (sess -> tx_success_count >= need &&
318- sess -> speed_level < ARQ_LADDER_LEVELS - 1 )
319- {
320- sess -> speed_level ++ ;
321- sess -> tx_success_count = 0 ;
322- HLOGD (LOG_COMP , "Ladder step-up to %d (%d clean%s)" ,
323- sess -> speed_level , need , sess -> fast_ramp ? ", fast ramp" : "" );
324- }
331+ /** Record the outcome of the retained TX frame once its fate is known.
332+ * Delivery-driven, no SNR/OLLA/reverse-hold: clean == the frame was delivered
333+ * with no retransmission; else it needed at least one retry. */
334+ static void record_tx_outcome (arq_session_t * sess , bool clean )
335+ {
336+ int delta = ladder_step (& sess -> speed_level , & sess -> tx_success_count ,
337+ & sess -> fast_ramp , clean );
338+ if (delta < 0 )
339+ HLOGD (LOG_COMP , "Ladder step-down to %d (retry)" , sess -> speed_level );
340+ else if (delta > 0 )
341+ HLOGD (LOG_COMP , "Ladder step-up to %d" , sess -> speed_level );
325342 apply_speed_level (sess );
326343}
327344
345+ /** IRS: mirror the peer's (ISS) ladder from the outcome of a received DATA
346+ * frame so our payload decoder is already on the mode the peer's NEXT burst
347+ * will use. clean_new == a new in-order frame decoded first try (mirrors the
348+ * sender's clean delivery); a duplicate (our ACK was lost, the sender retried
349+ * and stepped down) mirrors the sender's step-down. Keeps peer_tx_mode ==
350+ * arq_mode_ladder[rx_speed_level]. */
351+ static void irs_mirror_peer_ladder (arq_session_t * sess , bool clean_new )
352+ {
353+ int delta = ladder_step (& sess -> rx_speed_level , & sess -> rx_success_count ,
354+ & sess -> rx_fast_ramp , clean_new );
355+ sess -> peer_tx_mode =
356+ clamp_payload_mode_to_bandwidth (arq_mode_ladder [sess -> rx_speed_level ]);
357+ if (delta != 0 )
358+ HLOGD (LOG_COMP , "IRS RX-mode mirror %s to level %d (mode=%d)" ,
359+ delta > 0 ? "climb" : "step-down" ,
360+ sess -> rx_speed_level , sess -> peer_tx_mode );
361+ }
362+
328363/* IRS: arm the ACK deadline after a received DATA frame. Stop-and-wait: one
329364 * frame per burst, so we always wait the channel guard before emitting the
330365 * pattern ACK (lets the ISS relay switch TX->RX before our tones arrive). */
@@ -1000,8 +1035,12 @@ static void fsm_connected(arq_session_t *sess, const arq_event_t *ev)
10001035static void irs_receive_data (arq_session_t * sess , const arq_event_t * ev )
10011036{
10021037 update_local_snr (sess , ev );
1003- sess -> peer_tx_mode = ev -> mode ;
10041038 bool new_frame = deliver_rx_checked (sess , ev );
1039+ /* Follow the sender's delivery-driven ladder: a new in-order frame is a
1040+ * clean delivery on its side (climb); a duplicate means it retried and
1041+ * stepped down. This sets peer_tx_mode to the mode of the peer's NEXT
1042+ * burst so the payload decoder is already there when it arrives. */
1043+ irs_mirror_peer_ladder (sess , new_frame );
10051044 if (new_frame && g_timing )
10061045 arq_timing_record_data_rx (g_timing , (int )ev -> seq ,
10071046 (int )ev -> data_bytes , sess -> local_snr_x10 );
@@ -1209,7 +1248,6 @@ static void fsm_dflow(arq_session_t *sess, const arq_event_t *ev)
12091248 else if (ev -> id == ARQ_EV_RX_DATA )
12101249 {
12111250 update_local_snr (sess , ev );
1212- sess -> peer_tx_mode = ev -> mode ;
12131251 sess -> last_rx_ms = time_now_ms ();
12141252
12151253 if (ev -> seq == sess -> rx_expected )
@@ -1239,6 +1277,7 @@ static void fsm_dflow(arq_session_t *sess, const arq_event_t *ev)
12391277 "RX_DATA in WAIT_ACK (dup seq=%d expected=%d) — re-TX our seq=%d" ,
12401278 (int )ev -> seq , (int )sess -> rx_expected , (int )sess -> tx_frame_seq );
12411279 deliver_rx_checked (sess , ev ); /* logs dup; no delivery */
1280+ irs_mirror_peer_ladder (sess , false); /* peer retried → mirror step-down */
12421281 sess -> tx_frame_retx = true;
12431282 dflow_enter (sess , ARQ_DFLOW_DATA_TX , UINT64_MAX , ARQ_EV_TIMER_RETRY );
12441283 send_data_burst (sess );
@@ -1323,6 +1362,16 @@ static void fsm_dflow(arq_session_t *sess, const arq_event_t *ev)
13231362 }
13241363 else
13251364 {
1365+ /* Reset-on-miss: a full idle hold passed with no DATA. If a
1366+ * lost ACK left our RX-mode mirror climbed ABOVE the sender
1367+ * (which stepped down on its retry), we can no longer decode
1368+ * its bursts — silently stalling. Step the mirror down one
1369+ * rung toward the floor so the two ends re-rendezvous; the
1370+ * MFSK floor is the guaranteed common ground. At faster modes
1371+ * frames arrive well within the hold, so this only fires on a
1372+ * genuine stall (at the floor a step-down is a harmless no-op). */
1373+ if (sess -> rx_speed_level > 0 )
1374+ irs_mirror_peer_ladder (sess , false);
13261375 enter_idle_irs (sess ); /* re-arm the idle hold */
13271376 }
13281377 }
0 commit comments