--- cs:140/Robot/ServingRobotController.cs
+++ cs:141/Robot/ServingRobotController.cs
@@ -336,6 +336,21 @@
         /// <summary>この走りで寄る卓の数</summary>
         public int RunStopCount { get { return _stops.Count; } }
 
+        readonly List<float> _stopArrivedS = new List<float>();
+
+        /// <summary>
+        /// 止まる所ごとに着いた時刻（店の中, 秒）。着いていない所は -1（依頼 v8 の 2）。
+        /// trips.csv の stop_arrived_s に出す
+        /// </summary>
+        public IReadOnlyList<float> StopArrivedS { get { return _stopArrivedS; } }
+
+        /// <summary>
+        /// 止まる所に着いた（引数：ロボット・止まる所の番号）。依頼 v8 の 1。
+        /// ServiceOps が受けて、その所の皿を「届いた」にする。
+        /// 受け渡しの 25 秒（tableDwellSec）の **前** に出る
+        /// </summary>
+        public event Action<ServingRobotController, int> StopArrived;
+
         /// <summary>1往復が終わったときに呼ばれる</summary>
         public event Action<ServingRobotController> TripCompleted;
 
@@ -540,16 +555,19 @@
             RunStartS = startS;
             _stops.Clear();
             _tableIds.Clear();
+            _stopArrivedS.Clear();
             if (stops != null)
             {
                 for (int i = 0; i < stops.Count; i++)
                 {
                     _stops.Add(stops[i]);
                     _tableIds.Add(stops[i].tableId);
+                    _stopArrivedS.Add(-1f);     // 着いたら時刻を入れる（依頼 v8 の 2）
                 }
             }
             _stopIndex = 0;
             StopsCompleted = 0;
+            ResetTo = "";               // 打ち切りで置き直したときだけ入れる（依頼 v8b の A）
 
             if (_stops.Count == 0) { _phase = Phase.Idle; return; }
             BeginRun(passPoint);
@@ -801,7 +819,11 @@
             if (TotalS > spec.maxTripSec)
             {
                 TimedOut = true;
-                PlaceAt(_passPoint, transform.rotation);
+                // ほかの台と重ならない所へ置き直す（依頼 v8b の A）。
+                // 前は必ず受け渡し口の点に置いていたので、2台が打ち切られると
+                // 同じ点で重なり、お互いを「もう1台のロボット」として止まり続けていた
+                Vector3 to = PickResetPoint();
+                PlaceAt(to, transform.rotation);
                 Finish();
                 return;
             }
@@ -1069,6 +1091,79 @@
 
         List<Vector3> _sideCandidates;
 
+        // ---------------- 打ち切りで置き直す先（依頼 v8b の A）----------------
+
+        /// <summary>
+        /// 打ち切りで置き直した先（trips.csv の reset_to）。
+        /// pass / standby1 / standby2 …。打ち切りでない往復は空
+        /// </summary>
+        public string ResetTo { get; private set; }
+
+        /// <summary>
+        /// 置く先を選ぶ。ほかの台が近く（直径＋余裕）にいない最初の点。
+        ///   受け渡し口の点 → 待機場所を順に
+        /// どこも空いていなければ、ほかの台からいちばん遠い待機場所
+        /// （待機場所が無ければ受け渡し口の点）。
+        /// </summary>
+        Vector3 PickResetPoint()
+        {
+            float near = spec != null ? spec.diameter + ResetClearMarginM : 0.8f;
+
+            IList<Vector3> standby = ops != null && ops.store != null
+                ? ops.store.RobotStandbyPoints : null;
+
+            // 1. 受け渡し口の点
+            if (NearestRobotDistance(_passPoint) > near) { ResetTo = "pass"; return _passPoint; }
+
+            // 2. 待機場所を順に
+            if (standby != null)
+            {
+                for (int i = 0; i < standby.Count; i++)
+                {
+                    if (NearestRobotDistance(standby[i]) <= near) continue;
+                    ResetTo = "standby" + (i + 1);
+                    return standby[i];
+                }
+
+                // 3. どこも空いていない。ほかの台からいちばん遠い待機場所
+                int best = -1;
+                float bestD = -1f;
+                for (int i = 0; i < standby.Count; i++)
+                {
+                    float d = NearestRobotDistance(standby[i]);
+                    if (d <= bestD) continue;
+                    best = i; bestD = d;
+                }
+                if (best >= 0)
+                {
+                    ResetTo = "standby" + (best + 1);
+                    return standby[best];
+                }
+            }
+
+            ResetTo = "pass";
+            return _passPoint;
+        }
+
+        /// <summary>置く先として見るときの、直径に足す余裕（m）</summary>
+        const float ResetClearMarginM = 0.3f;
+
+        /// <summary>その点から、いちばん近いほかの台までの距離（m）。ほかに台が無ければ大きい値</summary>
+        float NearestRobotDistance(Vector3 p)
+        {
+            p.y = 0f;
+            float best = float.MaxValue;
+            for (int i = 0; i < _all.Count; i++)
+            {
+                ServingRobotController o = _all[i];
+                if (o == null || o == this) continue;
+                Vector3 q = o.transform.position; q.y = 0f;
+                float d = Vector3.Distance(p, q);
+                if (d < best) best = d;
+            }
+            return best;
+        }
+
         /// <summary>相手の外周と自分の外周のあいだの空き（m）</summary>
         float Gap(ServingRobotController other)
         {
@@ -1545,6 +1640,14 @@
                 _dwellTimer = 0f;
                 _speed = 0f;
                 StopsCompleted = _stopIndex + 1;   // この卓には届いた
+
+                // 卓に着いた時点を残して知らせる（依頼 v8 の 1）。
+                // サイトの試算と同じで、ここで皿は「届いた」。
+                // 受け渡しの 25 秒（tableDwellSec）はこのあと
+                int at = _stopIndex;
+                if (at >= 0 && at < _stopArrivedS.Count)
+                    _stopArrivedS[at] = ops != null ? ops.NowS : 0f;
+                if (StopArrived != null) StopArrived(this, at);
             }
             else if (_phase == Phase.ToPass)
             {
