@ingissa/navcore-simulator 1.0.8 → 1.0.10

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.
Files changed (170) hide show
  1. package/LICENSE +3 -3
  2. package/README.md +161 -356
  3. package/dist/index.d.mts +87 -0
  4. package/dist/{simulator/src/react-native/useSimulator.d.ts → index.d.ts} +37 -9
  5. package/dist/index.js +1393 -0
  6. package/dist/index.mjs +1383 -0
  7. package/dist/node.d.mts +527 -0
  8. package/dist/node.d.ts +527 -0
  9. package/dist/node.js +957 -0
  10. package/dist/node.mjs +949 -0
  11. package/dist/react-native.d.mts +4 -0
  12. package/dist/react-native.d.ts +4 -0
  13. package/dist/react-native.js +1393 -0
  14. package/dist/react-native.mjs +1383 -0
  15. package/package.json +61 -58
  16. package/dist/core/src/NavCore.d.ts +0 -142
  17. package/dist/core/src/NavCore.d.ts.map +0 -1
  18. package/dist/core/src/NavCore.js +0 -390
  19. package/dist/core/src/bearing/BearingEngine.d.ts +0 -24
  20. package/dist/core/src/bearing/BearingEngine.d.ts.map +0 -1
  21. package/dist/core/src/bearing/BearingEngine.js +0 -50
  22. package/dist/core/src/constants.d.ts +0 -34
  23. package/dist/core/src/constants.d.ts.map +0 -1
  24. package/dist/core/src/constants.js +0 -41
  25. package/dist/core/src/corridor/DeviationDetector.d.ts +0 -33
  26. package/dist/core/src/corridor/DeviationDetector.d.ts.map +0 -1
  27. package/dist/core/src/corridor/DeviationDetector.js +0 -83
  28. package/dist/core/src/corridor/DynamicCorridor.d.ts +0 -13
  29. package/dist/core/src/corridor/DynamicCorridor.d.ts.map +0 -1
  30. package/dist/core/src/corridor/DynamicCorridor.js +0 -26
  31. package/dist/core/src/dead-reckoning/DeadReckoning.d.ts +0 -35
  32. package/dist/core/src/dead-reckoning/DeadReckoning.d.ts.map +0 -1
  33. package/dist/core/src/dead-reckoning/DeadReckoning.js +0 -49
  34. package/dist/core/src/directions/DirectionsProvider.d.ts +0 -70
  35. package/dist/core/src/directions/DirectionsProvider.d.ts.map +0 -1
  36. package/dist/core/src/directions/DirectionsProvider.js +0 -7
  37. package/dist/core/src/directions/OSRMDirectionsProvider.d.ts +0 -26
  38. package/dist/core/src/directions/OSRMDirectionsProvider.d.ts.map +0 -1
  39. package/dist/core/src/directions/OSRMDirectionsProvider.js +0 -90
  40. package/dist/core/src/directions/OpenRouteServiceProvider.d.ts +0 -30
  41. package/dist/core/src/directions/OpenRouteServiceProvider.d.ts.map +0 -1
  42. package/dist/core/src/directions/OpenRouteServiceProvider.js +0 -111
  43. package/dist/core/src/directions/ValhallaDirectionsProvider.d.ts +0 -33
  44. package/dist/core/src/directions/ValhallaDirectionsProvider.d.ts.map +0 -1
  45. package/dist/core/src/directions/ValhallaDirectionsProvider.js +0 -155
  46. package/dist/core/src/eta/ETAEngine.d.ts +0 -61
  47. package/dist/core/src/eta/ETAEngine.d.ts.map +0 -1
  48. package/dist/core/src/eta/ETAEngine.js +0 -74
  49. package/dist/core/src/features.d.ts +0 -76
  50. package/dist/core/src/features.d.ts.map +0 -1
  51. package/dist/core/src/features.js +0 -76
  52. package/dist/core/src/geo/bearing.d.ts +0 -11
  53. package/dist/core/src/geo/bearing.d.ts.map +0 -1
  54. package/dist/core/src/geo/bearing.js +0 -25
  55. package/dist/core/src/geo/cumulative.d.ts +0 -9
  56. package/dist/core/src/geo/cumulative.d.ts.map +0 -1
  57. package/dist/core/src/geo/cumulative.js +0 -20
  58. package/dist/core/src/geo/haversine.d.ts +0 -13
  59. package/dist/core/src/geo/haversine.d.ts.map +0 -1
  60. package/dist/core/src/geo/haversine.js +0 -39
  61. package/dist/core/src/geo/projection.d.ts +0 -7
  62. package/dist/core/src/geo/projection.d.ts.map +0 -1
  63. package/dist/core/src/geo/projection.js +0 -17
  64. package/dist/core/src/geofencing/GeofencingEngine.d.ts +0 -93
  65. package/dist/core/src/geofencing/GeofencingEngine.d.ts.map +0 -1
  66. package/dist/core/src/geofencing/GeofencingEngine.js +0 -154
  67. package/dist/core/src/index.d.ts +0 -66
  68. package/dist/core/src/index.d.ts.map +0 -1
  69. package/dist/core/src/index.js +0 -62
  70. package/dist/core/src/kalman/KalmanFilter2D.d.ts +0 -46
  71. package/dist/core/src/kalman/KalmanFilter2D.d.ts.map +0 -1
  72. package/dist/core/src/kalman/KalmanFilter2D.js +0 -77
  73. package/dist/core/src/license/LicenseManager.d.ts +0 -40
  74. package/dist/core/src/license/LicenseManager.d.ts.map +0 -1
  75. package/dist/core/src/license/LicenseManager.js +0 -119
  76. package/dist/core/src/license/verifyP256Signature.d.ts +0 -2
  77. package/dist/core/src/license/verifyP256Signature.d.ts.map +0 -1
  78. package/dist/core/src/license/verifyP256Signature.js +0 -26
  79. package/dist/core/src/off-route/NavigationFSM.d.ts +0 -42
  80. package/dist/core/src/off-route/NavigationFSM.d.ts.map +0 -1
  81. package/dist/core/src/off-route/NavigationFSM.js +0 -56
  82. package/dist/core/src/off-route/OffRouteFSM.d.ts +0 -70
  83. package/dist/core/src/off-route/OffRouteFSM.d.ts.map +0 -1
  84. package/dist/core/src/off-route/OffRouteFSM.js +0 -151
  85. package/dist/core/src/predictor/RouteOffsetPredictor.d.ts +0 -24
  86. package/dist/core/src/predictor/RouteOffsetPredictor.d.ts.map +0 -1
  87. package/dist/core/src/predictor/RouteOffsetPredictor.js +0 -55
  88. package/dist/core/src/progress/InstructionEditor.d.ts +0 -77
  89. package/dist/core/src/progress/InstructionEditor.d.ts.map +0 -1
  90. package/dist/core/src/progress/InstructionEditor.js +0 -151
  91. package/dist/core/src/progress/InstructionResolver.d.ts +0 -25
  92. package/dist/core/src/progress/InstructionResolver.d.ts.map +0 -1
  93. package/dist/core/src/progress/InstructionResolver.js +0 -78
  94. package/dist/core/src/progress/ProgressTracker.d.ts +0 -12
  95. package/dist/core/src/progress/ProgressTracker.d.ts.map +0 -1
  96. package/dist/core/src/progress/ProgressTracker.js +0 -22
  97. package/dist/core/src/renderer/MapRendererAdapter.d.ts +0 -72
  98. package/dist/core/src/renderer/MapRendererAdapter.d.ts.map +0 -1
  99. package/dist/core/src/renderer/MapRendererAdapter.js +0 -19
  100. package/dist/core/src/route-builder/CustomRouteBuilder.d.ts +0 -83
  101. package/dist/core/src/route-builder/CustomRouteBuilder.d.ts.map +0 -1
  102. package/dist/core/src/route-builder/CustomRouteBuilder.js +0 -160
  103. package/dist/core/src/route-plan/RoutePlan.d.ts +0 -38
  104. package/dist/core/src/route-plan/RoutePlan.d.ts.map +0 -1
  105. package/dist/core/src/route-plan/RoutePlan.js +0 -102
  106. package/dist/core/src/session/NavigationSession.d.ts +0 -81
  107. package/dist/core/src/session/NavigationSession.d.ts.map +0 -1
  108. package/dist/core/src/session/NavigationSession.js +0 -115
  109. package/dist/core/src/snapper/ParallelRoadResolver.d.ts +0 -86
  110. package/dist/core/src/snapper/ParallelRoadResolver.d.ts.map +0 -1
  111. package/dist/core/src/snapper/ParallelRoadResolver.js +0 -83
  112. package/dist/core/src/snapper/RouteSnapper.d.ts +0 -38
  113. package/dist/core/src/snapper/RouteSnapper.d.ts.map +0 -1
  114. package/dist/core/src/snapper/RouteSnapper.js +0 -80
  115. package/dist/core/src/voice/VoiceTriggerEngine.d.ts +0 -80
  116. package/dist/core/src/voice/VoiceTriggerEngine.d.ts.map +0 -1
  117. package/dist/core/src/voice/VoiceTriggerEngine.js +0 -108
  118. package/dist/simulator/src/core/NoiseEngine.d.ts +0 -46
  119. package/dist/simulator/src/core/NoiseEngine.d.ts.map +0 -1
  120. package/dist/simulator/src/core/NoiseEngine.js +0 -106
  121. package/dist/simulator/src/core/RouteWalker.d.ts +0 -78
  122. package/dist/simulator/src/core/RouteWalker.d.ts.map +0 -1
  123. package/dist/simulator/src/core/RouteWalker.js +0 -180
  124. package/dist/simulator/src/core/SimulatorEngine.d.ts +0 -53
  125. package/dist/simulator/src/core/SimulatorEngine.d.ts.map +0 -1
  126. package/dist/simulator/src/core/SimulatorEngine.js +0 -298
  127. package/dist/simulator/src/core/types.d.ts +0 -157
  128. package/dist/simulator/src/core/types.d.ts.map +0 -1
  129. package/dist/simulator/src/core/types.js +0 -7
  130. package/dist/simulator/src/fleet/FleetSimulator.d.ts +0 -60
  131. package/dist/simulator/src/fleet/FleetSimulator.d.ts.map +0 -1
  132. package/dist/simulator/src/fleet/FleetSimulator.js +0 -114
  133. package/dist/simulator/src/index.d.ts +0 -28
  134. package/dist/simulator/src/index.d.ts.map +0 -1
  135. package/dist/simulator/src/index.js +0 -29
  136. package/dist/simulator/src/node.d.ts +0 -26
  137. package/dist/simulator/src/node.d.ts.map +0 -1
  138. package/dist/simulator/src/node.js +0 -25
  139. package/dist/simulator/src/react-native/SimulatorControls.d.ts +0 -35
  140. package/dist/simulator/src/react-native/SimulatorControls.d.ts.map +0 -1
  141. package/dist/simulator/src/react-native/SimulatorControls.js +0 -254
  142. package/dist/simulator/src/react-native/useSimulator.d.ts.map +0 -1
  143. package/dist/simulator/src/react-native/useSimulator.js +0 -100
  144. package/dist/simulator/src/replay/GpxParser.d.ts +0 -40
  145. package/dist/simulator/src/replay/GpxParser.d.ts.map +0 -1
  146. package/dist/simulator/src/replay/GpxParser.js +0 -95
  147. package/dist/simulator/src/replay/SessionRecorder.d.ts +0 -37
  148. package/dist/simulator/src/replay/SessionRecorder.d.ts.map +0 -1
  149. package/dist/simulator/src/replay/SessionRecorder.js +0 -120
  150. package/dist/simulator/src/scenarios/ScenarioInjector.d.ts +0 -55
  151. package/dist/simulator/src/scenarios/ScenarioInjector.d.ts.map +0 -1
  152. package/dist/simulator/src/scenarios/ScenarioInjector.js +0 -150
  153. package/src/__tests__/integration/simulator-pipeline.test.ts +0 -233
  154. package/src/core/NoiseEngine.ts +0 -137
  155. package/src/core/RouteWalker.ts +0 -227
  156. package/src/core/SimulatorEngine.ts +0 -364
  157. package/src/core/__tests__/NoiseEngine.test.ts +0 -131
  158. package/src/core/__tests__/RouteWalker.test.ts +0 -139
  159. package/src/core/__tests__/SimulatorEngine.test.ts +0 -317
  160. package/src/core/types.ts +0 -191
  161. package/src/fleet/FleetSimulator.ts +0 -162
  162. package/src/index.ts +0 -54
  163. package/src/node.ts +0 -48
  164. package/src/react-native/SimulatorControls.tsx +0 -473
  165. package/src/react-native/useSimulator.ts +0 -155
  166. package/src/replay/GpxParser.ts +0 -122
  167. package/src/replay/SessionRecorder.ts +0 -138
  168. package/src/replay/__tests__/GpxParser.test.ts +0 -136
  169. package/src/scenarios/ScenarioInjector.ts +0 -170
  170. package/src/scenarios/__tests__/ScenarioInjector.test.ts +0 -171
