minotor 11.2.0 → 11.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,7 +28,8 @@ export type ArrivalWithDuration = Arrival & {
28
28
  * The result of a Range RAPTOR query.
29
29
  *
30
30
  * Contains the complete Pareto-optimal set of journeys for a resolved
31
- * destination set.
31
+ * destination set, **or** the full per-departure-time routing state when no
32
+ * destinations were provided (full-network / isochrone mode).
32
33
  *
33
34
  * **Pareto dominance**: journey J1 dominates J2 iff
34
35
  * `τdep(J1) ≥ τdep(J2) AND τarr(J1) ≤ τarr(J2)`
@@ -38,6 +39,15 @@ export type ArrivalWithDuration = Arrival & {
38
39
  * strictly earlier *and* arrives strictly earlier than the previous one,
39
40
  * forming the classic staircase Pareto frontier.
40
41
  *
42
+ * **Full-network mode** (empty `destinations`): when no destinations are
43
+ * supplied to the range query every departure slot in the window becomes its
44
+ * own run, because destination-based Pareto pruning cannot be applied.
45
+ * In this mode the destination-specific helpers ({@link getRoutes},
46
+ * {@link bestRoute}, {@link latestDepartureRoute}, {@link fastestRoute})
47
+ * return empty results; use {@link allEarliestArrivals},
48
+ * {@link allShortestDurations}, {@link earliestArrivalAt}, or
49
+ * {@link shortestDurationTo} instead.
50
+ *
41
51
  * Destination handling is delegated to {@link Result}, which expands
42
52
  * equivalent stops when reconstructing routes or looking up arrivals.
43
53
  */
@@ -54,6 +64,10 @@ export declare class RangeResult {
54
64
  *
55
65
  * Each route in the list departs strictly earlier *and* arrives strictly
56
66
  * earlier than its predecessor.
67
+ *
68
+ * Returns an empty array when no destinations were provided (full-network
69
+ * mode). Use {@link allEarliestArrivals} or {@link allShortestDurations}
70
+ * to query individual stops in that case.
57
71
  */
58
72
  getRoutes(): Route[];
59
73
  /**
@@ -65,6 +79,8 @@ export declare class RangeResult {
65
79
  * at a transit stop.
66
80
  *
67
81
  * Defaults to this result's own destination stop(s) when `to` is omitted.
82
+ * Always pass an explicit `to` stop when operating in full-network mode
83
+ * (no destinations), otherwise `undefined` is returned.
68
84
  *
69
85
  * @param to Optional destination stop ID or set of stop IDs.
70
86
  * @returns The reconstructed {@link Route} with the earliest arrival,
@@ -81,6 +97,8 @@ export declare class RangeResult {
81
97
  * {@link fastestRoute}.
82
98
  *
83
99
  * Defaults to this result's own destination stop(s) when `to` is omitted.
100
+ * Always pass an explicit `to` stop when operating in full-network mode
101
+ * (no destinations), otherwise `undefined` is returned.
84
102
  *
85
103
  * @param to Optional destination stop ID or set of stop IDs.
86
104
  * @returns The reconstructed {@link Route} with the latest departure,
@@ -97,6 +115,8 @@ export declare class RangeResult {
97
115
  * spent traveling.
98
116
  *
99
117
  * Defaults to this result's own destination stop(s) when `to` is omitted.
118
+ * Always pass an explicit `to` stop when operating in full-network mode
119
+ * (no destinations), otherwise `undefined` is returned.
100
120
  *
101
121
  * @param to Optional destination stop ID or set of stop IDs.
102
122
  * @returns The reconstructed fastest {@link Route}, or `undefined` if the
@@ -69,7 +69,7 @@ export declare class RangeRaptorState implements IRaptorState {
69
69
  */
70
70
  get destinationBest(): Time;
71
71
  isDestination(stop: StopId): boolean;
72
- /** Updates both the per-run state and the cross-run shared labels. */
72
+ /** Updates the per-run aggregate best when improved, and always considers the cross-run shared label. */
73
73
  updateArrival(stop: StopId, time: Time, round: number): void;
74
74
  /**
75
75
  * initialized round `k` from round `k-1`: τk(p) ← min(τk(p), τk-1(p)).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minotor",
3
- "version": "11.2.0",
3
+ "version": "11.2.2",
4
4
  "description": "A lightweight client-side transit routing library.",
5
5
  "keywords": [
6
6
  "minotor",
@@ -2,7 +2,7 @@ import assert from 'node:assert';
2
2
  import { describe, it } from 'node:test';
3
3
 
4
4
  import { Timetable } from '../../router.js';
5
- import { Stop } from '../../stops/stops.js';
5
+ import { Stop, StopId } from '../../stops/stops.js';
6
6
  import { StopsIndex } from '../../stops/stopsIndex.js';
7
7
  import { Route } from '../../timetable/route.js';
8
8
  import { timeFromHM } from '../../timetable/time.js';
@@ -270,4 +270,89 @@ describe('RangeResult', () => {
270
270
  assert.deepStrictEqual(empty.allEarliestArrivals(), new Map());
271
271
  });
272
272
  });
273
+
274
+ describe('no destinations (full-network / isochrone mode)', () => {
275
+ // Same routing data as runA/runB but Result is built with empty destinations,
276
+ // reflecting how RangeRouter constructs results in full-network mode.
277
+ const emptyDests = new Set<StopId>();
278
+
279
+ const runAFull: ParetoRun = {
280
+ departureTime: timeFromHM(9, 0),
281
+ result: new Result(
282
+ emptyDests,
283
+ RoutingState.fromTestData({
284
+ nbStops: 2,
285
+ origins: [0],
286
+ destinations: [],
287
+ arrivals: [
288
+ [0, timeFromHM(9, 0), 0],
289
+ [DEST, timeFromHM(9, 30), 1],
290
+ ],
291
+ graph: [
292
+ [[0, { stopId: 0, arrival: timeFromHM(9, 0) }]],
293
+ [[DEST, edgeA]],
294
+ ],
295
+ }),
296
+ stopsIndex,
297
+ timetable,
298
+ ),
299
+ };
300
+
301
+ const runBFull: ParetoRun = {
302
+ departureTime: timeFromHM(8, 30),
303
+ result: new Result(
304
+ emptyDests,
305
+ RoutingState.fromTestData({
306
+ nbStops: 2,
307
+ origins: [0],
308
+ destinations: [],
309
+ arrivals: [
310
+ [0, timeFromHM(8, 30), 0],
311
+ [DEST, timeFromHM(9, 10), 1],
312
+ ],
313
+ graph: [
314
+ [[0, { stopId: 0, arrival: timeFromHM(8, 30) }]],
315
+ [[DEST, edgeB]],
316
+ ],
317
+ }),
318
+ stopsIndex,
319
+ timetable,
320
+ ),
321
+ };
322
+
323
+ const fullNetworkResult = new RangeResult([runAFull, runBFull], emptyDests);
324
+
325
+ it('destinations is empty', () => {
326
+ assert.strictEqual(fullNetworkResult.destinations.size, 0);
327
+ });
328
+
329
+ it('getRoutes returns an empty array', () => {
330
+ // Result was built with empty destinations, so bestRoute() on each inner
331
+ // Result finds no target and getRoutes() collapses to [].
332
+ assert.deepStrictEqual(fullNetworkResult.getRoutes(), []);
333
+ });
334
+
335
+ it('bestRoute without an explicit stop returns undefined', () => {
336
+ assert.strictEqual(fullNetworkResult.bestRoute(), undefined);
337
+ });
338
+
339
+ it('bestRoute with an explicit stop returns the correct route', () => {
340
+ const route = fullNetworkResult.bestRoute(DEST);
341
+ assert(route);
342
+ // Best route to DEST is via runB (earlier arrival at 09:10).
343
+ assert.strictEqual(route.arrivalTime(), timeFromHM(9, 10));
344
+ });
345
+
346
+ it('allEarliestArrivals covers all reachable stops', () => {
347
+ const arrivals = fullNetworkResult.allEarliestArrivals();
348
+ // DEST earliest arrival is 09:10 (from runB).
349
+ assert.strictEqual(arrivals.get(DEST)?.arrival, timeFromHM(9, 10));
350
+ });
351
+
352
+ it('allShortestDurations covers all reachable stops', () => {
353
+ const durations = fullNetworkResult.allShortestDurations();
354
+ // runA: 30 min, runB: 40 min → shortest duration to DEST is 30 min.
355
+ assert.strictEqual(durations.get(DEST)?.duration, 30);
356
+ });
357
+ });
273
358
  });
@@ -12,6 +12,7 @@ import {
12
12
  } from '../../timetable/timetable.js';
13
13
  import { AccessFinder } from '../access.js';
14
14
  import { RangeQuery } from '../query.js';
15
+ import { RangeResult } from '../rangeResult.js';
15
16
  import { RangeRouter } from '../rangeRouter.js';
16
17
  import { Raptor } from '../raptor.js';
17
18
 
@@ -469,4 +470,132 @@ describe('RangeRouter', () => {
469
470
  assert.strictEqual(run.departureTime, timeFromHM(8, 30));
470
471
  });
471
472
  });
473
+
474
+ describe('with no destinations (full-network / isochrone mode)', () => {
475
+ // Two-stop network, two trips:
476
+ // trip 0: stop 0 departs 08:00 → stop 1 arrives 08:30 (30-min journey)
477
+ // trip 1: stop 0 departs 08:30 → stop 1 arrives 09:00 (30-min journey)
478
+ // The query window covers both departure slots; no destination is specified.
479
+ let result: RangeResult;
480
+
481
+ beforeEach(() => {
482
+ const stopsAdjacency: StopAdjacency[] = [
483
+ { routes: [0] },
484
+ { routes: [0] },
485
+ ];
486
+
487
+ const routesAdjacency = [
488
+ Route.of({
489
+ id: 0,
490
+ serviceRouteId: 0,
491
+ trips: [
492
+ {
493
+ stops: [
494
+ {
495
+ id: 0,
496
+ arrivalTime: timeFromHM(8, 0),
497
+ departureTime: timeFromHM(8, 0),
498
+ },
499
+ {
500
+ id: 1,
501
+ arrivalTime: timeFromHM(8, 30),
502
+ departureTime: timeFromHM(8, 30),
503
+ },
504
+ ],
505
+ },
506
+ {
507
+ stops: [
508
+ {
509
+ id: 0,
510
+ arrivalTime: timeFromHM(8, 30),
511
+ departureTime: timeFromHM(8, 30),
512
+ },
513
+ {
514
+ id: 1,
515
+ arrivalTime: timeFromHM(9, 0),
516
+ departureTime: timeFromHM(9, 0),
517
+ },
518
+ ],
519
+ },
520
+ ],
521
+ }),
522
+ ];
523
+
524
+ const serviceRoutes: ServiceRoute[] = [
525
+ { type: 'BUS', name: 'Line 1', routes: [0] },
526
+ ];
527
+
528
+ const timetable = new Timetable(
529
+ stopsAdjacency,
530
+ routesAdjacency,
531
+ serviceRoutes,
532
+ );
533
+
534
+ const stops: Stop[] = [
535
+ {
536
+ id: 0,
537
+ sourceStopId: 'origin',
538
+ name: 'Origin',
539
+ lat: 0,
540
+ lon: 0,
541
+ children: [],
542
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
543
+ },
544
+ {
545
+ id: 1,
546
+ sourceStopId: 'dest',
547
+ name: 'Destination',
548
+ lat: 0,
549
+ lon: 0,
550
+ children: [],
551
+ locationType: 'SIMPLE_STOP_OR_PLATFORM',
552
+ },
553
+ ];
554
+
555
+ const stopsIndex = new StopsIndex(stops);
556
+ const accessFinder = new AccessFinder(timetable, stopsIndex);
557
+ const raptor = new Raptor(timetable);
558
+ const router = new RangeRouter(
559
+ timetable,
560
+ stopsIndex,
561
+ accessFinder,
562
+ raptor,
563
+ );
564
+
565
+ // Omitting .to() leaves toValue as its default empty Set.
566
+ const query = new RangeQuery.Builder()
567
+ .from(0)
568
+ .departureTime(timeFromHM(8, 0))
569
+ .lastDepartureTime(timeFromHM(8, 30))
570
+ .build();
571
+
572
+ result = router.rangeRoute(query);
573
+ });
574
+
575
+ it('returns a run for every departure slot in the window', () => {
576
+ // Without destinations the trivialDestCovered guard (0 === 0) previously
577
+ // aborted the loop before any run was processed; now both slots are kept.
578
+ assert.strictEqual(result.size, 2);
579
+ });
580
+
581
+ it('allEarliestArrivals covers all reachable stops', () => {
582
+ const arrivals = result.allEarliestArrivals();
583
+ // Stop 1 is first reached by trip 0 (depart 08:00, arrive 08:30).
584
+ assert.strictEqual(arrivals.get(1)?.arrival, timeFromHM(8, 30));
585
+ });
586
+
587
+ it('allShortestDurations covers all reachable stops', () => {
588
+ const durations = result.allShortestDurations();
589
+ // Both trips take 30 min; shortest duration to stop 1 is 30 min.
590
+ assert.strictEqual(durations.get(1)?.duration, 30);
591
+ });
592
+
593
+ it('bestRoute without an explicit stop returns undefined', () => {
594
+ assert.strictEqual(result.bestRoute(), undefined);
595
+ });
596
+
597
+ it('getRoutes returns an empty array', () => {
598
+ assert.deepStrictEqual(result.getRoutes(), []);
599
+ });
600
+ });
472
601
  });
@@ -229,6 +229,94 @@ describe('RangeRaptorState', () => {
229
229
  });
230
230
  });
231
231
 
232
+ describe('updateArrival aggregate overwrite behavior', () => {
233
+ it('does not overwrite the current run aggregate with a later arrival from a higher round', () => {
234
+ const state = new RangeRaptorState(
235
+ MAX_ROUNDS,
236
+ NB_STOPS,
237
+ timeFromHM(12, 0),
238
+ );
239
+ const run = RoutingState.fromTestData({
240
+ nbStops: NB_STOPS,
241
+ destinations: [2],
242
+ arrivals: [[2, timeFromHM(8, 30), 1]],
243
+ });
244
+ state.setCurrentRun(run);
245
+
246
+ state.updateArrival(2, timeFromHM(8, 45), 2);
247
+
248
+ assert.deepStrictEqual(run.getArrival(2), {
249
+ arrival: timeFromHM(8, 30),
250
+ legNumber: 1,
251
+ });
252
+ assert.strictEqual(state.roundLabels[2]![2], timeFromHM(8, 45));
253
+ });
254
+
255
+ it('does not overwrite the current run aggregate with an equal-time arrival using more legs', () => {
256
+ const state = new RangeRaptorState(
257
+ MAX_ROUNDS,
258
+ NB_STOPS,
259
+ timeFromHM(12, 0),
260
+ );
261
+ const run = RoutingState.fromTestData({
262
+ nbStops: NB_STOPS,
263
+ destinations: [2],
264
+ arrivals: [[2, timeFromHM(8, 30), 2]],
265
+ });
266
+ state.setCurrentRun(run);
267
+
268
+ state.updateArrival(2, timeFromHM(8, 30), 3);
269
+
270
+ assert.deepStrictEqual(run.getArrival(2), {
271
+ arrival: timeFromHM(8, 30),
272
+ legNumber: 2,
273
+ });
274
+ assert.strictEqual(state.roundLabels[3]![2], timeFromHM(8, 30));
275
+ });
276
+
277
+ it('prefers fewer legs for the current run aggregate when arrival time is equal', () => {
278
+ const state = new RangeRaptorState(
279
+ MAX_ROUNDS,
280
+ NB_STOPS,
281
+ timeFromHM(12, 0),
282
+ );
283
+ const run = RoutingState.fromTestData({
284
+ nbStops: NB_STOPS,
285
+ destinations: [2],
286
+ arrivals: [[2, timeFromHM(8, 30), 3]],
287
+ });
288
+ state.setCurrentRun(run);
289
+
290
+ state.updateArrival(2, timeFromHM(8, 30), 2);
291
+
292
+ assert.deepStrictEqual(run.getArrival(2), {
293
+ arrival: timeFromHM(8, 30),
294
+ legNumber: 2,
295
+ });
296
+ });
297
+
298
+ it('still updates the current run aggregate when the new arrival is earlier', () => {
299
+ const state = new RangeRaptorState(
300
+ MAX_ROUNDS,
301
+ NB_STOPS,
302
+ timeFromHM(12, 0),
303
+ );
304
+ const run = RoutingState.fromTestData({
305
+ nbStops: NB_STOPS,
306
+ destinations: [2],
307
+ arrivals: [[2, timeFromHM(8, 45), 1]],
308
+ });
309
+ state.setCurrentRun(run);
310
+
311
+ state.updateArrival(2, timeFromHM(8, 30), 3);
312
+
313
+ assert.deepStrictEqual(run.getArrival(2), {
314
+ arrival: timeFromHM(8, 30),
315
+ legNumber: 3,
316
+ });
317
+ });
318
+ });
319
+
232
320
  describe('isDestination', () => {
233
321
  it('delegates to the current run', () => {
234
322
  const state = new RangeRaptorState(
@@ -31,7 +31,8 @@ export type ArrivalWithDuration = Arrival & {
31
31
  * The result of a Range RAPTOR query.
32
32
  *
33
33
  * Contains the complete Pareto-optimal set of journeys for a resolved
34
- * destination set.
34
+ * destination set, **or** the full per-departure-time routing state when no
35
+ * destinations were provided (full-network / isochrone mode).
35
36
  *
36
37
  * **Pareto dominance**: journey J1 dominates J2 iff
37
38
  * `τdep(J1) ≥ τdep(J2) AND τarr(J1) ≤ τarr(J2)`
@@ -41,6 +42,15 @@ export type ArrivalWithDuration = Arrival & {
41
42
  * strictly earlier *and* arrives strictly earlier than the previous one,
42
43
  * forming the classic staircase Pareto frontier.
43
44
  *
45
+ * **Full-network mode** (empty `destinations`): when no destinations are
46
+ * supplied to the range query every departure slot in the window becomes its
47
+ * own run, because destination-based Pareto pruning cannot be applied.
48
+ * In this mode the destination-specific helpers ({@link getRoutes},
49
+ * {@link bestRoute}, {@link latestDepartureRoute}, {@link fastestRoute})
50
+ * return empty results; use {@link allEarliestArrivals},
51
+ * {@link allShortestDurations}, {@link earliestArrivalAt}, or
52
+ * {@link shortestDurationTo} instead.
53
+ *
44
54
  * Destination handling is delegated to {@link Result}, which expands
45
55
  * equivalent stops when reconstructing routes or looking up arrivals.
46
56
  */
@@ -70,6 +80,10 @@ export class RangeResult {
70
80
  *
71
81
  * Each route in the list departs strictly earlier *and* arrives strictly
72
82
  * earlier than its predecessor.
83
+ *
84
+ * Returns an empty array when no destinations were provided (full-network
85
+ * mode). Use {@link allEarliestArrivals} or {@link allShortestDurations}
86
+ * to query individual stops in that case.
73
87
  */
74
88
  getRoutes(): Route[] {
75
89
  const routes: Route[] = [];
@@ -89,6 +103,8 @@ export class RangeResult {
89
103
  * at a transit stop.
90
104
  *
91
105
  * Defaults to this result's own destination stop(s) when `to` is omitted.
106
+ * Always pass an explicit `to` stop when operating in full-network mode
107
+ * (no destinations), otherwise `undefined` is returned.
92
108
  *
93
109
  * @param to Optional destination stop ID or set of stop IDs.
94
110
  * @returns The reconstructed {@link Route} with the earliest arrival,
@@ -124,6 +140,8 @@ export class RangeResult {
124
140
  * {@link fastestRoute}.
125
141
  *
126
142
  * Defaults to this result's own destination stop(s) when `to` is omitted.
143
+ * Always pass an explicit `to` stop when operating in full-network mode
144
+ * (no destinations), otherwise `undefined` is returned.
127
145
  *
128
146
  * @param to Optional destination stop ID or set of stop IDs.
129
147
  * @returns The reconstructed {@link Route} with the latest departure,
@@ -148,6 +166,8 @@ export class RangeResult {
148
166
  * spent traveling.
149
167
  *
150
168
  * Defaults to this result's own destination stop(s) when `to` is omitted.
169
+ * Always pass an explicit `to` stop when operating in full-network mode
170
+ * (no destinations), otherwise `undefined` is returned.
151
171
  *
152
172
  * @param to Optional destination stop ID or set of stop IDs.
153
173
  * @returns The reconstructed fastest {@link Route}, or `undefined` if the
@@ -45,6 +45,8 @@ export class RangeRouter {
45
45
  .flatMap((destination) => this.stopsIndex.equivalentStops(destination))
46
46
  .map((destination) => destination.id);
47
47
 
48
+ const noDestinations = destinations.length === 0;
49
+
48
50
  const accessLegs = this.accessFinder.collectAccessPaths(
49
51
  query.from,
50
52
  query.options.minTransferTime,
@@ -99,15 +101,19 @@ export class RangeRouter {
99
101
  },
100
102
  rangeState,
101
103
  );
102
- for (const dest of destinations) {
103
- const t = routingState.arrivalTime(dest);
104
- if (t < (paretoDestBest.get(dest) ?? UNREACHED_TIME))
105
- paretoDestBest.set(dest, t);
104
+ if (!noDestinations) {
105
+ for (const dest of destinations) {
106
+ const t = routingState.arrivalTime(dest);
107
+ if (t < (paretoDestBest.get(dest) ?? UNREACHED_TIME))
108
+ paretoDestBest.set(dest, t);
109
+ }
106
110
  }
107
111
  }
108
112
 
109
113
  for (const { depTime, legs } of departureSlots) {
110
- if (trivialDestCovered.size === destinations.length) break;
114
+ if (!noDestinations && trivialDestCovered.size === destinations.length) {
115
+ break;
116
+ }
111
117
 
112
118
  if (routingState === null) {
113
119
  routingState = new RoutingState(
@@ -129,23 +135,25 @@ export class RangeRouter {
129
135
  rangeState,
130
136
  );
131
137
 
132
- let isParetoOptimal = false;
133
- for (const dest of destinations) {
134
- const arrival = routingState.arrivalTime(dest);
135
- if (arrival >= (paretoDestBest.get(dest) ?? UNREACHED_TIME)) {
136
- continue;
137
- }
138
+ let isParetoOptimal = noDestinations;
139
+ if (!noDestinations) {
140
+ for (const dest of destinations) {
141
+ const arrival = routingState.arrivalTime(dest);
142
+ if (arrival >= (paretoDestBest.get(dest) ?? UNREACHED_TIME)) {
143
+ continue;
144
+ }
138
145
 
139
- if (trivialDests.has(dest) && trivialDestCovered.has(dest)) {
140
- paretoDestBest.set(dest, arrival);
141
- continue;
142
- }
146
+ if (trivialDests.has(dest) && trivialDestCovered.has(dest)) {
147
+ paretoDestBest.set(dest, arrival);
148
+ continue;
149
+ }
143
150
 
144
- paretoDestBest.set(dest, arrival);
145
- if (trivialDests.has(dest)) {
146
- trivialDestCovered.add(dest);
151
+ paretoDestBest.set(dest, arrival);
152
+ if (trivialDests.has(dest)) {
153
+ trivialDestCovered.add(dest);
154
+ }
155
+ isParetoOptimal = true;
147
156
  }
148
- isParetoOptimal = true;
149
157
  }
150
158
 
151
159
  if (isParetoOptimal) {
@@ -113,9 +113,19 @@ export class RangeRaptorState implements IRaptorState {
113
113
  return this.currentRun.isDestination(stop);
114
114
  }
115
115
 
116
- /** Updates both the per-run state and the cross-run shared labels. */
116
+ /** Updates the per-run aggregate best when improved, and always considers the cross-run shared label. */
117
117
  updateArrival(stop: StopId, time: Time, round: number): void {
118
- this.currentRun.updateArrival(stop, time, round);
118
+ const currentRunArrival = this.currentRun.getArrival(stop);
119
+ const improvesCurrentRunAggregate =
120
+ currentRunArrival === undefined ||
121
+ time < currentRunArrival.arrival ||
122
+ (time === currentRunArrival.arrival &&
123
+ round < currentRunArrival.legNumber);
124
+
125
+ if (improvesCurrentRunAggregate) {
126
+ this.currentRun.updateArrival(stop, time, round);
127
+ }
128
+
119
129
  if (time < this.roundLabels[round]![stop]!) {
120
130
  this.roundLabels[round]![stop] = time;
121
131
  this.changedInRound[round]!.push(stop);