chiark / gitweb /
realtime: break out new function movpos.c:ouposn_feat
[trains.git] / hostside / movpos.c
index 586bcf737d1bb88ee2b8f497f7eec068f8dee26b..b13326761792cb50868b7ebb44cdab775948ed25 100644 (file)
@@ -11,78 +11,134 @@ typedef struct {
   Small posn;
 } Motion;
 
-typedef struct KindInfo KindInfo;
+typedef struct Method Method;
+typedef struct Change Change;
+
+typedef struct MovPosChange {
+  Segment *move;
+  /* everything beyond here is private for indep */
+  MovPosComb actual, target;
+  int refcount;
+  int n_changes;
+  Change *changes[];
+} Indep;
+
+static void method_update_feature(Method*, MovPosChange*, const Motion *mo);
+  /* Called from methods' execution logic when an individual feature
+   * has been moved.  This is used by the method-independent code to
+   * compute the correct delta set of movements from the current
+   * actual position, when thinking about new plans.  It is also sent
+   * to clients and ultimately used by the UI.
+   */
+
+static void method_change_done(Method *m, Change *chg);
+  /* Called from methods' execution logic when a whole change
+   * has been done.  The method-independent code will take care of
+   * updating move->movposcomb. etc.
+   *
+   * REENTRANCY: May be called from within a call to the method's
+   * execute().  Of course cannot legally be called from within
+   * prepare, consider, check or dispose.
+   */
+
 
 /* Kind-independent code is responsible for determining
  * the method, doing a bit of cleanup, and adjusting the flow
  * slightly.  Per-kind code does the actual work and is mostly in
  * charge - it is also responsible for updating seg->moving and ->motion.
  */
-/* The following states exist for each MovPosChange
+/*
+ * The following states exist for each Method
+ *   S  Starting     corresponds to global states other than Sta_Run
+ *   T  Tentative    changes have been made but may yet be undone
+ *   Y  Yes          proposed changes have been checked and are OK
+ *   E  Executing    the method is executing
+ *
+ * The following states exist for each Change
  * at points when control flow  passes between kind and indep:
- *   U  Unallocated  no memory allocated (MovPosChange does not exist)
- *   A  Allocated    memory allocation done
- *   R  Reserved     reservation was successful
- *   C  Confirmed    motion queued and will occur
- *   D  Done         motion is complete and callback just needs to be made
- *   E  Erroneous    indep must call destroy straight away
- * seg->moving and ->motion is in one of the states UC
+ *   U  Unallocated  no memory allocated (Change does not exist)
+ *   P  Prepared     memory allocation done, basics filled in
+ *   I  Installed    reservation or confirmation successful
+ *   D  Done         motion is complete and callback is being entered
+ *   G  Garbage      motion is complete and callback is being exited
+ *
+ * Changes may be for:
+ *   R  Reservation
+ *   C  Confirmation
+ *
+ * No Changes remain Prepared while the Method is Executing.
  */
 