@@ -1,180 +0,0 @@
1
- /**
2
- * RouteWalker — Manages traversal of a route geometry array.
3
- *
4
- * Extracted from the inline useGpsSimulator hook so the traversal logic
5
- * is testable independently of React, timers, or noise.
6
- *
7
- * ZERO dependencies beyond @navcore/core types.
8
- */
9
- import { getBearingBetweenPoints, haversineDistance, } from '@ingissa/navcore-core';
10
- export class RouteWalker {
11
- constructor(geometry, startIndex = 0) {
12
- if (geometry.length < 2) {
13
- throw new Error('RouteWalker requires at least 2 geometry points');
14
- }
15
- this.geometry = geometry;
16
- this._index = Math.max(0, Math.min(startIndex, geometry.length - 1));
17
- this._isComplete = false;
18
- // Pre-compute cumulative distances for O(1) distanceToIndex()
19
- this._cumulativeDistances = new Array(geometry.length).fill(0);
20
- for (let i = 1; i < geometry.length; i++) {
21
- const a = geometry[i - 1];
22
- const b = geometry[i];
23
- this._cumulativeDistances[i] =
24
- (this._cumulativeDistances[i - 1] ?? 0) +
25
- haversineDistance(a[1], a[0], b[1], b[0]);
26
- }
27
- this._distanceTravelled = this._cumulativeDistances[this._index] ?? 0;
28
- }
29
- // ── Public API ─────────────────────────────────────────────────────────────
30
- /**
31
- * Advance the walker by `steps` geometry points.
32
- * Fractional steps are supported for sub-second precision.
33
- */
34
- advance(steps) {
35
- if (this._isComplete)
36
- return this._currentPosition();
37
- this._index += steps;
38
- return this._finalizeAdvance();
39
- }
40
- /**
41
- * Advance the walker by a physical distance in metres.
42
- * This is the preferred way for smooth constant-speed simulation.
43
- */
44
- advanceByDistance(meters) {
45
- if (this._isComplete)
46
- return this._currentPosition();
47
- this._distanceTravelled += meters;
48
- if (this._distanceTravelled >= this.totalDistanceM) {
49
- this._distanceTravelled = this.totalDistanceM;
50
- this._index = this.geometry.length - 1;
51
- this._isComplete = true;
52
- }
53
- else {
54
- this._index = this._distanceToIndex(this._distanceTravelled);
55
- }
56
- return this._currentPosition();
57
- }
58
- _finalizeAdvance() {
59
- if (this._index >= this.geometry.length - 1) {
60
- this._index = this.geometry.length - 1;
61
- this._isComplete = true;
62
- }
63
- this._distanceTravelled = this._interpolatedDistance(this._index);
64
- return this._currentPosition();
65
- }
66
- /**
67
- * Seek directly to a geometry index (integer or fractional).
68
- * Recalculates bearing from the new position.
69
- */
70
- seek(index) {
71
- this._index = Math.max(0, Math.min(index, this.geometry.length - 1));
72
- this._isComplete = this._index >= this.geometry.length - 1;
73
- this._distanceTravelled = this._interpolatedDistance(this._index);
74
- return this._currentPosition();
75
- }
76
- /**
77
- * Get an interpolated coordinate between geometry[index] and geometry[index+1].
78
- * @param index - integer index
79
- * @param fraction - 0..1 interpolation factor
80
- */
81
- interpolate(index, fraction) {
82
- const clampedIdx = Math.max(0, Math.min(index, this.geometry.length - 2));
83
- const a = this.geometry[clampedIdx];
84
- const b = this.geometry[clampedIdx + 1];
85
- const f = Math.max(0, Math.min(1, fraction));
86
- return [a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f];
87
- }
88
- /**
89
- * Compute the forward-looking bearing at a given integer index.
90
- * At the last point, returns the bearing of the final segment.
91
- */
92
- bearingAt(index) {
93
- const clampedIdx = Math.max(0, Math.min(Math.floor(index), this.geometry.length - 2));
94
- const a = this.geometry[clampedIdx];
95
- const b = this.geometry[clampedIdx + 1];
96
- return getBearingBetweenPoints(a, b);
97
- }
98
- /**
99
- * Total haversine distance from the route start to the given index.
100
- * Uses the pre-computed cumulative distances (O(1)).
101
- */
102
- distanceAtIndex(index) {
103
- const clampedIdx = Math.max(0, Math.min(Math.floor(index), this.geometry.length - 1));
104
- return this._cumulativeDistances[clampedIdx] ?? 0;
105
- }
106
- /**
107
- * Alias for distanceAtIndex.
108
- */
109
- distanceToIndex(index) {
110
- return this.distanceAtIndex(index);
111
- }
112
- /**
113
- * Convert a distance in metres back to a fractional index.
114
- * Uses binary search for O(log N) performance.
115
- */
116
- _distanceToIndex(targetDistance) {
117
- if (targetDistance <= 0)
118
- return 0;
119
- if (targetDistance >= this.totalDistanceM)
120
- return this.geometry.length - 1;
121
- // Binary search for the segment [i, i+1] containing targetDistance
122
- let low = 0;
123
- let high = this._cumulativeDistances.length - 1;
124
- let i = 0;
125
- while (low <= high) {
126
- const mid = Math.floor((low + high) / 2);
127
- if (this._cumulativeDistances[mid] <= targetDistance) {
128
- i = mid;
129
- low = mid + 1;
130
- }
131
- else {
132
- high = mid - 1;
133
- }
134
- }
135
- // Now targetDistance is between i and i+1
136
- const dStart = this._cumulativeDistances[i];
137
- const dEnd = this._cumulativeDistances[i + 1];
138
- const segmentDist = dEnd - dStart;
139
- if (segmentDist <= 0)
140
- return i;
141
- const fraction = (targetDistance - dStart) / segmentDist;
142
- return i + fraction;
143
- }
144
- // ── Readonly state ──────────────────────────────────────────────────────────
145
- get totalDistanceM() {
146
- return this._cumulativeDistances[this.geometry.length - 1] ?? 0;
147
- }
148
- get totalPoints() {
149
- return this.geometry.length;
150
- }
151
- get isComplete() {
152
- return this._isComplete;
153
- }
154
- get currentIndex() {
155
- return this._index;
156
- }
157
- // ── Private helpers ─────────────────────────────────────────────────────────
158
- _currentPosition() {
159
- const floorIdx = Math.min(Math.floor(this._index), this.geometry.length - 2);
160
- const fraction = this._index - floorIdx;
161
- const coord = this.interpolate(floorIdx, fraction);
162
- const bearing = this.bearingAt(floorIdx);
163
- return {
164
- index: this._index,
165
- coord,
166
- bearing,
167
- distanceTravelled: this._distanceTravelled,
168
- isComplete: this._isComplete,
169
- };
170
- }
171
- /** Interpolate cumulative distance for fractional index */
172
- _interpolatedDistance(index) {
173
- const floor = Math.min(Math.floor(index), this.geometry.length - 1);
174
- const ceil = Math.min(floor + 1, this.geometry.length - 1);
175
- const fraction = index - floor;
176
- const dFloor = this._cumulativeDistances[floor] ?? 0;
177
- const dCeil = this._cumulativeDistances[ceil] ?? 0;
178
- return dFloor + (dCeil - dFloor) * fraction;
179
- }
180
- }
@@ -1,53 +0,0 @@
1
- /**
2
- * SimulatorEngine — The headless GPS simulation core.
3
- *
4
- * Works in Node.js for CI tests. NO React, NO Expo APIs.
5
- * All timers are cleaned up on destroy(). No memory leaks.
6
- *
7
- * State machine: idle → running ⇄ paused → complete
8
- */
9
- import type { SimPosition, SimulatorConfig, SimSession, Scenario } from './types';
10
- export type EngineState = 'idle' | 'running' | 'paused' | 'complete';
11
- export declare class SimulatorEngine {
12
- private readonly config;
13
- private walker;
14
- private noise;
15
- private injector;
16
- private _state;
17
- private _position;
18
- private _speedMultiplier;
19
- private _timer;
20
- private _lastTick;
21
- private static readonly TICK_MS;
22
- private _recording;
23
- private _recordedPositions;
24
- private _recordedScenarios;
25
- private _sessionId;
26
- private _sessionStart;
27
- private _noiseSamplesAdded;
28
- constructor(config: SimulatorConfig);
29
- start(): void;
30
- stop(): void;
31
- pause(): void;
32
- resume(): void;
33
- /**
34
- * Jump to any geometry index.
35
- * Bearing is recalculated from the new segment — seek() is always bearing-safe.
36
- */
37
- seek(geometryIndex: number): void;
38
- setSpeed(multiplier: 1 | 2 | 4 | 8): void;
39
- addScenario(scenario: Scenario): void;
40
- removeScenario(name: string): void;
41
- clearScenarios(): void;
42
- get state(): EngineState;
43
- get position(): SimPosition | null;
44
- get progress(): number;
45
- startRecording(sessionId?: string): void;
46
- stopRecording(): SimSession;
47
- destroy(): void;
48
- private _startTimer;
49
- private _stopTimer;
50
- private _tick;
51
- private _buildAndEmitPosition;
52
- }
53
- //# sourceMappingURL=SimulatorEngine.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SimulatorEngine.d.ts","sourceRoot":"","sources":["../../../../src/core/SimulatorEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAWlF,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;AAErE,qBAAa,eAAe;IAE1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAEiF;IAGxG,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,QAAQ,CAAmB;IAGnC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,gBAAgB,CAAgB;IACxC,OAAO,CAAC,MAAM,CAA+C;IAC7D,OAAO,CAAC,SAAS,CAAa;IAE9B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAM;IAGrC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,kBAAkB,CAAsD;IAChF,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,kBAAkB,CAAK;gBAEnB,MAAM,EAAE,eAAe;IA4CnC,KAAK,IAAI,IAAI;IAOb,IAAI,IAAI,IAAI;IAMZ,KAAK,IAAI,IAAI;IAMb,MAAM,IAAI,IAAI;IAMd;;;OAGG;IACH,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI;IAkBjC,QAAQ,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI;IAUzC,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAIrC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIlC,cAAc,IAAI,IAAI;IAMtB,IAAI,KAAK,IAAI,WAAW,CAEvB;IAED,IAAI,QAAQ,IAAI,WAAW,GAAG,IAAI,CAEjC;IAED,IAAI,QAAQ,IAAI,MAAM,CAGrB;IAID,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IASxC,aAAa,IAAI,UAAU;IAqC3B,OAAO,IAAI,IAAI;IAWf,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,KAAK;IA0Db,OAAO,CAAC,qBAAqB;CAoD9B"}
@@ -1,298 +0,0 @@
1
- /**
2
- * SimulatorEngine — The headless GPS simulation core.
3
- *
4
- * Works in Node.js for CI tests. NO React, NO Expo APIs.
5
- * All timers are cleaned up on destroy(). No memory leaks.
6
- *
7
- * State machine: idle → running ⇄ paused → complete
8
- */
9
- import { NoiseEngine } from './NoiseEngine';
10
- import { RouteWalker } from './RouteWalker';
11
- import { ScenarioInjector } from '../scenarios/ScenarioInjector';
12
- /** Generate a UUID v4 without external dependencies */
13
- function uuidV4() {
14
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
15
- const r = (Math.random() * 16) | 0;
16
- const v = c === 'x' ? r : (r & 0x3) | 0x8;
17
- return v.toString(16);
18
- });
19
- }
20
- export class SimulatorEngine {
21
- constructor(config) {
22
- // ── State ────────────────────────────────────────────────────────────────────
23
- this._state = 'idle';
24
- this._position = null;
25
- this._timer = null;
26
- this._lastTick = 0;
27
- // ── Recording ────────────────────────────────────────────────────────────────
28
- this._recording = false;
29
- this._recordedPositions = [];
30
- this._recordedScenarios = [];
31
- this._sessionId = null;
32
- this._sessionStart = null;
33
- this._noiseSamplesAdded = 0;
34
- const geometry = config.routeGeometry;
35
- if (!geometry || geometry.length < 2) {
36
- throw new Error('SimulatorEngine: routeGeometry must have at least 2 points');
37
- }
38
- this._speedMultiplier = config.speedMultiplier ?? 1;
39
- this.config = {
40
- routeGeometry: geometry,
41
- speedKmh: config.speedKmh ?? 50,
42
- speedMultiplier: this._speedMultiplier,
43
- noiseProfile: config.noiseProfile ?? NoiseEngine.STANDARD,
44
- loop: config.loop ?? false,
45
- startIndex: config.startIndex ?? 0,
46
- autoStart: config.autoStart ?? false,
47
- onPositionUpdate: config.onPositionUpdate,
48
- onRouteComplete: config.onRouteComplete,
49
- onScenarioStart: config.onScenarioStart,
50
- onScenarioEnd: config.onScenarioEnd,
51
- };
52
- this.walker = new RouteWalker(geometry, this.config.startIndex);
53
- this.noise = new NoiseEngine(this.config.noiseProfile);
54
- this.injector = new ScenarioInjector((s) => {
55
- if (this._recording) {
56
- this._recordedScenarios.push({ scenario: s, firedAt: Date.now() });
57
- }
58
- try {
59
- this.config.onScenarioStart?.(s);
60
- }
61
- catch (_) { /* never throw */ }
62
- }, (s) => {
63
- try {
64
- this.config.onScenarioEnd?.(s);
65
- }
66
- catch (_) { /* never throw */ }
67
- });
68
- if (this.config.autoStart) {
69
- this.start();
70
- }
71
- }
72
- // ── Playback controls ────────────────────────────────────────────────────────
73
- start() {
74
- if (this._state === 'running')
75
- return;
76
- if (this._state === 'complete')
77
- return;
78
- this._state = 'running';
79
- this._startTimer();
80
- }
81
- stop() {
82
- this._stopTimer();
83
- this._state = 'idle';
84
- this._position = null;
85
- }
86
- pause() {
87
- if (this._state !== 'running')
88
- return;
89
- this._stopTimer();
90
- this._state = 'paused';
91
- }
92
- resume() {
93
- if (this._state !== 'paused')
94
- return;
95
- this._state = 'running';
96
- this._startTimer();
97
- }
98
- /**
99
- * Jump to any geometry index.
100
- * Bearing is recalculated from the new segment — seek() is always bearing-safe.
101
- */
102
- seek(geometryIndex) {
103
- const wasRunning = this._state === 'running';
104
- if (wasRunning)
105
- this._stopTimer();
106
- const pos = this.walker.seek(geometryIndex);
107
- this._buildAndEmitPosition(pos.coord, pos.bearing, pos.distanceTravelled, false);
108
- // Reset the completion flag so simulation can continue from the new index
109
- if (this._state === 'complete' && !pos.isComplete) {
110
- this._state = 'idle';
111
- }
112
- if (wasRunning && !pos.isComplete) {
113
- this._state = 'running';
114
- this._startTimer();
115
- }
116
- }
117
- setSpeed(multiplier) {
118
- this._speedMultiplier = multiplier;
119
- if (this._state === 'running') {
120
- this._stopTimer();
121
- this._startTimer();
122
- }
123
- }
124
- // ── Scenario management ──────────────────────────────────────────────────────
125
- addScenario(scenario) {
126
- this.injector.register(scenario);
127
- }
128
- removeScenario(name) {
129
- this.injector.remove(name);
130
- }
131
- clearScenarios() {
132
- this.injector.clear();
133
- }
134
- // ── State accessors ──────────────────────────────────────────────────────────
135
- get state() {
136
- return this._state;
137
- }
138
- get position() {
139
- return this._position;
140
- }
141
- get progress() {
142
- if (this.walker.totalPoints <= 1)
143
- return 0;
144
- return Math.min(1, this.walker.currentIndex / (this.walker.totalPoints - 1));
145
- }
146
- // ── Session recording ────────────────────────────────────────────────────────
147
- startRecording(sessionId) {
148
- this._recording = true;
149
- this._recordedPositions = [];
150
- this._recordedScenarios = [];
151
- this._sessionId = sessionId ?? uuidV4();
152
- this._sessionStart = Date.now();
153
- this._noiseSamplesAdded = 0;
154
- }
155
- stopRecording() {
156
- this._recording = false;
157
- const endedAt = Date.now();
158
- const startedAt = this._sessionStart ?? endedAt;
159
- const positions = this._recordedPositions;
160
- const durationMs = endedAt - startedAt;
161
- let distanceTravelledM = 0;
162
- if (positions.length > 0) {
163
- distanceTravelledM = positions[positions.length - 1]?.metadata?.distanceTravelled ?? 0;
164
- }
165
- const avgSpeedKmh = durationMs > 0
166
- ? ((distanceTravelledM / (durationMs / 1000)) * 3.6)
167
- : 0;
168
- return {
169
- id: this._sessionId ?? uuidV4(),
170
- startedAt: new Date(startedAt).toISOString(),
171
- endedAt: new Date(endedAt).toISOString(),
172
- route: this.config.routeGeometry,
173
- positions,
174
- scenarios: this._recordedScenarios,
175
- stats: {
176
- durationMs,
177
- distanceTravelledM,
178
- avgSpeedKmh,
179
- noiseSamplesAdded: this._noiseSamplesAdded,
180
- scenariosFired: this._recordedScenarios.length,
181
- },
182
- };
183
- }
184
- // ── Destroy ──────────────────────────────────────────────────────────────────
185
- destroy() {
186
- this._stopTimer();
187
- this.injector.destroy();
188
- this._state = 'idle';
189
- this._position = null;
190
- this._recording = false;
191
- this._recordedPositions = [];
192
- }
193
- // ── Private ──────────────────────────────────────────────────────────────────
194
- _startTimer() {
195
- this._lastTick = Date.now();
196
- const intervalMs = Math.round(1000 / this._speedMultiplier);
197
- this._timer = setInterval(() => this._tick(), intervalMs);
198
- }
199
- _stopTimer() {
200
- if (this._timer !== null) {
201
- clearInterval(this._timer);
202
- this._timer = null;
203
- }
204
- }
205
- _tick() {
206
- if (this._state !== 'running')
207
- return;
208
- const now = Date.now();
209
- this._lastTick = now;
210
- const speedMs = (this.config.speedKmh / 3.6);
211
- const walkerPos = this.walker.advance(1);
212
- // Fire any scenarios at this index
213
- const activeScenarioName = this.injector.tick(walkerPos.index);
214
- // Apply noise (skip if GPS_BLACKOUT scenario is active)
215
- const activeScenario = this.injector.activeScenario;
216
- let coord = walkerPos.coord;
217
- let isNoisy = false;
218
- if (activeScenario?.type !== 'GPS_BLACKOUT') {
219
- coord = this.noise.apply(walkerPos.coord);
220
- isNoisy = this.noise.currentProfile.stdDevM > 0;
221
- if (isNoisy)
222
- this._noiseSamplesAdded++;
223
- }
224
- // Compute speed — apply speed spike if active
225
- //let speedMs = (this.config.speedKmh / 3.6);
226
- if (activeScenario?.type === 'SPEED_SPIKE') {
227
- const spikeKmh = activeScenario.params?.['spikeKmh'] ?? this.config.speedKmh;
228
- //speedMs = spikeKmh / 3.6;
229
- }
230
- // Compute accuracy — degrade if ACCURACY_DROP scenario is active
231
- let accuracy = 8 + Math.random() * 6;
232
- if (activeScenario?.type === 'ACCURACY_DROP') {
233
- accuracy = activeScenario.params?.['newAccuracyM'] ?? accuracy;
234
- }
235
- this._buildAndEmitPosition(coord, walkerPos.bearing, walkerPos.distanceTravelled, isNoisy, {
236
- speedMs,
237
- accuracy,
238
- activeScenarioName,
239
- segmentIndex: Math.floor(walkerPos.index),
240
- });
241
- if (walkerPos.isComplete) {
242
- this._stopTimer();
243
- if (this.config.loop) {
244
- // Restart from the beginning
245
- this.walker = new RouteWalker(this.config.routeGeometry, 0);
246
- this._startTimer();
247
- }
248
- else {
249
- this._state = 'complete';
250
- }
251
- try {
252
- this.config.onRouteComplete?.();
253
- }
254
- catch (_) { /* never throw */ }
255
- }
256
- }
257
- _buildAndEmitPosition(coord, bearing, distanceTravelled, isNoisy, opts) {
258
- const speedMs = opts?.speedMs ?? (this.config.speedKmh / 3.6);
259
- const accuracy = opts?.accuracy ?? 8;
260
- const ts = Date.now();
261
- const locationCoords = {
262
- latitude: coord[1],
263
- longitude: coord[0],
264
- altitude: 0,
265
- accuracy,
266
- altitudeAccuracy: null,
267
- heading: bearing,
268
- speed: speedMs,
269
- };
270
- const pos = {
271
- coord: coord,
272
- bearing,
273
- speed: speedMs,
274
- accuracy,
275
- timestamp: ts,
276
- coords: locationCoords,
277
- location: {
278
- coords: locationCoords,
279
- timestamp: ts,
280
- },
281
- metadata: {
282
- segmentIndex: opts?.segmentIndex ?? Math.floor(this.walker.currentIndex),
283
- distanceTravelled,
284
- isNoisy,
285
- scenarioActive: opts?.activeScenarioName,
286
- },
287
- };
288
- this._position = pos;
289
- if (this._recording) {
290
- this._recordedPositions.push(pos);
291
- }
292
- try {
293
- this.config.onPositionUpdate?.(pos);
294
- }
295
- catch (_) { /* never throw */ }
296
- }
297
- }
298
- SimulatorEngine.TICK_MS = 50; // 20 FPS for smooth movement
@@ -1,157 +0,0 @@
1
- /**
2
- * @navcore/simulator — Core Types
3
- *
4
- * These types are the foundation of the entire simulator package.
5
- * All other modules build on top of these definitions.
6
- */
7
- /** [longitude, latitude] — Mapbox/GeoJSON order */
8
- export type Coordinate = [number, number];
9
- /**
10
- * Standard coords conforming to expo-location LocationObject['coords']
11
- * and @react-native-community/geolocation.
12
- */
13
- export interface SimLocationCoords {
14
- latitude: number;
15
- longitude: number;
16
- altitude: number | null;
17
- accuracy: number | null;
18
- altitudeAccuracy: number | null;
19
- heading: number | null;
20
- speed: number | null;
21
- }
22
- /**
23
- * Standard LocationObject conforming to expo-location.
24
- */
25
- export interface SimLocationObject {
26
- coords: SimLocationCoords;
27
- timestamp: number;
28
- }
29
- /**
30
- * A single simulated GPS position, emitted by SimulatorEngine on every tick.
31
- * Shape includes standard LocationObject coords for drop-in Expo/React Native usage.
32
- */
33
- export interface SimPosition {
34
- /** [longitude, latitude] */
35
- coord: Coordinate;
36
- /** Heading in degrees 0–360 */
37
- bearing: number;
38
- /** Speed in metres per second */
39
- speed: number;
40
- /** Simulated GPS accuracy radius in metres */
41
- accuracy: number;
42
- /** Unix timestamp in milliseconds */
43
- timestamp: number;
44
- /**
45
- * Standard LocationObject coords conforming to expo-location & React Native geolocation.
46
- */
47
- coords: SimLocationCoords;
48
- /**
49
- * Full standard LocationObject conforming to expo-location.
50
- */
51
- location?: SimLocationObject;
52
- /** Optional debug metadata */
53
- metadata?: {
54
- /** Which geometry segment the vehicle is currently on */
55
- segmentIndex: number;
56
- /** Total metres travelled from route start */
57
- distanceTravelled: number;
58
- /** Whether noise was applied on this tick */
59
- isNoisy: boolean;
60
- /** Name of the active scenario, if any */
61
- scenarioActive?: string;
62
- };
63
- }
64
- /**
65
- * Noise model applied to simulated GPS coordinates each tick.
66
- */
67
- export interface NoiseProfile {
68
- /** Noise type */
69
- type: 'NONE' | 'GAUSSIAN' | 'URBAN' | 'TUNNEL' | 'CUSTOM';
70
- /** Standard deviation of the Gaussian noise in metres (default: 12) */
71
- stdDevM: number;
72
- /**
73
- * Optional seed for deterministic noise generation.
74
- * Same seed + same route = identical output (critical for regression tests).
75
- */
76
- seed?: number;
77
- /**
78
- * For CUSTOM type: return [lngOffset, latOffset] for each tick.
79
- * @param index - current geometry segment index
80
- */
81
- customFn?: (index: number) => [number, number];
82
- }
83
- /**
84
- * A scenario that is injected at a specific geometry index during simulation.
85
- */
86
- export interface Scenario {
87
- /** Unique name for this scenario */
88
- name: string;
89
- /** Geometry index at which the scenario fires */
90
- triggerIndex: number;
91
- /** Scenario type */
92
- type: 'GPS_BLACKOUT' | 'WRONG_SIDE_ROAD' | 'UTURN' | 'SPEED_SPIKE' | 'ACCURACY_DROP' | 'CUSTOM';
93
- /** How long the scenario lasts in milliseconds */
94
- durationMs?: number;
95
- /** Scenario-specific configuration */
96
- params?: Record<string, unknown>;
97
- }
98
- /**
99
- * Full configuration object passed to SimulatorEngine.
100
- * All callback fields are optional — the engine is fully usable without them
101
- * (useful for CI/headless tests).
102
- */
103
- export interface SimulatorConfig {
104
- /** The route geometry to simulate along */
105
- routeGeometry: Coordinate[];
106
- /** Simulated vehicle speed in km/h (default: 50) */
107
- speedKmh?: number;
108
- /** Playback speed multiplier: 1×, 2×, 4×, 8× (default: 1) */
109
- speedMultiplier?: 1 | 2 | 4 | 8;
110
- /** Noise model applied to each position (default: NoiseEngine.STANDARD) */
111
- noiseProfile?: NoiseProfile;
112
- /** Whether to restart from the beginning when the route ends (default: false) */
113
- loop?: boolean;
114
- /** Start simulation from this geometry index (default: 0) */
115
- startIndex?: number;
116
- /** If true, start() is called automatically after construction (default: false) */
117
- autoStart?: boolean;
118
- /** Called on every tick with the new simulated position */
119
- onPositionUpdate?: (pos: SimPosition) => void;
120
- /** Called when the route end is reached (or loop restarts) */
121
- onRouteComplete?: () => void;
122
- /** Called when a scenario begins */
123
- onScenarioStart?: (scenario: Scenario) => void;
124
- /** Called when a scenario ends */
125
- onScenarioEnd?: (scenario: Scenario) => void;
126
- }
127
- /**
128
- * A complete recorded simulation session.
129
- * Produced by SimulatorEngine.stopRecording() / SessionRecorder.stop().
130
- */
131
- export interface SimSession {
132
- /** UUID v4 session identifier */
133
- id: string;
134
- /** ISO 8601 start timestamp */
135
- startedAt: string;
136
- /** ISO 8601 end timestamp */
137
- endedAt: string;
138
- /** The route geometry used */
139
- route: Coordinate[];
140
- /** All SimPositions emitted during the session */
141
- positions: SimPosition[];
142
- /** Scenarios that fired, with their tick timestamps */
143
- scenarios: Array<{
144
- scenario: Scenario;
145
- /** Unix ms when the scenario fired */
146
- firedAt: number;
147
- }>;
148
- /** Aggregate statistics for this session */
149
- stats: {
150
- durationMs: number;
151
- distanceTravelledM: number;
152
- avgSpeedKmh: number;
153
- noiseSamplesAdded: number;
154
- scenariosFired: number;
155
- };
156
- }
157
- //# sourceMappingURL=types.d.ts.map