chiark / gitweb /
wip new safety - before change way trackloc movpos is done
authorian <ian>
Sun, 20 Apr 2008 14:35:26 +0000 (14:35 +0000)
committerian <ian>
Sun, 20 Apr 2008 14:35:26 +0000 (14:35 +0000)
hostside/errorcodes.h.gen
hostside/movpos.c
hostside/safety.c
hostside/safety.h

index 49d802513c3357a417030e4b13a4da96ec85b272..d8570b513c806b8e5aa42be1f48ec5adeebcd6fe 100755 (executable)
@@ -2,13 +2,14 @@
 
 @f= qw(
        OK
-       Invalid
-       Safety
        MovFeatTooLate
        MovFeatKindsCombination
+       MovFeatReservationInapplicable
        BufferFull
        BadCmd
        NotFound
+       ProblemPredicted
+       SignalStopHorizonReached
        );
 
 
index e883968cce5ba238ced2805db3f3bd9c663153c0..973d583169459873e00dcc882b18208e18220896 100644 (file)
@@ -33,6 +33,7 @@ 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()    */
   /* kind-specific data follows */ /*  varies     kind-specific code, varies */
 } Change;
   /* `actual' contains the kind's public opinion about the physical
@@ -311,6 +312,8 @@ fprintf(stderr,"  point confirm\n");
    * it back if it doesn't work.
    */
 
+  if (n_motions >= r->n_motions)
+    return EC_MovFeatReservationInapplicable;
   assert(n_motions <= r->n_motions);
   newdeadline= pt_maxdelay_reldeadline(maxdelay_ms) + pt_cslot;
   allow_failure= newdeadline < r->deadline;
@@ -471,71 +474,82 @@ static const KindInfo methodinfos[]= {
 };
 
 static Change *mp_allocate(const KindInfo *ki, Segment *move,
-                          int alloc_motions) {
+                          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;
 }
 
-static int movpos__evaluate_target(Segment *move, MovPosComb target) {
-  /* returns number of features which have to change to reach target */
+static int change_needed(const MovFeatInfo *feati, MovPosComb target,
+                       MovPosComb startpoint) {
+  return
+    startpoint<0 ||
+    (target - startpoint) / feati->weight % feati->posns;
+}  
+
+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;
-  MovPosComb actual;
   const MovFeatInfo *feati;
+  MovFeatKind kind;
 
-  actual= movpos_poscomb_actual(move);
+  if (startpoint<0) startpoint= movpos_poscomb_actual(move);
 
-  for (feat=0, feati=movei->movfeats, tchanges=0;
+  for (feat=0, feati=movei->movfeats, tchanges=0, kind= mfk_none;;
        feat<movei->n_movfeats;
-       feat++)
-    if (actual<0 || (target - actual) / feati->weight % feati->posns)
-      tchanges++;
+       feat++, feati++) {
+    if (!change_needed(feati,target,startpoint)) continue;
+    tchanges++;
+    if (kind && feati->kind != kind) return -1;
+    kind= feati->kind;
+  }
+
+  if (kind_r) *kind_r= kind;
   return tchanges;
 }
 