-typedef struct MovPosChange {      /* valid in:   filled in by and when:     */
-  const KindInfo *ki;              /*  ARCDE       indep after allocate()    */
-  Segment *move;                   /*  ARCDE       indep after allocate()    */
-  MovPosComb actual;               /*  CD          see below                 */
-  MovPosComb intent;               /*  RCD         indep after allocate()    */
+struct Change {                    /* valid in:  filled in by and when:      */
+  Method *meth;                    /*  PIDG       indep after prepare()      */
+  MovPosChange *indep;             /*  PID        indep after prepare()      */
+  unsigned installed:1; /* private for indep */
   /* kind-specific data follows */ /*  varies     kind-specific code, varies */
-} Change;
-  /* `actual' contains the kind's public opinion about the physical
-   * state.  It is initialised by indep (just before confirm) from
-   * move->motion->actual or move->movposcomb as the case may be.  It
-   * should be updated by the kind, since it is used by indep for
-   * calculating the number and identities of the features which may
-   * need to change when a new move request is intended to replace an
-   * existing one - ie, the contents of the motions[] provided to a
-   * subsequent confirm().  So while a change is Confirmed, the
-   * physical state is recorded only in the relevant change, and not
-   * in the segment's movposcomb.  Once a change goes to Confirmed,
-   * the indep code never untangles it so the kind can manage the
-   * proper transition.  */
-
-struct KindInfo {
+};
+
+struct Method {
   const char *pname;
-  Change *(*allocate)(int alloc_motions); /* U->A (always succeeds) */
-  ErrorCode (*reserve)(Change*, Segment*, int ms); /* A->R; error: A->E */
-  ErrorCode (*confirm)(Change*, Segment*, int n_motions,
-                      const Motion*, int ms);   /* [AR]->C; error; [AR]->E */
-  void (*destroy)(Change*);                        /* [ARCE]->U */
-  /* indep guarantees that
-   *   alloc_motions >= move->i->n_motions    on reserve
-   *   alloc_motions >= n_motions             on confirm
-   * and that if on entry to reserve move->motion is non-0,
-   *  it move->motion is non-0 and of the same kind
-   */
+
+  ErrorCode (*prepare)(Method *m,                                 /* TYE->T */
+                      const Segment *move,
+                      int n_motions, const Motion *motions,
+                      int ms, int confirmation,
+                      Change **chg_r,          /* 0->P; err: 0->0; may be 0 */
+                      int *cost_r /* may be 0 */);
+  void (*dispose)(Method *m,                                       /* TY->TY */
+                 Change*);                                          /* P->U */
+
+  ErrorCode (*install)(Method *m,                   /* TYE->T; err: TYE->TYY */
+                      Change *inst);                     /* P->I; err: P->P */
+      /* error: ET->T, no change to Changes */
+  void (*remove)(Method *m,                                      /* TYE->TYY */
+                Change *remove);                                    /* I->P */
+      /* error: ET->T, no change to Changes */
+
+  ErrorCode (*check)(Method *m);                    /* TYE->Y; err: TYE->TYE */
+  void (*execute)(Method *m);                                       /* EY->E */
+  void (*all_abandon)(Method *m);                                  /* TYE->S */
+
+  unsigned needcheck, needexec; /* used by indep to track T/Y/E */
 };
 
-static const char *posnpname(Segment *move, MovPosComb poscomb) {
-  return poscomb<0 ? "?" : move->i->poscombs[poscomb].pname;
+/*========== general utility functions ==========*/
+
+const char *movpos_pname(const Segment *move, MovPosComb poscomb) {
+  return !SOMEP(poscomb) ? "?" : move->i->poscombs[poscomb].pname;
 }
 
-static void ouposn_moving(Change *chg) {
-  Segment *move= chg->move;
-  oprintf(UPO, "movpos %s position %s moving\n",
-         move->i->pname, posnpname(move, chg->actual));
+static void ouposn_moving(const MovPosChange *indep) {
+  Segment *move= indep->move;
+  ouprintf("movpos %s position %s moving\n",
+          move->i->pname, movpos_pname(move, indep->actual));
+}
+static void ouposn_stable(const Segment *move) {
+  ouprintf("movpos %s position %s stable\n",
+          move->i->pname, movpos_pname(move, move->movposcomb));
+}
+static void ouposn_feat(const Segment *move, const MovFeatInfo *feati,
+                       MovPosComb posn, const Method *m) {
+  ouprintf("movpos %s feat %s %d %s\n", move->i->pname,
+          feati->pname, posn, m->pname);
 }
 
-static void motion_done(Segment *move, MovPosComb actual) {
-  move->moving= 0;
-  move->motion= 0;
-  move->movposcomb= actual;
-  oprintf(UPO, "movpos %s position %s stable\n",
-         move->i->pname, posnpname(move, move->movposcomb));
+MovPosComb movposcomb_update_feature(MovPosComb startpoint,
+                                    const MovFeatInfo *mfi,
+                                    int featpos) {
+  MovPosComb above_weight= mfi->weight * mfi->posns;
+  MovPosComb above= startpoint / above_weight;
+  MovPosComb below= startpoint % mfi->weight;
+  return above*above_weight + featpos*mfi->weight + below;
 }
 
-/*========== points ==========*/
+MovPosComb movpos_poscomb_actual(const Segment *seg) {
+  return seg->moving ? seg->motion->actual : seg->movposcomb;
+}
+
+static void ignore_all_abandon(Method *m) { }
+
+/*========== points and other fixed timeslot movfeats ==========*/
 
 /*
  * We maintain two queues, one for reserved one for actually confirmed
@@ -95,651 +151,1017 @@ static void motion_done(Segment *move, MovPosComb actual) {
  *     currently     next in      after
  *     changing      line         that
  *
- * We increment cslot when we issue a POINT command to the PIC.
+ * We increment cslot when we consider the movfeat to move;
+ * for points and relays this is when we issue the command to the PIC.
  * In a request, the deadline represents the latest allowable value
  * of cslot just before that increment.
  */
 
-typedef unsigned PtSlot;
-typedef int PtSlotSigned;
+typedef unsigned FsqSlot;
+typedef int FsqSlotSigned;
 
-/* We think there are three states: Allocated, Reserved and Confirmed.
- * (plus of course Unallocated where we don't have a request at all).
- * These correspond to the indep code as follows:
- *
- *  indep state   pt state    queues checked and plan viable
- *   Unallocated   n/a         yes
- *   Allocated     Allocated   yes
- *   Reserved      Reserved    yes
- *   Confirmed     Confirmed   yes
- *   Erroneous     A/R/C       no
- *
- * Erroneous exists only after a failed reserve() or confirm() so it's
- * not that confusing to have this slightly malleable terminology.
- */
+#define FSQDN (~(FsqSlot)0)
 
-typedef struct {                 /* Allocated  Reserved   Confirmed       */
-                     /* in queue?    absent     reserved   confirmed      */
-  Change h;
-  PtSlot deadline;   /*              ~0         relative   absolute    <- */
-  MovPosComb actual; /*              undef      undef      see below      */
-  int n_motions;     /*              alloc'd    alloc'd    undone         */
-  Motion motions[];  /*   [0].i:     0          0          non-0       <- */
-                     /*  [..].i:     undef      undef      non-0          */
-                     /*   .posn:     undef      undef      defined        */
-} PointReq;
-  /* We can determine the the state by looking at the two
-   * `statedet' fields, marked <- above.
-   * There are also intermediate states where the req's
-   *  statedet fields do not agree with the queue it's on.
-   *  We write these as, for example,
-   *      AR   to mean  statedet says Allocated, but queued on pt_reserved
-   *      A?   to mean  statedet says Allocated, but may be queued
-   *  etc.  They are only allowed while we are in a pt_... method function.
-   */
-  /* PointReq.actual is subtly differnet to MovPosChange.actual,
-   * as follows:
-   *                              in MovPosChange     in PointReq
-   *  Position unknown              -1                  0
-   *  Position partly known         -1                  unknown feats are 0
-   *  Position completely known     exact               exact
-   *
-   * The partial knowledge positions can only occur in requests that
-   * are confirmed with as many motions as features, so we know that
-   * if we complete a request we know that we can copy actual out
-   * to MovPosChange.
-   *
-   * If we abandon a half-done change to a multi-feat segment
-   * we lose the partial knowledge.
-   */
+typedef struct {   /* state      Prep Resv  Inst Resv  Prep Conf  Inst Conf   */
+  Change h;        /* in queue?  absent     reserved   absent     confirmed   */
+  FsqSlot deadline; /*           relative   relative   absolute   absolute    */
+  int n_motions;    /*            1          1         num undone num undone  */
+  Motion motions[]; /*  [0].i:    0          0         non-0      non-0       */
+                    /*  .posn:    undef      undef     defined    defined     */
+} FsqReq;
 
-#define CDU_RECHARGE   250 /*ms*/
-#define POINT_MOVEMENT  50 /*ms*/
-#define PT_MAX_QUEUE  15
+#define FSQ_MAX_QUEUE  15
 
 typedef struct {
   int n;
-  PointReq *l[PT_MAX_QUEUE];
-} PointQueue;
+  FsqReq *l[FSQ_MAX_QUEUE];
+} FsqQueue;
 
-/*
- * CDU and point queue states:
- *
- *
- *    ____________                 pt_cdu_     conf'd
- *   /   points_  \                charged       .n
- *   |   all_      |
- *   |   abaondon  |
- *   |             V
- *   |from       INACTIVE              -1      0
- *   |any       <=Sta_Settling
- *  ^^^^^^^^     (start)
- *                 |
- *     ___________ |turning
- *    /           \| _on
- *   |             V
- *   |           CHARGING              0       any
- *   |          >=Sta_Resolving
- *   |             |
- *   |             |on_pic
- *   |             |_charged
- *   |             V
- *   ^           READY                 1       any
- *   |             |
- *   |             |pt_check_action
- *   |             | fires a point
- *    \___________/
- *
- */
+typedef struct FsqMethod FsqMethod;
 
-static PtSlot pt_cslot;
-static int pt_cdu_charged;
-static PointQueue pt_confirmed, pt_reserved;
+typedef struct {
+  /* set by fsq's client before fsq_init */
+  int slot_ms, lag_ms;
+  void (*move)(FsqMethod *m, const MovFeatInfo *mfi, int movfeatposn);
+  /* shared */
+  int ready; /* >0 means ready; set to 0 by fsq just before move */
+  /* fsq's client must arrange that these start out at all-bits-zero */
+  FsqSlot cslot;
+  FsqQueue confirmed, reserved;
+} FsqState;
+
+struct FsqMethod {
+  Method m;
+  FsqState f;
+  /* client may put things here */
+};
 
-static void pt_check_action(void);
-static ErrorCode pt_check_plan(void);
+static void fsq_check_action(FsqMethod *m);
+static ErrorCode fsq_check_plan(FsqMethod *m);
 
-static PtSlotSigned pt_maxdelay_reldeadline(int maxdelay_ms, int n_motions) {
-  if (maxdelay_ms==-1) return PT_MAX_QUEUE*16;
-  return (maxdelay_ms - POINT_MOVEMENT) / CDU_RECHARGE - n_motions;
+static FsqSlotSigned fsq_maxdelay_reldeadline(FsqMethod *m,
+                                             int maxdelay_ms, int n_motions) {
+  if (maxdelay_ms==-1) return FSQ_MAX_QUEUE*16;
+  return (maxdelay_ms - m->f.lag_ms) / m->f.slot_ms - n_motions;
 }
 
-static void pt_queue_remove_index(PointQueue *q, int index) {
+static void fsq_queue_remove_index(FsqQueue *q, int index) {
   q->n--;
   memmove(&q->l[index], &q->l[index+1], sizeof(q->l[0]) * (q->n - index));
 }
 
-static int pt_req_compar(const void *av, const void *bv) {
-  PointReq *const *a= av;
-  PointReq *const *b= av;
-  return (PtSlotSigned)((*b)->deadline - (*a)->deadline);
+static int fsq_req_compar_approx(const void *av, const void *bv) {
+  FsqReq *const *a= av;
+  FsqReq *const *b= av;
+  FsqSlotSigned dldiff= (*b)->deadline - (*a)->deadline;
+  return dldiff;
 }
 
-static void pt_queue_remove_item(PointQueue *q, PointReq *r) {
-  PointReq **entry;
-  entry= bsearch(r, q->l, q->n, sizeof(q->l[0]), pt_req_compar);
+static void fsq_queue_remove_item(FsqQueue *q, FsqReq *r) {
+  FsqReq **entry;
+  int i;
+  entry= bsearch(r, q->l, q->n, sizeof(q->l[0]), fsq_req_compar_approx);
   assert(entry);
-  pt_queue_remove_index(q, entry - q->l);
-}
 
-static void pt_dequeue(PointReq *r) { /* X->XA */
-  if (r->motions[0].i) {
-    pt_queue_remove_item(&pt_confirmed, r);
-  } else if (~r->deadline) {
-    pt_queue_remove_item(&pt_reserved, r);
-  } else {
-    return;
-  }
-  pt_check_plan();
-}
+#define SEARCH_CHECK do{                               \
+    if (q->l[i] == r) goto found;                      \
+    if (q->l[i]->deadline != r->deadline) break;       \
+}while(0)
+  for(i= entry - q->l;     i >= 0;   i--)  SEARCH_CHECK;
+  for(i= entry - q->l + 1; i < q->n; i++)  SEARCH_CHECK;
+  abort();
 
-static void pt_mark_as_allocated(PointReq *r) { /* AX->X */
-  /* Sets statedet fields for Allocated */
-  r->deadline= ~(PtSlot)0;
-  r->motions[0].i=0;
+ found:
+  fsq_queue_remove_index(q, i);
 }
 
+static FsqQueue *fsq_item_queue(FsqMethod *m, FsqReq *r)
+  { return r->motions[0].i ? &m->f.confirmed : &m->f.reserved; }
+
 #define WHICH(wh)                              \
   (whichr= wh##r,                              \
    whichwhen= wh##when,                                \
    wh++)
 
-static ErrorCode pt_check_plan(void) {
+static ErrorCode fsq_check_plan(FsqMethod *m) {
   /* Checks whether we can meet the currently queued commitments */
-  int future, conf, resv, whichwhen;
-  PointReq *whichr;
+  /* if this fails, indep machinery calls fsq_prepare to dequeue */
+  int future, conf, resv, whichwhen, DP;
+  FsqReq *whichr;
 
   conf=resv=0;
   future=0;
 
-  oprintf(DUPO("movpos/point") "  plan");
+  DPRINTF1(movpos,fsq, "%s   plan", m->m.pname);
 
-  /* If CDU is charged we can't do one right away */
-  if (!pt_cdu_charged) {
-    oprintf(UPO, " +");
+  /* If CDU isn't charged we can't do one right away */
+  if (m->f.ready<0) {
+    DPRINTF2(" +");
     future++;
   }
 
   for (;;) {
-    PointReq *confr= conf < pt_confirmed.n ? pt_confirmed.l[conf] : 0;
-    PointReq *resvr= resv < pt_reserved .n ? pt_reserved .l[resv] : 0;
+    FsqReq *confr= conf < m->f.confirmed.n ? m->f.confirmed.l[conf] : 0;
+    FsqReq *resvr= resv < m->f.reserved .n ? m->f.reserved .l[resv] : 0;
     if (!confr && !resvr) break;
-    oprintf(UPO," %d:",future);
-    int confwhen= confr ? confr->deadline - pt_cslot : INT_MAX;
-    int resvwhen= resvr ? resvr->deadline            : INT_MAX;
+    DPRINTF2(" %d:",future);
+    int confwhen= confr ? confr->deadline - m->f.cslot : INT_MAX;
+    int resvwhen= resvr ? resvr->deadline              : INT_MAX;
     if (future && resvwhen < confwhen) {
-      WHICH(resv);
-      oprintf(UPO,"~");
+      WHICH(resv);  DPRINTF2("~");
     } else if (confr) {
       WHICH(conf);
     } else {
-      oprintf(UPO,"-");
-      future++;
+      future++;     DPRINTF2("-");
       continue;
     }
-    oprintf(UPO, "%s/%s[%d@t+%d]", whichr->h.move->i->pname,
-           posnpname(whichr->h.move, whichr->h.intent),
+    DPRINTF2("%s/%s[%d@t+%d]", whichr->h.indep->move->i->pname,
+           movpos_pname(whichr->h.indep->move, whichr->h.indep->target),
            whichr->n_motions, whichwhen);
     if (future > whichwhen) {
-      oprintf(UPO,"!...bad\n");
+      DPRINTF2("!...bad\n");
       return EC_MovFeatTooLate;
     }
     future += whichr->n_motions;
   }
-  oprintf(UPO," ok\n");
+  DPRINTF2(" ok\n");
   return 0;
 }
 
 #undef WHICH
 
-static ErrorCode pt_enqueue(PointQueue *q, PointReq *r) { /* XA -> X */
-  int insat;                 /* ... where X is R or C and corresponds to q */
-                             /* or on error,   XA -> A */
+static ErrorCode fsq_queue_insert_item(FsqMethod *m, FsqQueue *q, FsqReq *r) {
+  int insat;
 
-  if (q->n == PT_MAX_QUEUE) {
+  if (q->n == FSQ_MAX_QUEUE)
     return EC_BufferFull;
-  }
 
   for (insat= q->n;
-       insat>0 && (PtSlotSigned)(r->deadline - q->l[insat-1]->deadline) < 0;
+       insat>0 && (FsqSlotSigned)(r->deadline - q->l[insat-1]->deadline) < 0;
        insat--)
     q->l[insat]= q->l[insat-1];
   q->l[insat]= r;
   q->n++;
 
-  return pt_check_plan();
-  /* if this fails, indep machinery calls pt_destroy which dequeues */
+  return 0;
 }
 
-/*---------- kind method entrypoints ----------*/
-
-static Change *point_allocate(int alloc_motions) {
-  PointReq *r;
-
-  assert(pt_cdu_charged>=0);
-  if (!alloc_motions)
-    /* we need at least one motion in the table so we can tell
-     *  the difference between the states by looking at motions[0].i */
-    alloc_motions= 1;
-
-  r= mmalloc(sizeof(*r) + alloc_motions * sizeof(r->motions[0]));
-  r->deadline= ~(PtSlot)0;
-  r->n_motions= alloc_motions;
-  r->motions[0].i= 0;
-  return (Change*)r;
+static void fsq_item_debug(FsqMethod *m, FsqReq *r, const char *opwhat) {
+  int DP;
+  Segment *move= r->h.indep->move;
+  DPRINTF1(movpos,fsq, "%s %s %s", m->m.pname, opwhat, move->i->pname);
+  if (r->motions[0].i) {
+    int i;
+    Motion *mo;
+    for (i=0, mo=r->motions; i<r->n_motions; i++, mo++)
+      DPRINTF2("/%s%d", mo->i->pname, (int)mo->posn);
+    DPRINTF2("\n");
+  } else {
+    DPRINTF2("(%d)\n", r->n_motions);
+  }
+}
+static ErrorCode fsq_enqueue(FsqMethod *m, FsqReq *r) { /* P->I; err: P->P */
+  if (!r) return 0;
+  fsq_item_debug(m,r,"enqueue");
+  return fsq_queue_insert_item(m, fsq_item_queue(m,r), r);
 }
 
-static ErrorCode point_reserve(Change *chg, Segment *move,
-                              int maxdelay_ms) {
-  PointReq *r= (PointReq*)chg;
-  PtSlotSigned reldeadline;
-
-  reldeadline= pt_maxdelay_reldeadline(maxdelay_ms, r->n_motions);
-  if (reldeadline <= 0) { pt_mark_as_allocated(r); return EC_MovFeatTooLate; }
-  r->deadline= reldeadline;
-  return pt_enqueue(&pt_reserved, r);
+static void fsq_dequeue(FsqMethod *m, FsqReq *r) { /* I->P */
+  if (!r) return;
+  fsq_item_debug(m,r,"dequeue");
+  fsq_queue_remove_item(fsq_item_queue(m,r), r);
 }
 
-static ErrorCode point_confirm(Change *chg, Segment *move,
-                              int n_motions, const Motion *motions,
-                              int maxdelay_ms) {
-  PointReq *r= (PointReq*)chg;
-  PtSlotSigned reldeadline;
-  int allow_failure;
-  ErrorCode ec;
+/*---------- method entrypoints ----------*/
 
-  oprintf(DUPO("movpos/point") "confirm %s n=%d maxdelay=%dms"
-         " (res: [%d@t+%d])\n",
-         move->i->pname, n_motions, maxdelay_ms,
-         r->n_motions, r->deadline);
+static ErrorCode fsq_prepare(Method *mm, const Segment *move,
+                            int n_motions, const Motion *motions,
+                            int ms, int confirmation,
+                            Change **chg_r, int *cost_r) {
+  FsqMethod *m= (void*)mm;
 
-  /* If the segment is moving, these motions are already based on the
-   * actual physical position which is stored in the existing request.
-   * So we try removing the existing request from the queue and put
-   * it back if it doesn't work.
-   */
+  assert(n_motions > 0);
 
-  if (n_motions > r->n_motions)
-    return EC_MovFeatReservationInapplicable;
-  assert(n_motions <= r->n_motions);
-  if (maxdelay_ms == -1) {
-    reldeadline= r->deadline;
-    if (!~r->deadline) reldeadline= pt_maxdelay_reldeadline(-1, n_motions);
-  } else {
-    reldeadline= pt_maxdelay_reldeadline(maxdelay_ms, n_motions);
-  }
-  allow_failure= reldeadline < (PtSlotSigned)r->deadline;
-  oprintf(DUPO("movpos/point") " reldeadline=[%d@t+%d] allow_failure=%d\n",
-         n_motions, reldeadline, allow_failure);
-
-  /* state A or R */
-  pt_dequeue(r);
-                                           /* states of existing: */
-  PointReq *existing=
-    move->moving ? (PointReq*)move->motion : 0;   /* U or C */
-  if (existing) {
-    oprintf(DUPO("movpos/point")
-           " existing %s n=%d deadline=t+%d\n",
-           existing->h.move->i->pname,
-           existing->n_motions,
-           existing->deadline - pt_cslot);
-    pt_dequeue(existing);                         /* U or CA */
-  }
-
-  /* state A or RA */
-  memcpy(r->motions, motions, sizeof(r->motions[0])*n_motions);
-  if (!n_motions) r->motions[0].i= move->i->movfeats;
-  assert(r->motions[0].i);
-  r->n_motions= n_motions;
-  r->deadline= reldeadline + pt_cslot;
+  FsqSlotSigned reldeadline= fsq_maxdelay_reldeadline(m, ms, n_motions);
+  if (reldeadline <= 0) return EC_MovFeatTooLate;
 
-  if (n_motions == move->i->n_movfeats)
-    r->actual= 0;
-  else
-    r->actual= chg->actual;
-  assert(r->actual >= 0);
-
-  /* state CA */
-  ec= pt_enqueue(&pt_confirmed, r);
-  oprintf(DUPO("movpos/point") " pt_enqueue=%s\n", ec2str(ec));
-  assert(allow_failure || !ec);
-
-  if (existing) {                                  /* CA */
-    if (ec) { /* state C but bad */
-      pt_dequeue(r); /* state CA */
-      pt_mark_as_allocated(r); /* state A */
-      ErrorCode ec_putback= pt_enqueue(&pt_confirmed, existing);
-      assert(!ec_putback);                         /* C */
-    } else { /* state C and good */
-      free(existing);                              /* U */
+  if (chg_r) {
+    int alloc_motions= confirmation ? n_motions : 1;
+      /* we need at least one motion in the table so we can tell
+       *  the difference between the states by looking at motions[0].i */
+    FsqReq *r= mmalloc(sizeof(*r) + alloc_motions * sizeof(r->motions[0]));
+    r->n_motions= n_motions;
+    if (confirmation) {
+      r->deadline= reldeadline + m->f.cslot;
+      memcpy(r->motions, motions, sizeof(*r->motions)*n_motions);
+    } else {
+      r->deadline= reldeadline;
+      r->motions[0].i= 0;
     }
+    *chg_r= &r->h;
+  }
+  if (cost_r) {
+    *cost_r= n_motions * m->f.slot_ms;
   }
-  /* either  ec=0   state C                            U
-   *     or  ec!=0  state A                            C
-   *     or  ec!=0  state C but bad                    C
-   */
-
-  if (ec) return ec;
 
-  move->moving= 1;
-  move->motion= chg;
-  move->movposcomb= -1;
-  ouposn_moving(chg);
-  pt_check_action();
   return 0;
 }
 
-static void point_destroy(Change *chg) { /* X->XA and then free it */
-  PointReq *r= (PointReq*)chg;
-  pt_dequeue(r);
+static void fsq_dispose(Method *mm, Change *chg) {
+  FsqReq *r= (FsqReq*)chg;
   free(r);
 }
 
-/*---------- actually firing points, yay! ----------*/
+static void fsq_remove(Method *mm, Change *remvchg) {
+  FsqMethod *m= (void*)mm;
+  FsqReq *remv= (FsqReq*)remvchg;
 
-static void pt_check_action(void) {
-  PicInsn piob;
+  fsq_dequeue(m, remv);
+}
+
+static ErrorCode fsq_install(Method *mm, Change *instchg) {
+  FsqMethod *m= (void*)mm;
+  FsqReq *inst= (FsqReq*)instchg;
+
+  return fsq_enqueue(m, inst);
+}
+
+static ErrorCode fsq_check(Method *mm) { 
+  FsqMethod *m= (void*)mm;
+  ErrorCode ec= fsq_check_plan(m);
+  return ec;
+}
+
+static void fsq_execute(Method *mm) {
+  FsqMethod *m= (void*)mm;
+  fsq_check_action(m);
+}
+
+/*---------- something to do ? ----------*/
+
+static void fsq_check_action(FsqMethod *m) {
+  /* client should call this after it sets ready */
   ErrorCode ec;
 
-  if (!pt_confirmed.n) {
+  if (!m->f.confirmed.n) {
     if (sta_state == Sta_Finalising) resolve_motioncheck();
     return;
   }
 
-  PointReq *r= pt_confirmed.l[0];
-
-  if (r->n_motions && pt_cdu_charged) {
-    /* look for something to fire */
-    Motion *m= &r->motions[--r->n_motions];
-    assert(m->posn < m->i->posns);
-    enco_pic_point(&piob, m->i->boob[m->posn]);
-    serial_transmit(&piob);
-    oprintf(UPO, "movpos %s point %s%d\n", r->h.move->i->pname,
-           m->i->pname, m->posn);
-    pt_cdu_charged= 0;
-    pt_cslot++;
-
-    MovPosComb above_weight= m->i->weight * m->i->posns;
-    MovPosComb above= r->actual / above_weight;
-    MovPosComb below= r->actual % m->i->weight;
-    r->actual= above*above_weight + m->posn*m->i->weight + below;
-    if (r->h.actual >= 0 || !r->n_motions)
-      r->h.actual= r->actual;
-    ouposn_moving(&r->h);
+  FsqReq *r= m->f.confirmed.l[0];
+
+  if (r->n_motions && m->f.ready>0) {
+    /* look for something to move */
+    Motion *mo= &r->motions[--r->n_motions];
+    assert(mo->posn < mo->i->posns);
+    m->f.ready= 0;
+    m->f.move(m, mo->i, mo->posn);
+    m->f.cslot++;
+
+    method_update_feature(&m->m, r->h.indep, mo);
   }
 
   if (!r->n_motions) {
-    /* look for something to report
-     * we can get things here other than from the above
-     * eg if we are asked to move the 
-     */
-    Segment *move= r->h.move;
-    assert(move->moving && move->motion == (Change*)r);
-    pt_queue_remove_index(&pt_confirmed,0);
-    pt_mark_as_allocated(r); /* now state A aka Done */
-    motion_done(move,r->h.actual);
-    free(r);
-    ec= pt_check_plan();  assert(!ec);
-    pt_check_action();
+    fsq_queue_remove_index(&m->f.confirmed, 0);
+    method_change_done(&m->m, &r->h);
+    m->m.dispose(&m->m, &r->h);
+
+    ec= fsq_check_plan(m);  assert(!ec);
+    fsq_check_action(m);
   }
 }
 
-/*---------- entrypoints from rest of program ----------*/
+/*========== points ==========*/
 
-void points_all_abandon(void) {
-  int i;
+/*
+ * CDU and point queue states:
+ *
+ *
+ *    ____________                             conf'd
+ *   /   points_  \                ready         .n
+ *   |   all_      |
+ *   |   abandon   |
+ *   |             V
+ *   |from       INACTIVE              -1      0
+ *   |any       <=Sta_Settling
+ *  ^^^^^^^^     (start)
+ *                 |
+ *     ___________ |turning
+ *    /           \| _on
+ *   |             V
+ *   |           CHARGING              0       any
+ *   |          >=Sta_Resolving
+ *   |             |
+ *   |             |on_pic
+ *   |             |_charged
+ *   |             V
+ *   ^           READY                 1       any
+ *   |             |
+ *   |             |fsq_check_action
+ *   |             | calls point_move which fires a point
+ *    \___________/
+ *
+ */
 
-  assert(!pt_reserved.n);
+#define CDU_RECHARGE   350 /*ms*/
+#define POINT_MOVEMENT  50 /*ms*/
 
-  for (i=0; i<pt_confirmed.n; i++) {
-    PointReq *r= pt_confirmed.l[i];
-    Segment *move= r->h.move;
-    assert(move->motion == (Change*)r);
-    motion_done(move,r->h.actual);
-    free(r);
-  }
-  pt_confirmed.n= 0;
-  pt_cdu_charged= -1;
+static ErrorCode point_prepare(Method *mm, const Segment *move,
+                              int n_motions, const Motion *motions,
+                              int ms, int confirmation,
+                              Change **chg_r, int *cost_r) {
+  FsqMethod *m= (void*)mm;
+  assert(m->f.ready>=0);
+  return fsq_prepare(mm,move, n_motions,motions,
+                    ms,confirmation, chg_r,cost_r);
+ }
+
+static void point_move(FsqMethod *m, const MovFeatInfo *mfi, int posn) {
+  /* actually firing points, yay! */
+  PicInsn piob;
+  enco_pic_point(&piob, mfi->boob[posn]);
+  serial_transmit(&piob);
 }
 
-void points_turning_on(void) {
-  pt_cdu_charged= 0;
+static void points_all_abandon(Method *mm) {
+  FsqMethod *m= (void*)mm;
+  m->f.ready= -1;
 }
 
+static FsqMethod points_method;
+
+void points_turning_on(void) {
+  FsqMethod *m= &points_method;
+  m->f.ready= 0;
+}
 void on_pic_charged(const PicInsnInfo *pii, const PicInsn *pi, int objnum) {
-  if (pt_cdu_charged<0) return;
-  pt_cdu_charged= 1;
-  pt_check_action();
+  FsqMethod *m= &points_method;
+  if (m->f.ready<0) return;
+  m->f.ready= 1;
+  fsq_check_action(m);
 }
 
-/*========== dummy `nomove' kind ==========*/
+static FsqMethod points_method= {
+  { "point",
+    point_prepare, fsq_dispose,
+    fsq_install, fsq_remove,
+    fsq_check, fsq_execute, points_all_abandon },
+  { .lag_ms= POINT_MOVEMENT, .slot_ms= CDU_RECHARGE, .move= point_move }
+};
+
+/*========== relays ==========*/
 
-static Change *nomove_allocate(int alloc_motions) {
-  oprintf(DUPO("movfeatkind-momove") "allocate %d\n",alloc_motions);
-  return mmalloc(sizeof(Change));
+/*
+ * Waggler states:
+ *
+ *    ____________                             conf'd
+ *   /  wagglers_ \                ready         .n
+ *   |   all_      |
+ *   |   abandon   |
+ *   |             V
+ *   |from       UNKNOWN               -1      0
+ *   |any       <=Sta_Settling
+ *  ^^^^^^^^     (start)
+ *                 |
+ *     ___________ |turning
+ *    /           \| _on
+ *   |             V
+ *   |           CHARGING              0       any
+ *   |          >=Sta_Resolving
+ *   |             |
+ *   |             |on_pic
+ *   |             |_charged
+ *   |             V
+ *   ^           READY                 1       any
+ *   |             |
+ *   |             |fsq_check_action
+ *   |             | calls waggle_do which switches a relay
+ *    \___________/
+ *
+ */
+
+static FsqMethod waggle;
+
+static void waggle_do(FsqMethod *m, const MovFeatInfo *mfi, int posn) {
+  /* actually setting relays */
+  PicInsn piob;
+  enco_pic_waggle(&piob, mfi->boob[0], posn);
+  serial_transmit(&piob);
+}
+
+static SegmentNum waggle_settle_seg;
+static int waggle_settle_feat;
+
+static void waggle_settle_check(void) {
+  for (;;) {
+    if (waggle_settle_seg >= info_nsegments) return;
+
+    Segment *seg= &segments[waggle_settle_seg];
+    if (waggle_settle_feat >= seg->i->n_movfeats) {
+      waggle_settle_seg++; waggle_settle_feat=0; continue;
+    }
+
+    const MovFeatInfo *feati= &seg->i->movfeats[waggle_settle_feat];
+    if (feati->kind != mfk_relay) {
+      waggle_settle_feat++; continue;
+    }
+
+    waggle.f.ready= 0;
+    waggle_do(&waggle, feati, (seg->movposcomb / feati->weight) & 1);
+    waggle_settle_feat++;
+  }
+}
+
+void waggle_startup_manual(void) {
+  waggle.f.ready= 1;
+}
+
+void waggle_settle(void) {
+  waggle_settle_seg= 0;
+  waggle_settle_feat= 0;
+  waggle.f.ready= 1;
+  waggle_settle_check();
 }
-static void nomove_destroy(Change *chg) {
-  free(chg);
+
+void on_pic_waggled(const PicInsnInfo *pii, const PicInsn *pi, int objnum) {
+  if (sta_state == Sta_Settling) {
+    waggle.f.ready= 1;
+    waggle_settle_check();
+  } else if (sta_state >= Sta_Resolving || sta_state == Sta_Manual) {
+    waggle.f.ready= 1;
+    fsq_check_action(&waggle);
+    return;
+  }
 }
 
-static ErrorCode nomove_reserve(Change *chg, Segment *move, int ms) {
-  oprintf(DUPO("movfeatkind-nomove") "reserve\n");
+static FsqMethod waggle= {
+  { "relay",
+    fsq_prepare, fsq_dispose,
+    fsq_install, fsq_remove,
+    fsq_check, fsq_execute, ignore_all_abandon },
+  { .lag_ms= 5, .slot_ms= 50, .move= waggle_do }
+};
+
+/*========== dummy `nomove' kind ==========*/
+
+typedef struct NomoveChange {
+  Change h;
+  DLIST_NODE(struct NomoveChange) inqueue;
+  int n_motions; /* 0 for reservations */
+  Motion motions[];
+} NomoveChange;
+
+typedef struct {
+  Method m;
+  NomoveChange *queuehead; /* contains confirmations only */
+} NomoveMethod;
+
+static ErrorCode nomove_prepare(Method *meth_in, const Segment *move,
+                               int n_motions, const Motion *motions,
+                               int ms, int confirming,
+                               Change **chg_r, int *cost_r) {
+  if (chg_r) {
+    NomoveChange *chg;
+    assert(n_motions>0);
+    if (!confirming) n_motions= 0;
+    chg= mmalloc(sizeof(*chg) + sizeof(Motion)*n_motions);
+    chg->n_motions= n_motions;
+    memcpy(chg->motions, motions, sizeof(Motion)*n_motions);
+  }
+  if (cost_r) {
+    *cost_r= 0;
+  }
   return 0;
 }
-static ErrorCode nomove_confirm(Change *chg, Segment *move, int n_motions,
-                      const Motion *motions, int ms) {
-  oprintf(DUPO("movfeatkind-nomove") "confirm\n");
-  nomove_destroy(chg);
+static void nomove_dispose(Method *mm, Change *remvchg) {
+  NomoveChange *remv= (void*)remvchg;
+  free(remv);
+}
+
+static ErrorCode nomove_install(Method *mm, Change *instchg) {
+  NomoveMethod *meth= (void*)mm;
+  NomoveChange *inst= (void*)instchg;
+  if (inst->n_motions)
+    DLIST1_PREPEND(meth->queuehead, inst, inqueue);
   return 0;
 }
+static void nomove_remove(Method *mm, Change *remvchg) {
+  NomoveMethod *meth= (void*)mm;
+  NomoveChange *remv= (void*)remvchg;
+  if (remv->n_motions)
+    DLIST1_REMOVE(meth->queuehead, remv, inqueue);
+}
+
+static ErrorCode nomove_check(Method *mm) { return 0; }
+
+static void nomove_execute(Method *mm) {
+  NomoveMethod *meth= (void*)mm;
+  NomoveChange *done;
+
+  while ((done= meth->queuehead)) {
+    assert(done->n_motions);
+    int i;
+    for (i=0; i<done->n_motions; i++)
+      method_update_feature(&meth->m, done->h.indep, &done->motions[i]);
+    method_change_done(&meth->m, &done->h);
+    DLIST1_REMOVE(meth->queuehead, done, inqueue);
+    nomove_dispose(&meth->m, &done->h);
+  }
+}
+
+static Method nomove_method= {
+  "nomove",
+  nomove_prepare, nomove_dispose,
+  nomove_install, nomove_remove,
+  nomove_check, nomove_execute, ignore_all_abandon
+};
 
 /*========== method-independent machinery ==========*/
 
-static const KindInfo methodinfos[]= {
- { "nomove", nomove_allocate, nomove_reserve, nomove_confirm, nomove_destroy },
- { "point",  point_allocate,  point_reserve,  point_confirm,  point_destroy  },
- { 0 }
+#define INDEP_DBG_FMT "<%p:%s/%s[%d]>"
+#define INDEP_DBG_ARGS(in) (in),                                       \
+    ((in)->move->i->pname), (movpos_pname((in)->move, (in)->target)),  \
+    ((in)->n_changes)
+
+#define INDEP_DPFX_FMT "movpos " INDEP_DBG_FMT " "
+#define INDEP_DPFX_ARGS(in) INDEP_DBG_ARGS((in))
+
+#define METH_DPFX_FMT "%s " INDEP_DBG_FMT " "
+#define METH_DPFX_ARGS(indep, meth) ((meth).pname), INDEP_DBG_ARGS((indep))
+
+static void indep_indep_done(Indep *indep);
+
+static Method *methods[]= {
+  [mfk_none] = (Method*)&nomove_method,
+  [mfk_point] = (Method*)&points_method,
+  [mfk_relay] = (Method*)&waggle,
+  0
 };
 
-static Change *mp_allocate(const KindInfo *ki, Segment *move,
-                          int alloc_motions, MovPosComb target) {
-  assert(sta_state >= Sta_Resolving);
-  Change *chg= ki->allocate(alloc_motions);
-  chg->ki=        ki;
-  chg->move=      move;
-  chg->intent=    target;
-  return chg;
+/*---------- entrypoints from methods ----------*/
+
+static void method_update_feature(Method *m, MovPosChange *indep,
+                                 const Motion *mo) {
+  ouposn_feat(indep->move, mo->i, mo->posn, m);
+  if (SOMEP(indep->actual))
+    indep->actual=
+      movposcomb_update_feature(indep->actual, mo->i, mo->posn);
+  ouposn_moving(indep);
+}
+
+static void method_change_done(Method *m, Change *chg) {
+  Indep *indep= chg->indep;
+  Change **search;
+
+  DPRINTF(movpos,meth, METH_DPFX_FMT "method_change_done...\n",
+         METH_DPFX_ARGS(indep,*m));
+
+  for (search=indep->changes; *search; search++)
+    if ((*search) == chg) goto found;
+  assert(!"change in indep");
+
+ found:
+  indep->n_changes--;
+  if (indep->n_changes) {
+    *search= indep->changes[indep->n_changes];
+    return;
+  }
+
+  if (!indep->refcount)
+    indep_indep_done(indep);
+}
+
+static void indep_indep_done(Indep *indep) {
+  /* all done */
+  Segment *move= indep->move;
+  move->moving= 0;
+  move->motion= 0;
+  move->movposcomb= indep->target;
+  ouposn_stable(move);
+  free(indep);
+}
+
+/*---------- internal core machinery ----------*/
+
+static Method *feature_method(const MovFeatInfo *feati) {
+  assert(feati->kind >= 0);
+  assert(feati->kind < ARRAY_SIZE(methods));
+  Method *meth= methods[feati->kind];
+  assert(meth);
+  return meth;
 }
 
 static int change_needed(const MovFeatInfo *feati,
                         MovPosComb startpoint, MovPosComb target) {
   int r;
-  r= startpoint<0 ||
-    (target - startpoint) / feati->weight % feati->posns;
-  oprintf(DUPO("movpos/change-needed") "%s:%s(%d*%d) %d..%d => %d\n",
-         methodinfos[feati->kind].pname, feati->pname,
-         feati->posns, feati->weight,
-         startpoint, target, r);
+  r= !SOMEP(startpoint) ||
+    (target / feati->weight) % feati->posns -
+    (startpoint / feati->weight) % feati->posns;
+  if (DEBUGP(movpos,eval))
+    DPRINTFA(" { %s:%s(%d*%d) %d..%d => %d }",
+            feature_method(feati)->pname, feati->pname,
+            feati->posns, feati->weight,
+            startpoint, target, r);
   return r;
-}  
+}
+
+static void indep_dispose(MovPosChange *indep) {
+  if (!indep) return;
+
+  DPRINTF(movpos,intern, INDEP_DPFX_FMT "dispose...\n",
+         INDEP_DPFX_ARGS(indep));
+
+  int changei;
+  for (changei=0; changei<indep->n_changes; changei++) {
+      Change *chg= indep->changes[changei];
+      if (!chg) continue;
+
+      Method *meth= chg->meth;
+      DPRINTF(movpos,meth, METH_DPFX_FMT "dispose...\n",
+             METH_DPFX_ARGS(indep,*meth));
+      meth->dispose(meth, chg);
+  }
+  free(indep);
+}
+
+#define EVAL_MAX_METHODS 2
+#define EVAL_MAX_MOTIONS 2
+
+static ErrorCode indep_prepare(Segment *move, MovPosComb target,
+                              MovPosComb startpoint,
+                              int ms, int confirming,
+                              MovPosChange **indep_r /* 0 ok */,
+                              int *cost_r /* 0 ok */) {
+  static int n_meths;
+  static Method *meths[EVAL_MAX_METHODS];
+  static int n_motions[EVAL_MAX_METHODS];
+  static Motion motions[EVAL_MAX_METHODS][EVAL_MAX_MOTIONS];
 
-static int evaluate_target(Segment *move, MovPosComb target,
-                          MovPosComb startpoint, MovFeatKind *kind_r) {
-  /* returns number of features which have to change to reach target,
-   * or -1 for mixed kinds.  kind_r may be 0. */
   const SegmentInfo *movei= move->i;
-  int feat, tchanges;
-  const MovFeatInfo *feati;
-  MovFeatKind kind;
+  int feat, DP;
+
+  MovPosChange *indep=0;
 
-  oprintf(DUPO("movpos/eval") "%s/%s <-%s\n",
-         move->i->pname, posnpname(move,target), posnpname(move,startpoint));
+  DPRINTF1(movpos,eval, "movpos prepare %s/%s <-%s", move->i->pname,
+          movpos_pname(move,target), movpos_pname(move,startpoint));
 
-  if (startpoint<0) {
+  if (!SOMEP(startpoint)) {
     startpoint= movpos_poscomb_actual(move);
-    oprintf(DUPO("movpos/eval") "  actual <-%s\n",
-           posnpname(move,startpoint));
+    DPRINTF2(" actual <-%s", movpos_pname(move,startpoint));
   }
 
-  for (feat=0, feati=movei->movfeats, tchanges=0, kind= mfk_none;
-       feat<movei->n_movfeats;
-       feat++, feati++) {
+  n_meths= 0;
+
+  for (feat=0; feat<movei->n_movfeats; feat++) {
+    const MovFeatInfo *feati= &movei->movfeats[feat];
     if (!change_needed(feati,startpoint,target)) continue;
-    tchanges++;
-    if (kind && feati->kind != kind) return -1;
-    kind= feati->kind;
+    MovPosComb posn= target / feati->weight % feati->posns;
+    Method *meth= feature_method(feati);
+
+    int methi;
+    for (methi=0; methi<n_meths; methi++)
+      if (meths[methi] == meth) goto found_method;
+    /* need a new method */
+    methi= n_meths++;
+    if (methi >= EVAL_MAX_METHODS) {
+      DPRINTF2(" MovFeatTooManyMethods methi=%d\n",methi);
+      return EC_MovFeatTooManyMethods;
+    }
+    meths[methi]= meth;
+    n_motions[methi]= 0;
+    DPRINTF2(" meths[%d]=%s", methi,meth->pname);
+
+  found_method:;
+    int motioni= n_motions[methi]++;
+    if (motioni >= EVAL_MAX_MOTIONS) {
+      DPRINTF2(" MovFeatTooManyMotions motioni=%d\n",motioni);
+      return EC_MovFeatTooManyMotions;
+    }
+    DPRINTF2(" motion[%d][%d]=%s%d", methi, motioni, feati->pname,posn);
+    motions[methi][motioni].i= feati;
+    motions[methi][motioni].posn= posn;
+  }
+
+  if (indep_r) {
+    DPRINTF2(" alloc");
+    indep= mmalloc(sizeof(*indep) + sizeof(Change*) * n_meths);
+    indep->move= move;
+    indep->actual= startpoint;
+    indep->target= target;
+    indep->n_changes= n_meths;
+    indep->refcount= 0;
+    memset(indep->changes, 0, sizeof(Change*) * n_meths);
+  }
+  DPRINTF2("\n");
+
+  int totalcost= 0;
+  int changei;
+  ErrorCode ec;
+
+  for (changei=0; changei<n_meths; changei++) {
+    Method *meth= meths[changei];
+    int thiscost= 0;
+    meth->needcheck= 1;
+    meth->needexec= 1;
+
+    if (indep_r)
+      DPRINTF(movpos,meth, METH_DPFX_FMT "prepare n_motions=%d...\n",
+             METH_DPFX_ARGS(indep,*meth), n_motions[changei]);
+    else
+      DPRINTF(movpos,meth, "%s prepare (costing) n_motions=%d...\n",
+             meth->pname, n_motions[changei]);
+
+    ec= meth->prepare(meth,move,
+                     n_motions[changei],motions[changei],
+                     ms, confirming,
+                     indep ? &indep->changes[changei] : 0,
+                     &thiscost);
+    if (ec) goto x;
+    if (indep) {
+      Change *chg= indep->changes[changei];
+      chg->meth= meth;
+      chg->indep= indep;
+      chg->installed= 0;
+    }
+    totalcost += thiscost;
+  }
+
+  if (indep_r) *indep_r= indep;
+  if (cost_r) *cost_r= totalcost;
+
+  if (indep_r)
+    DPRINTF(movpos,eval, INDEP_DPFX_FMT "prepare cost=%d ok\n",
+           INDEP_DPFX_ARGS(indep), totalcost);
+  else
+    DPRINTF(movpos,eval, "movpos prepare %s/%s cost=%d ok\n",
+           move->i->pname, movpos_pname(move,target), totalcost);
+  return 0;
+
+ x:
+  indep_dispose(indep);
+  DPRINTF(movpos,entry, "movpos prepare %s/%s err=%s\n", move->i->pname,
+         movpos_pname(move,target), ec2str(ec));
+  return ec;
+}
+
+static void indep_remove(MovPosChange *remv) {
+  if (!remv) return;
+  
+  DPRINTF(movpos,intern, INDEP_DPFX_FMT "remove...\n",
+         INDEP_DPFX_ARGS(remv));
+
+  int i;
+  for (i=0; i<remv->n_changes; i++) {
+    Change *chg= remv->changes[i];
+    if (!chg->installed) continue;
+    Method *meth= chg->meth;
+    meth->needexec= 1;
+
+    DPRINTF(movpos,meth, METH_DPFX_FMT "remove...\n",
+           METH_DPFX_ARGS(remv,*meth));
+    meth->remove(meth, chg);
+    chg->installed= 0;
+  }
+}
+
+static ErrorCode
+indep_install(MovPosChange *inst, int checknow) {
+  /* if this fails, inst may be left partially installed */
+  if (!inst) return 0;
+  
+  DPRINTF(movpos,intern, INDEP_DPFX_FMT "install checknow=%d...\n",
+         INDEP_DPFX_ARGS(inst), checknow);
+
+  int i;
+  ErrorCode ec;
+  for (i=0; i<inst->n_changes; i++) {
+    Change *chg= inst->changes[i];
+    assert(!chg->installed);
+    Method *meth= chg->meth;
+
+    meth->needexec= 1;
+    ec= meth->install(meth, chg);
+    DPRINTF(movpos,meth, METH_DPFX_FMT "install=%s\n",
+           METH_DPFX_ARGS(inst,*meth), ec2str(ec));
+    if (ec) goto x;
+    chg->installed= 1;
+    meth->needcheck= 1;
+
+    if (checknow) {
+      ec= meth->check(meth);
+      DPRINTF(movpos,meth, METH_DPFX_FMT "check=%s\n",
+             METH_DPFX_ARGS(inst,*meth), ec2str(ec));
+      if (ec) goto x;
+      meth->needcheck= 0;
+    }
   }
+  return 0;
+
+ x:
+  return ec;
+}
 
-  if (kind_r) *kind_r= kind;
-  oprintf(DUPO("movpos/eval") "changes=%d kind=%s\n",
-         tchanges, methodinfos[kind].pname);
-  return tchanges;
+static void indep_check_execute(void) {
+  DPRINTF(movpos,intern, "movpos indep_check_execute\n");
+
+  Method **methwalk, *meth;
+  for (methwalk= methods; (meth= *methwalk); methwalk++) {
+    if (meth->needcheck) {
+      ErrorCode ec= meth->check(meth);
+      DPRINTF(movpos,meth, "%s check=%s\n", meth->pname, ec2str(ec));
+      assert(!ec);
+      meth->needcheck= 0;
+    }
+    if (meth->needexec) {
+      meth->needexec= 0;
+      DPRINTF(movpos,meth, "%s execute...\n", meth->pname);
+      meth->execute(meth);
+    }
+  }
+}
+
+/*---------- entrypoints from rest of program ----------*/
+
+ErrorCode
+movpos_reserve(Segment *move, int maxdelay_ms, MovPosChange **res_r,
+              MovPosComb target, MovPosComb startpoint /*as for findcomb*/) {
+  ErrorCode ec;
+  MovPosChange *indep= 0;
+
+  DPRINTF(movpos,entry, "movpos reserve %s/%s maxdelay=%dms startpoint=%s\n",
+         move->i->pname, movpos_pname(move,target),
+         maxdelay_ms, movpos_pname(move,startpoint));
+
+  ec= indep_prepare(move,target, startpoint,
+                   maxdelay_ms,0,
+                   &indep, 0);
+  if (ec) return ec;
+
+  ec= indep_install(indep, 1);
+  if (ec) goto x;
+
+  indep_check_execute();
+
+  DPRINTF(movpos,entry, "movpos reserve %s/%s ok\n",
+         move->i->pname, movpos_pname(move,target));
+  *res_r= indep;
+  return 0;
+
+ x:
+  indep_remove(indep);
+  indep_dispose(indep);
+  indep_check_execute();
+
+  DPRINTF(movpos,entry, "movpos reserve %s/%s err=%s\n",
+         move->i->pname, movpos_pname(move,target), ec2str(ec));
+  return ec;
 }
 
 ErrorCode movpos_findcomb_bysegs(Segment *back, Segment *move, Segment *fwd,
                                 MovPosComb startpoint, MovPosComb *chosen_r) {
   const SegmentInfo *movei= move->i;
   MovPosComb tcomb, bestcomb=-1;
-  int tchanges, bestchanges=INT_MAX;
+  int tcost, bestcost=INT_MAX;
   const SegPosCombInfo *pci;
 
+  DPRINTF(movpos,eval, "movpos_findcomb_bysegs %s-%s-%s <-%s\n",
+         back ? back->i->pname : "*", move->i->pname,
+         fwd  ? fwd ->i->pname : "*", movpos_pname(move, startpoint));
+
   for (tcomb=0, pci=movei->poscombs;
        tcomb<movei->n_poscombs;
        tcomb++, pci++) {
+    /* these next assignments may generate segments[-1] but we don't
+     * care because that won't compare equal to back or fwd */
     Segment *tback= &segments[pci->link[1].next];
     Segment *tfwd=  &segments[pci->link[0].next];
+
+    DPRINTF(movpos,intern, "movpos_findcomb_bysegs  ... %s : %s-%s-%s\n",
+           movpos_pname(move, tcomb),
+           SOMEP(pci->link[1].next) ? tback->i->pname : "#",
+           move->i->pname,
+           SOMEP(pci->link[0].next) ? tfwd->i->pname : "#");       
+
     if (back && !(back==tback || back==tfwd)) continue;
     if (fwd  && !(fwd ==tback || fwd ==tfwd)) continue;
 
     /* we have to search for the one which is least effort */
-    tchanges= evaluate_target(move,tcomb,startpoint,0);
-
-    if (tchanges==-1)
-      /* mixed kinds */
-      tchanges= INT_MAX-1;
+    ErrorCode ec= indep_prepare(move,tcomb,startpoint, -1,0, 0,&tcost);
+    if (ec) return ec;
 
-    if (tchanges >= bestchanges) /* prefer low-numbered movposcombs */
+    if (tcost >= bestcost) /* prefer low-numbered movposcombs */
       continue;
 
     bestcomb= tcomb;
-    bestchanges= tchanges;
+    bestcost= tcost;
   }
+  DPRINTF(movpos,entry, "movpos_findcomb_bysegs %s..%s..%s <-%s => %s/%s\n",
+         back ? back->i->pname : "-", move->i->pname,
+         fwd  ? fwd ->i->pname : "-", movpos_pname(move, startpoint),
+         move->i->pname, movpos_pname(move,bestcomb));
+
   if (chosen_r) *chosen_r= bestcomb;
   return
-    bestchanges==INT_MAX ? EC_MovFeatRouteNotFound :
-    bestchanges==INT_MAX-1 ? EC_MovFeatKindsCombination :
+    bestcost==INT_MAX ? EC_MovFeatRouteNotFound :
     0;
 }
 
 ErrorCode movpos_change(Segment *move, MovPosComb target,
-                       int maxdelay_ms, MovPosChange *chg) {
-  const SegmentInfo *movei= move->i;
-  const MovFeatInfo *feati;
-  int feat;
+                       int maxdelay_ms, MovPosChange *resv) {
+  int DP;
   MovPosComb actual;
   ErrorCode ec;
-  MovFeatKind kind= mfk_none;
 
   if (!move->moving) {
     actual= move->movposcomb;
     assert(!move->motion);
   } else {
-    kind= move->motion->ki - methodinfos;
     actual= move->motion->actual;
   }
 
-  oprintf(DUPO("movpos/change") "%s/%s maxdelay=%dms actual=%s\n",
-         move->i->pname, posnpname(move,target),
-         maxdelay_ms, posnpname(move, actual));
-  if (chg) oprintf(DUPO("movpos/change") " chg=%s:%s/%s\n",
-                  chg->ki->pname, chg->move->i->pname,
-                  posnpname(chg->move, chg->intent));
-
-  { /* provide horizon for visibility of motions[] */
-    int n_motions=0;
-    Motion motions[movei->n_movfeats];
-
-    for (feat=0, feati=movei->movfeats;
-        feat<movei->n_movfeats;
-        feat++, feati++) {
-      if (!change_needed(feati,actual,target))
-       continue;
-      MovPosComb posn= target / feati->weight % feati->posns;
-      if (kind) {
-       if (feati->kind != kind) { ec= EC_MovFeatKindsCombination; goto x; }
-      } else {
-       kind= feati->kind;
-      }
-      motions[n_motions].i= feati;
-      motions[n_motions].posn= posn;
-      n_motions++;
-    }
+  DPRINTF1(movpos,entry, "movpos change %s/%s maxdelay=%dms actual=%s",
+          move->i->pname, movpos_pname(move, target),
+          maxdelay_ms, movpos_pname(move, actual));
+  if (resv) DPRINTF2(" resv=%s/%s",
+                    resv->move->i->pname,
+                    movpos_pname(resv->move, resv->target));
+  if (move->motion) DPRINTF2(" oldmotion=/%s",
+                            movpos_pname(move, move->motion->target));
+  DPRINTF2("\n");
+
+  MovPosChange *inst= 0;
+
+  ec= indep_prepare(move,target, actual,
+                   maxdelay_ms,1,
+                   &inst, 0);
+  if (ec) goto x;
 
-    const KindInfo *ki= &methodinfos[kind];
+  indep_remove(resv);
+  indep_remove(move->motion);;
 
-    if (chg) {
-      if (chg->ki != ki ||
-         chg->move != move ||
-         chg->intent != target)
-       return EC_MovFeatReservationInapplicable;
-    } else {
-      chg= mp_allocate(ki,move,n_motions,target);
-    }
-    chg->actual= actual;
+  ec= indep_install(inst, 1);
+  if (ec) goto x;
 
-    oprintf(DUPO("movpos/change") "confirm %s:%d...\n", ki->pname, n_motions);
-    ec= ki->confirm(chg, move, n_motions, motions, maxdelay_ms);
-    oprintf(DUPO("movpos/change") "confirm => %s\n",errorcodelist[ec]);
-    if (ec) goto x;
-  }
-  return 0;
+  indep_dispose(resv);
+  indep_dispose(move->motion);
 
- x:
-  movpos_unreserve(chg);
-  return ec;
-}
-
-ErrorCode
-movpos_reserve(Segment *move, int maxdelay_ms, MovPosChange **res_r,
-              MovPosComb target, MovPosComb startpoint /*as for findcomb*/) {
-  MovFeatKind kind= mfk_none;
-  ErrorCode ec;
-  int nchanges;
+  move->motion= inst;
+  move->moving= 1;
 
-  oprintf(DUPO("movpos/reserve") "%s/%s maxdelay=%dms startpoint=%s\n",
-         move->i->pname, posnpname(move,target),
-         maxdelay_ms, posnpname(move,startpoint));
+  inst->refcount++; /* prevents method_change_done from destroying it */
 
-  nchanges= evaluate_target(move,target,startpoint,&kind);
-  if (nchanges==-1) return EC_MovFeatKindsCombination;
+  ouposn_moving(inst);
+  indep_check_execute();
 
-  const KindInfo *ki= &methodinfos[kind];
-  oprintf(DUPO("movpos/reserve") "allocate %s:%d...\n", ki->pname, nchanges);
-  Change *chg= mp_allocate(ki, move, nchanges, target);
-  ec= ki->reserve(chg, move, maxdelay_ms);
-  oprintf(DUPO("movpos/reserve") "reserve => %s\n",errorcodelist[ec]);
-  if (ec) goto x;
+  inst->refcount--;
+  if (!inst->n_changes)
+    /* oh! */
+    indep_indep_done(inst);
 
-  *res_r= chg;
+  DPRINTF(movpos,entry, "movpos change %s/%s ok\n",
+         move->i->pname, movpos_pname(move, target));
   return 0;
 
  x:
-  movpos_unreserve(chg);
+  indep_remove(inst);
+  indep_dispose(inst);
+  ec= indep_install(move->motion, 0);  assert(!ec);
+  ec= indep_install(resv, 0);          assert(!ec);
+  indep_check_execute();
+
+  DPRINTF(movpos,entry, "movpos change %s/%s err=%s\n",
+         move->i->pname, movpos_pname(move, target), ec2str(ec));
   return ec;
 }
 
-void movpos_unreserve(MovPosChange *res) {
-  if (!res) return;
-  oprintf(DUPO("movpos/unreserve") "%s:%s/%s\n",
-         res->ki->pname, res->move->i->pname,
-         posnpname(res->move, res->intent));
-  res->ki->destroy(res);
+void movpos_unreserve(MovPosChange *resv) {
+  if (!resv) return;
+  DPRINTF(movpos,entry, "movpos unreserve %s/%s...\n",
+         resv->move->i->pname, movpos_pname(resv->move, resv->target));
+  indep_remove(resv);
+  indep_dispose(resv);
+  indep_check_execute();
 }
 
-MovPosComb movpos_poscomb_actual(Segment *seg) {
return seg->moving ? seg->motion->actual : seg->movposcomb;
+MovPosComb movpos_change_intent(MovPosChange *indep) {
 return indep->target;
 }
 
-MovPosComb movpos_change_intent(MovPosChange *chg) {
-  return chg->intent;
+void motions_all_abandon(void) {
+  Method **meth;
+  SEG_IV;
+  DPRINTF(movpos,entry, "movpos motions_all_abandon...\n");
+  FOR_SEG {
+    if (!seg->moving) continue;
+
+    MovPosChange *abandon= seg->motion;
+    indep_remove(abandon);
+    seg->movposcomb= abandon->actual;
+    seg->moving= 0;
+    seg->motion= 0;
+    indep_dispose(abandon);
+  }
+  for (meth=methods; *meth; meth++)
+    (*meth)->all_abandon(*meth);
 }