   if (estimate < CYCLE_TIME - CYCLE_TIME/5 ||
       estimate > CYCLE_TIME + CYCLE_TIME/5)
     estimate = CYCLE_TIME;
-  Serial.print("start estimate = "); Serial.println(estimate);
+  Serial.print("start "); Serial.println(estimate);
   // narrow down estimate from 20 % -> 2 %
   window = estimate / 5; 
-  while (window >= 16)
+  while (window >= 2 * MIN_WIN)
     if (estimateWithWatchdog())
       window /= 2;
-  // save best estimaet so far
+  // save best estimate so far
   eeprom_write_word(EEADDR, estimate);
-  Serial.print("save estimate "); Serial.println(estimate);
+  Serial.print("save "); Serial.println(estimate);
 }
 
 void setup () {
   Serial.begin(57600);
-  Serial.println("\n[syncRecv]");
+  Serial.println("\n[syncRecv] " __TIME__);
   rf12_initialize(1, SEND_FREQ, SEND_GROUP); // we never send
   chooseEstimate();
+  Serial.flush(); delayMicroseconds(250);
+  window = MIN_WIN;
 }
 
 void loop () {
   uint32_t now = millis();
+  // make a guess as to how many packets we lost
   byte lost = (now - lastRecv + estimate/2) / estimate;
-  Serial.print(" #"); Serial.print(lost);
-  if (lost > 20)
+  
+  // if things are really bad, resync from scratch
+  if (lost > 20) {
     chooseEstimate();
-  else {
-    uint32_t predict = lastRecv + (lost + 1) * estimate;
-    word sleep = predict - now - window;
+    return;
+  }
 
-    Serial.print(" s "); Serial.print(sleep);
-    Serial.flush(); delayMicroseconds(250);
+  // double the window every 4 additional packets lost
+  if (lost > 0 && lost % 4 == 0 && window < MAX_WIN)
+    window *= 2;
 
-    radioSleep(sleep);
+  // make a prediction for when the next packet should arrive
+  uint32_t predict = lastRecv + (lost + 1) * estimate;
+  word sleep = predict - now - (lost ? 0 : window);
+  radioSleep(sleep);
 
-    uint32_t recvTime;
-    do {
-      recvTime = millis();
-      if (recvTime > predict + window)
-        return;
-    } while (!isDesiredPacket());
+  // listen to the radio until one window past predicted time
+  uint32_t recvTime;
+  do {
+    recvTime = millis();
+    if (recvTime > predict + window)
+      return;
+  } while (!isDesiredPacket());
 
-    estimate = (recvTime - lastRecv) / (lost + 1);
-    int offset = (estimate - sleep) - window;
-    lastRecv = recvTime;
+  // adjust our estimate and prepare for more packets
+  word newEst = (recvTime - lastRecv) / (lost + 1);
+  estimate = (5 * estimate + newEst) / 6; // 6-fold smoothing
+  int offset = (estimate - sleep) - window;
+  lastRecv = recvTime;
 
-    Serial.print(" e "); Serial.print(estimate);
-    Serial.print(" w "); Serial.print(window);
-    Serial.print(" o "); Serial.print(offset);
-    Serial.print(" rf "); Serial.println(offset + window);
-  }
+  Serial.print(" n "); Serial.print(lost);
+  Serial.print(" e "); Serial.print(estimate);
+  Serial.print(" w "); Serial.print(window);
+  Serial.print(" o "); Serial.print(offset);
+  Serial.print(" rf "); Serial.println(offset + window);
+  Serial.flush(); delayMicroseconds(250);
+
+  // successful reception means we can narrow down the window
+  if (window > MIN_WIN)
+    window /= 2;
 }

ISR(WDT_vect) { Sleepy::watchdogEvent(); }

static void radioSleep (word ms) {
  rf12_sleep(RF12_SLEEP);
  Sleepy::loseSomeTime(ms);
  rf12_sleep(RF12_WAKEUP);
}

static bool isDesiredPacket () {

#define SEND_ID     9             // listen for this node ID

#define CYCLE_TIME  3000          // expected cycle, milliseconds
#define MIN_WIN     8             // minimum window size, power of 2
#define MAX_WIN     1024          // maximum window size, power of 2

#define EEADDR ((word*) 0)        // save estimate at this EEPROM address