-ErrorCode
-movpos_change_bysegs(Segment *back, Segment *move, Segment *fwd,
-                    int maxdelay_ms, MovPosChange *chg) {
+ErrorCode movpos_findcomb_bysegs(Segment *back, Segment *move, Segment *fwd,
+                                MovPosComb startpoint, MovPosComb *chosen_r) {
   const SegmentInfo *movei= move->i;
-  MovPosComb tcomb, bestcomb=0;
+  MovPosComb tcomb, bestcomb=-1;
   int tchanges, bestchanges=INT_MAX;
   const SegPosCombInfo *pci;
 
-  //fprintf(stderr,"moving %s\n",move->i->pname);
   for (tcomb=0, pci=movei->poscombs;
        tcomb<movei->n_poscombs;
        tcomb++, pci++) {
-    //fprintf(stderr," tcomb %lu\n",tcomb);
     Segment *tback= &segments[pci->backwards.next];
     Segment *tfwd=  &segments[pci->forwards .next];
-    //fprintf(stderr,"  tback %s tfwd %s\n",
-    // tback?tback->i->pname:"-",
-    // tfwd?tfwd->i->pname:"-"
-    // );
     if (back && !(back==tback || back==tfwd)) continue;
     if (fwd  && !(fwd ==tback || fwd ==tfwd)) continue;
 
     if (movei->n_movfeats>1) {
       //fprintf(stderr,"  several feats\n");
       /* we have to search for the one which is least effort, then */
-      tchanges= movpos__evaluate_target(move, tcomb);
+      tchanges= evaluate_target(move,tcomb,startpoint,0);
       if (tchanges >= bestchanges) /* prefer low-numbered movposcombs */
        continue;
+      if (tchanges==-1) {
+       tchanges= INT_MAX-1;
+       /* fall through and update */
+      }
     } else {
       tchanges= 1;
     }
-    //fprintf(stderr,"  best %lu changes %d\n",tcomb,tchanges);
     bestcomb= tcomb;
     bestchanges= tchanges;
   }
-
-  //fprintf(stderr," best %lu changes %d\n",bestcomb,bestchanges);
-  if (bestchanges==INT_MAX) { movpos_unreserve(chg); return EC_Invalid; }
-
-  return movpos_change(move, tcomb, maxdelay_ms, chg);
+  if (*chosen_r) *chosen_r= bestcomb;
+  return
+    tchanges==INT_MAX ? EC_Invalid :
+    tchanges==INT_MAX-1 ? EC_MovFeatKindsCombination :
+    0;
 }
 
 ErrorCode movpos_change(Segment *move, MovPosComb target,
@@ -547,11 +561,12 @@ ErrorCode movpos_change(Segment *move, MovPosComb target,
   ErrorCode ec;
   MovFeatKind kind= mfk_none;
 
-  if (move->moving) {
-    kind= move->moving->ki - methodinfos;
-    actual= move->moving->actual;
-  } else {
+  if (!move->moving) {
     actual= move->movposcomb;
+    assert(!move->motion);
+  } else {
+    kind= move->motion->ki - methodinfos;
+    actual= move->motion->actual;
   }
 
   {
@@ -564,7 +579,7 @@ fprintf(stderr," motions...  best=%lu actual=%ld\n",target,actual);
         feat++, feati++) {
 fprintf(stderr,"  checking %s w=%lu posns=%d\n",
        feati->pname,feati->weight,(int)feati->posns);
-      if (actual>=0 && !((target - actual) / feati->weight % feati->posns))
+      if (!change_needed(feati,actual,target))
        continue;
       MovPosComb posn= target / feati->weight % feati->posns;
 fprintf(stderr,"    motion %s %lu kind=%d\n",feati->pname,posn,kind);
@@ -581,9 +596,12 @@ fprintf(stderr,"    motion %s %lu kind=%d\n",feati->pname,posn,kind);
     const KindInfo *ki= &methodinfos[kind];
 
     if (chg) {
-      assert(move == chg->move);
+      if (chg->ki != ki ||
+         chg->move != move ||
+         chg->intent != intent)
+       return EC_MovFeatReservationInapplicable;
     } else {
-      chg= mp_allocate(ki,move,n_motions);
+      chg= mp_allocate(ki,move,n_motions,target);
     }
     chg->actual= actual;
 
@@ -600,22 +618,18 @@ fprintf(stderr," confirming gave %s\n",errorcodelist[ec]);
 }
 
 ErrorCode
-movpos_reserve(Segment *move, int maxdelay_ms, MovPosChange **res_r) {
+movpos_reserve(Segment *move, int maxdelay_ms, MovPosChange **res_r,
+              MovPosComb target, MovPosComb startpoint /*as for findcomb*/) {
   MovFeatKind kind= mfk_none;
   const MovFeatInfo *feati;
   ErrorCode ec;
-  int feat;
+  int feat, nchanges;
 
-  for (feat=0, feati=move->i->movfeats;
-       feat<move->i->n_movfeats;
-       feat++, feati++)
-    if (kind) {
-      if (feati->kind != kind) return EC_MovFeatKindsCombination;
-      kind= feati->kind;
-    }
+  nchanges= evaluate_target(move,target,startpoint,&kind);
+  if (nchanges=-1) return EC_MovFeatKindsCombination;
 
   const KindInfo *ki= &methodinfos[kind];
-  Change *chg= mp_allocate(ki, move, move->i->n_movfeats);
+  Change *chg= mp_allocate(ki, move, move->i->n_movfeats, target);
   ec= ki->reserve(chg, move, maxdelay_ms);
   if (ec) goto x;
 
@@ -635,3 +649,7 @@ void movpos_unreserve(MovPosChange *res) {
 MovPosComb movpos_poscomb_actual(Segment *seg) {
  return seg->moving ? seg->moving->actual : seg->movposcomb;
 }
+
+MovPosComb movpos_change_intent(MovPosChange *chg) {
+  return chg->intent;
+}
index 64c54a2e0cf41741a43f7a4088a4e2a64f67fd46..a2d9969231abf67b43c5ec2d2b79f7068f149429 100644 (file)
@@ -60,18 +60,21 @@ Segment *segments;
  * If we _have_ claimed it then it had better be predicted to be
  * vacant by the time we get there (pred_present clear, see below).
  *
- * If it is currently moving then that must be OK because we checked
- * it before (except that if we're trying to increase our speed that
- * may not be OK and we must reject the speed increase because we
- * can't easily check whether the movement would happen in time).
+ * Regarding moveable features, we continuously develop our plan,
+ * as represented in route_plan and route_reservation.
  *
- * If it is not currently moving and is set against us, then we need
- * to arrange that we set it appropriately: if we've just claimed the
- * segment then we can ask movfeat to set it for us (using any
- * reservation which we may have stashed away) (and fail if it
- * declines); otherwise we ask movfeat to give us a reservation valid
- * for the time interval between the predicted vacation and present
- * times.
+ * If the feature is currently moving and owned by us then that must
+ * be OK because we checked it before (except that if we're trying to
+ * increase our speed that may not be OK and we must replan the speed
+ * increase to check whether the movement will happen in time).
+ *
+ * If it is not currently moving and is set against us, then we plan
+ * to set it.  If there is already a plan (ie, a reservation) and
+ * we're not accelerating then that's fine.  Otherwise we make one: we
+ * determine the desired movposcomb make a reservation to move it in
+ * time.  (If the segment is already owned by us that means a
+ * reservation to move it between when the tail leaves and the nose
+ * enters.)
  *
  * When predicted foredetect enters a new segment we add the new
  * segment to the tally of segments of the particular kind and
@@ -82,14 +85,16 @@ Segment *segments;
  *
  * And, when predicted foredetect enters a new segment we advance the
  * predicted tail (as described above).  We set pred_vacated and
- * until_here, and remove the departing segments from the kind and
- * train-relative orientation tallies.
+ * until, and remove the departing segments from tallies.
  *
  * If all of this works then pred_present and pred_vacated constitute
- * the new ownership.
+ * the new ownership and motion constitutes the new plan.  We commit
+ * to it by rewriting owner and det_* and actually requesting any
+ * movfeat changes.
  *
  * If not then we issue an signal stop command (we'd better not be
- * doing that already!) or countermand the speed increase or whatever */
+ * doing that already!) or countermand the speed increase or whatever.
+ */
 /*
  * Here is how we use the stuff in the Segment:
  *
@@ -102,16 +107,18 @@ Segment *segments;
  *  movposcomb         in an owned segment, if moving, means that
  *   &                         we have a promise that the feature
  *  moving                     will be OK by the time we get there
- *                             at our current speed
- * 
- *  move_reservation   0 for unowned segments (although during prediction
- *                             may be non-0 for a bit, in which case
- *                             we garbage collect it in a bit)
- * 
- *                     for an owned segment is our reservation which
+ *   &                         at our current speed
+ *  motion             if not moving then motion is our reservation which
  *                             allows us to know that we are going to
  *                             be able to change this segment between
  *                             the tail leaving it and the head arriving
+ *                     in each case movpos_change_intent tells us the plan
+ *
+ *                     during prediction, motion can be non-0 for
+ *                             unowned segments as we make our plan.
+ *                             If we succeed we will end up
+ *                             owning the segment, and if we fail
+ *                             we garbage collect it
  * 
  *  det_ignore         we expect interference detection here due to a
  *                             crossing (unowned segment), or
@@ -148,6 +155,9 @@ void update_head(Train *tra, Segment *seg) {
     safety_panic(tra,seg, "track route not set and train has arrived");
 }  
 
+typedef void PredictionProblemCallback(void *pu, Train *tra, TrackSegment *seg,
+                                      const char *fmt, va_list al);
+
 typedef struct {
   unsigned
     accelerating:1;
@@ -155,6 +165,9 @@ typedef struct {
   TrackAdvanceContext nosec, tailc, fdetc;
   TimeInterval elapsed;
   Distance was_distance;
+
+  PredictionProblemCallback *problem_callback;
+  void *problem_callback_u;
   
   /* tally counts segments which have been entered in fdet_nextseg but
    * not left in tail_nextseg (ie not the same ones as pred_present),
@@ -162,6 +175,21 @@ typedef struct {
   int noninv_tally[2];
 } PredictUserContext;
 
+static ErrorCode predict_vproblem(PredicUserContext *u, TrackSegment *seg,
+                                 const char *fmt, va_list al) {
+  u->problem_callback(u->train,seg,fmt,al);
+  return EC_ProblemPredicted;
+}
+static ErrorCode predict_problem(PredicUserContext *u, TrackSegment *seg,
+                                 const char *fmt, ...) {
+  ErrorCode ec;
+  va_list al;
+  va_start(al,fmt);
+  ec= predict_vproblem(u,seg,fmt,al);
+  va_end(al);
+  return ec;
+}
+
 static int initpresent_nextseg(TrackLocation *t, TrackAdvanceContext *c,
                               MovPosComb *mpc_io, Segment *before) {
   PredictUserContext *u= c->u;
@@ -172,124 +200,130 @@ static int initpresent_nextseg(TrackLocation *t, TrackAdvanceContext *c,
 static int nose_nextseg(TrackLocation *t, TrackAdvanceContext *c,
                        MovPosComb *mpc_io, Segment *before) {
   PredictUserContext *u= c->u;
+  MovPosComb route_plan;
+  MovPosChange *route_change;
 
   /* Is it empty ? */
 
+  if (u->train->sigstopping && t->seg->owner != u->train)
+      return EC_SignalStopHorizonReached;
+
   if (t->seg->owner) {
     if (t->seg->owner != u->train)
-      return safety_problem(u->train, t->seg, "impeded by %s",
-                           t->seg->owner->pname);
+      return predict_problem(u, t->seg, "impeded by %s",
+                            t->seg->owner->pname);
   }
   if (t->seg->pred_present)
-    return safety_problem(u->train, t->seg, "will collide with itself!");
+    return predict_problem(u, t->seg, "will collide with itself!");
 
-  /* Is it set for us ? */
+  /* What about the route ? */
 
   if (t->seg->i->n_poscombs==1)
     goto movement_ok;
   
   if (t->seg->moving) {
     if (!t->seg->owner)
-      return safety_problem(u->train, t->seg, "route not yet set");
-no good if we are trying to accelerate - may get there too soon
-    goto movement_ok;
+      return predict_problem(u, t->seg, "route not yet set");
+    if (!u->accelerating) {
+      *mpc_io= movpos_change_intent(t->seg->motion);
+      goto movement_ok;
+    }
   }
 
-  /* Maybe we can set it apropriately, or plan to. */
-
-  if (t->seg->pred_vacated) {
-    /* We're just predicting that we'll have it vacated. */
-    MovPosChange *newres;
-    
-    if (t->seg->move_reservation && !u->accelerating)
+  if (t->seg->motion) {
+    /* We already have a plan. */
+    route_plan= movpos_change_intent(t->seg->motion);
+    if (!u->accelerating) {
+      *mpc_io= route_plan
       goto movement_ok;
-    
-need to calculate new intended movposcomb and remember it
-so we get right answer next time
-    ec= movpos_reserve(t->seg, u->elapsed - t->seg->until, &newres);
-    if (ec) return safety_problem(u->train, t->seg, "cannot promise to"
-                                 " set route in time: %s", ec2str(ec));
-    movpos_unreserve(t->seg->move_reservation);
-    t->seg->move_reservation= newres;
+    }
   } else {
-    /* Hah!  We can change it! */
-    /* This also copes with the case where in fact it was set right
-     * all along.  In that case movpos's minimisation algorithm will
-     * chose to implement our request without any actual motions. */
-    ec= movpos_change_bysegs(before,t->seg,0, t->seg->until,
-                            t->seg->move_reservation);
-stash intended movposcomb in new field in Segment
-need to make us owner now as well
-ownership should last until it stops moving at least
-  no actually this is still wrong
-    t->seg->move_reservation= 0;
-    if (ec) return safety_problem(u->train, t->seg, "cannot"
-                                 " set route in time: %s", ec2str(ec));
+    /* Extend the plan. */
+    ec= movpos_findcomb_bysegs(before,t->seg,0,*mpc_io, &route_plan);
+    assert(!ec); /* there must be _some_ route since
+                   we're moving into t->seg */
   }
+  if (route_plan == *mpc_io && !t->seg->moving) {
+    /* Well, that's all fine then. */
+    movpos_unreserve(t->seg->motion); /* we don't need this */
+    t->seg->motion= 0;
+    goto movement_ok;
+  }
+  /* We have a plan but it involves some motion: */
+  ec= movpos_reserve(t->seg,
+                    !t->seg->pred_vacated ? u->elapsed
+                    : u->elapsed - t->seg->until,
+                    &route_reservation, route_plan, *mpc_io);
+  if (ec) return predict_problem(u, t->seg, "cannot promise to"
+                                " set route: %s", ec2str(ec));
+
+  movpos_unreserve(t->seg->motion);
+  t->seg->motion= route_reservation;
+  *mpc_io= route_plan;
 
  movement_ok:
+  /* Now we definitely have a plan which sets a good route at t->seg. */
+  t->seg->tr_backwards= t->backwards;
   t->seg->pred_present= 1;
   t->seg->until= u->elapsed;
+  return 0; /* yay! */
+}
 
-  adjust
-                            
-                        &
-       
-
-    const MovPosCombInfo *pci;
-    const SegmentLinkInfo *link;
-
-    r= trackloc_getlink(t,0,0,&link,-1,1);
-    if (!r && link->next==before)
-      goto movement_ok;
-
-    
-
-    pci= t->seg->i->poscombs[
-    link= t->backwards ? &tryi->forwards : &tryi->backwards;
-
-    MovPosComb only_ok= -1, try;
-    for (try=0, tryi=t->seg->i->poscombs;
-        try < t->seg->i->n_poscombs; try++, tryi++) {
-      link= t->backwards ? &tryi->forwards : &tryi->backwards;
-      if (link->next != before) continue;
-      if (only_ok >= 0) { only_ok=-1; goto several_combs_ok; }
-      only_ok= try;
-    }
-    assert(only_ok >= 0); /* otherwise graph is full of lies */
-  several_combs_ok:
-
-    if (t->seg->movposcomb < 0 || t->seg->movposcomb != only_ok) {
-      
-    else if (t->seg->movposcomb < 0) {
-      
+static int tail_nextseg(TrackLocation *t, TrackAdvanceContext *c,
+                       MovPosComb *mpc_io, Segment *leaving) {
+  PredictUserContext *u= c->u;
 
-      return safety_problem(
+  if (t->seg->motion)
+    *mpc_io= movpos_change_intent(t->seg->motion);
 
-  t->seg->pred_present= 1;
+  u->noninv_tally[leaving->tr_backwards]--;
 
+  if (t->seg->pred_vacated) return 0; /* only vacate once */
+  t->seg->pred_present= 0;
+  t->seg->pred_vacated= 1;
+  t->seg->until= u->elapsed;
 
-    safety_problem
+  return 0;
+}
 
 static int fdet_nextseg(TrackLocation *t, TrackAdvanceContext *c,
-                       Segment *next, MovPosComb *mpc_io) {
+                       MovPosComb *mpc_io, Segment *before) {
   PredictUserContext *u= c->u;
   int advanced;
 
-  advanced= c->u->was_distance - c->distance;
+  if (t->seg->motion)
+    *mpc_io= movpos_change_intent(t->seg->motion);
+
+  advanced= u->was_distance - c->distance;
 
   u->nosec.distance= advanced;
   r= trackloc_advance(&u->nose,&u->nosec);  if (r) return r;
 
-tally add new segment
+  if (!t->seg->invertible) {
+    u->noninv_tally[t->backwards]++;
+    if (u->noninv_tally[0] && u->noninv_tally[1])
+      return predict_problem(u, t->seg, "cannot set track polarity");
+  }
 
   u->tailc.distance= advanced;
-  r= 
+  r= trackloc_advance(&u->tail,&u->tailc);  assert(!r);
 
-void predict_and_provide(Train *tra) {
+  if (u->was_distance == INT_MAX)
+    c->distance= speedmanager_stoppingdistance(u->train);
+
+  u->was_distance= c->distance;
+  return 0;
+}
+
+static ErrorCode predict_confirm(Train *tra, int accelerate,
+                                PredictionProblemCallback *ppc, void *ppcu) {
+  /* Caller must call this with different situations until it succeeds! */
   PredictUserContext u;
 
   memset(&u,0,sizeof(u));
+  u.accelerating= accelerate;
+  u.problem_callback= ppc;
+  u.problem_callback_u= ppcu;
 
   trackloc_set_maxinto(&u.fdet, tra->foredetect,
                       tra->foredetect->tr_backwards);
@@ -315,7 +349,7 @@ void predict_and_provide(Train *tra) {
   u.nosec.distance= tra->detectable + (tra->backwards ? tra->tail : tra->head);
   u.nosec.nextseg= nose_nextseg;
   u.nosec.trackend= pred_trackend;
-  u.nosec.getmovpos= pred_getmovpos;
+  u.nosec.getmovpos= 0;
   u.nosec.u= &u;
 
   r= trackloc_advance(&u.nose,&u.nosec);
@@ -323,15 +357,33 @@ void predict_and_provide(Train *tra) {
 
   /* predict the future */
 
-  fdetc.distance= u.was_distance= INT_MAX;
-  fdetc.nextseg= fdet_nextseg;
-  fdetc.trackend= pred_trackend;
-  fdetc.getmovpos= pred_getmovpos;
-  fdetc.u= &u;
+  u.fdetc.distance= u.was_distance= INT_MAX;
+  u.fdetc.nextseg= fdet_nextseg;
+  u.fdetc.trackend= pred_trackend;
+  u.fdetc.getmovpos= 0;
+  u.fdetc.u= &u;
+
+  u.tailc.nextseg= tail_nextseg;
 
   r= trackloc_advance(&fdet,&fdetc);
-  
-  
+  if (r) return r;
+
+  /* commit here */
+  FOR_SEG {
+    intr= interferes(seg);
+    if (seg->owner == tra) {
+      seg->det_ignore= 0;
+      seg->det_expect= 0;
+      seg->owner= 0;
+      if (intr) {
+       assert(!intr->owner || intr->owner==tra);
+       intr->det_ignore= 0;
+      }
+    }
+    if (seg->pred_present || seg->pred_vacated) {
+      seg->owner= tra;
+      if (seg->until) 
+    
 predict the future
   fdet.seg= tra->foredetect;
 
index 36481b753ae5770b26aa9d818c07919537c8e6af..eb51e000d8a40977789e60252e59ab73f317a589 100644 (file)
@@ -43,7 +43,7 @@ struct Train {
     backwards:1, /* train is moving backwards wrt its own front and back */
 
   /* Speed: */
-    estopping:1, /* set and cleared by speed.c */
+    sigstopping:1, /* set and cleared by speed.c */
 
   /* Startup resolution (resolve.c): */
     resolution:2;
@@ -67,16 +67,14 @@ struct Segment {
   unsigned
     tr_backwards:1, /* train's motion is (would be) backwards wrt track */
     ho_backwards:1, /* home train has its front and rear backwards wrt track */
-    cm_autostop:1, /* train should stop on detection */
     seg_inverted:1, /* polarity is inverted */
     det_ignore:1, det_expected:1, /* safety.c */
+    moving:1, /* feature(s) have been told to change */
     mark0,mark1,mark2,mark3, /* for temporary private uses */
     res_detect:1; /* detection noticed here during resolution */
-  TimeInterval until_here; /* ) nonnegative; */  /* ) always valid but      */
-                           /* ) 0 if already */  /* )  meaningful iff owner */
-  MovPosComb movposcomb; /* -1 means not known or moving */
-  MovPosChange *moving, /* non-0 iff feature(s) have been told to change */
-    *move_reservation;  /* see safety.c */
+  MovPosComb movposcomb, /* -1 means not known or moving */
+  MovPosChange *motion; /* if ->moving, owned by movpos, otherwise by safety */
+  TimeInterval until; /* for use by safety.c */
   const SegmentInfo *i;
 };
 
@@ -140,35 +138,44 @@ movpos_change(Segment *tomove, MovPosComb target,
    * reservation should be 0, or the results of movpos_reservechange
    * on the same move.  It is always consumed, even on error.
    *
-   * On successful exit, tomove->moving is set non-0; from then on
-   * until ->moving becomes 0, movposcomb may not reflect the real
-   * physical state of the layout; instead it gives only information
-   * about the target configuration.  (Choreographers are allowed to
-   * know implementation details and may know what movposcomb means
-   * for certain segments depending on the movement requested.)
+   * One of the following must be true of tomove on entry:
+   *   moving==1   motion already owned by movpos, see above
+   *   motion==0   motion empty, will become owned by movpos
    *
-   * Returns EC_Invalid if there is no movposcomb of tomove matching
-   * back/fwd, or EC_MovFeatTooLate if the maxdelay_ms could not be
-   * met because of lack of ability to change points.
+   * On successful exit, tomove->moving=1 and tomove->motion!=0; from
+   * then on until ->moving becomes 0, movposcomb may not reflect the
+   * real physical state of the layout; instead it gives only
+   * information about the target configuration.  (Choreographers are
+   * allowed to know implementation details and may know what
+   * movposcomb means for certain segments depending on the movement
+   * requested.)
    */
 
-ErrorCode
-movpos_change_bysegs(Segment *back, Segment *tomove, Segment *fwd,
-                    int maxdelay_ms, MovPosChange *reservation);
-  /* back and fwd may be 0 if we don't care (and must be if there is
-   * no track in that direction.  It is immaterial which is back and
-   * which fwd.
+ErrorCode movpos_findcomb_bysegs(Segment *back, Segment *move, Segment *fwd,
+                                MovPosComb startpoint, MovPosComb *chosen_r);
+  /* Looks for a movposcomb of move where the adjacent segment in one
+   * direction is back and the other fwd.  back and fwd may be 0 if we
+   * don't care (and must be if there is no track in that direction.
+   * It is immaterial which is back and which fwd.
+   *
+   * The returned movposcomb is the `best' one which is the one
+   * closest to the starting point (and if that is not unique, the
+   * lowest-numbered).
+   *
+   * startpoint=-1 means starting with current state.  chosen_r may be 0.
    */
 
 ErrorCode
-movpos_reserve(Segment *move, int maxdelay_ms, MovPosChange **res_r);
-  /* Returns EC_MovFeatTooLate if the maxdelay_ms could not be met.
-   * The resulting MovPosChange is a reservation which is guaranteed
-   * to be useable successfully later for any movpos_change for
-   * the same move and a greater or equal maxdelay_ms.  On successful
-   * exit *res_r is set to non-0.  On transition out of Sta_Run,
-   * all unconfirmed reservations must be unreserved before
-   * points_all_abandon is called (see startup.c);
+movpos_reserve(Segment *move, int maxdelay_ms, MovPosChange **res_r,
+              MovPosComb target, MovPosComb startpoint /*as for findcomb*/);
+  /* The resulting MovPosChange is a reservation which is guaranteed
+   * to be useable successfully later for any movpos_change for the
+   * same move, target and startpoing and a greater or equal
+   * maxdelay_ms.  On successful exit *res_r is set to non-0.
+   *
+   * On transition out of Sta_Run, all unconfirmed reservations must
+   * be unreserved before points_all_abandon is called (see
+   * startup.c);
    */
 
 void movpos_unreserve(MovPosChange *reservation /* 0 ok */);
@@ -178,6 +185,18 @@ MovPosComb movpos_poscomb_actual(Segment *seg);
    *  -1 means not known or cannot be represented as a MovPosComb
    */
 
+MovPosComb movpos_change_intent(MovPosChange *chg);
+  /* Returns the target value supplied to _reserve or _change */
+
+ /* Error returns from movpos calls
+  *
+  *  EC_Invalid         there is no movposcomb of tomove matching back/fwd
+  *  EC_MovFeatTooLate  maxdelay_ms could not be met
+  *  EC_MovFeatReservationInapplicable
+  *                     reservation not compatible with change request
+  *                       and/or current situation
+  */
+
 /*========== speedmgr.c ==========*/
 
 void speedmanager_speedchange_request(Train *tra, long speed);