@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
package/LICENSE CHANGED
@@ -1,3 +1,3 @@
1
- Copyright (c) 2026 Abdelnasser Aitabi. All rights reserved.
2
-
3
- No license is granted except by separate written agreement.
1
+ Copyright (c) 2026 Abdelnasser Aitabi. All rights reserved.
2
+
3
+ No license is granted except by separate written agreement.
package/README.md CHANGED
@@ -1,64 +1,92 @@
1
- # @ingissa/navcore-simulator
1
+ # @ingissa/navcore-simulator
2
+
3
+ [Official Website](https://navcore-web.vercel.app/)
2
4
 
3
5
  > Headless GPS simulation engine for the NavCore ecosystem.
4
- > Works in **Node.js** (CI), **React Native**, and any JavaScript environment.
6
+ > Works in **Node.js** (CI/CD), **React Native**, and browser runtimes.
7
+
8
+ [![npm version](https://img.shields.io/npm/v/@ingissa/navcore-simulator)](https://www.npmjs.com/package/@ingissa/navcore-simulator)
9
+ ![License](https://img.shields.io/badge/license-Proprietary-red)
10
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](https://www.typescriptlang.org/)
5
11
 
6
12
  ---
7
13
 
8
14
  ## Contents
9
15
 
16
+ - [Overview](#overview)
10
17
  - [Installation](#installation)
11
18
  - [Quick Start](#quick-start)
12
19
  - [Core Classes](#core-classes)
13
- - [SimulatorEngine](#simulatorengine)
14
- - [RouteWalker](#routewalker)
15
- - [NoiseEngine](#noiseengine)
16
- - [ScenarioInjector](#scenarioinjector)
17
- - [GpxParser](#gpxparser)
18
- - [SessionRecorder](#sessionrecorder)
19
- - [FleetSimulator](#fleetsimulator-phase-2)
20
- - [React Native](#react-native)
21
- - [useSimulator](#usesimulator)
22
- - [SimulatorControls](#simulatorcontrols)
20
+ - [1. SimulatorEngine](#1-simulatorengine)
21
+ - [2. RouteWalker](#2-routewalker)
22
+ - [3. NoiseEngine](#3-noiseengine)
23
+ - [4. ScenarioInjector](#4-scenarioinjector)
24
+ - [5. GpxParser](#5-gpxparser)
25
+ - [6. SessionRecorder](#6-sessionrecorder)
26
+ - [7. FleetSimulator](#7-fleetsimulator)
27
+ - [React Native Integration](#react-native-integration)
23
28
  - [Noise Profiles](#noise-profiles)
24
- - [Scenario Types](#scenario-types)
25
- - [GPX Round-Trip Loop](#gpx-round-trip-loop)
26
- - [CI / Regression Testing](#ci--regression-testing)
27
- - [Migration from useGpsSimulator](#migration-from-usegpssimulator)
29
+ - [CI Regression Testing](#ci-regression-testing)
30
+ - [Licensing](#licensing)
31
+
32
+ ---
33
+
34
+ ## Overview
35
+
36
+ `@ingissa/navcore-simulator` provides realistic GPS signal simulation for development, UI validation, and automated regression testing. It simulates vehicle movement along route geometry with configurable speeds, bearing interpolation, realistic Gaussian noise, and dynamic scenario faults (such as GPS blackouts, sensor jitter, and temporary deviations).
28
37
 
29
38
  ---
30
39
 
31
40
  ## Installation
32
41
 
33
42
  ```bash
34
- # Inside the go4exams monorepo — already available via workspace
35
- # Import directly from the source tree:
36
- import { SimulatorEngine, NoiseEngine } from '@ingissa/navcore-simulator'
43
+ npm install @ingissa/navcore-simulator @ingissa/navcore-core
37
44
  ```
38
45
 
39
- **Peer dependencies:** `@ingissa/navcore-core` (already in monorepo). React Native hooks additionally require `react` and `react-native`, which are optional peer deps (tree-shaken in Node.js CI).
46
+ ### Peer Dependencies
47
+ - `@ingissa/navcore-core`: `^1.0.9` (required)
48
+ - `react`: `>=18` (optional, needed only for React Native UI hooks)
49
+ - `react-native`: `>=0.72` (optional, needed only for React Native UI hooks)
50
+
51
+ ### Subpath Exports
52
+ - `@ingissa/navcore-simulator`: Universal export containing engine, utilities, and UI controls.
53
+ - `@ingissa/navcore-simulator/node`: Headless export for Node.js scripts, Vitest/Jest suites, and CI pipelines (zero React/DOM dependencies).
54
+ - `@ingissa/navcore-simulator/react-native`: Mobile export with `useSimulator` and `SimulatorControls`.
40
55
 
41
56
  ---
42
57
 
43
58
  ## Quick Start
44
59
 
60
+ ### Node.js / CI Headless Simulation
61
+
45
62
  ```typescript
46
- import { SimulatorEngine, NoiseEngine, ScenarioInjector } from '@ingissa/navcore-simulator';
63
+ import { SimulatorEngine, NoiseEngine, ScenarioInjector } from '@ingissa/navcore-simulator/node';
64
+ import type { Coordinate } from '@ingissa/navcore-core';
65
+
66
+ const routeGeometry: Coordinate[] = [
67
+ [2.3522, 48.8566],
68
+ [2.3400, 48.8650],
69
+ [2.3009, 48.8741],
70
+ ];
47
71
 
48
72
  const engine = new SimulatorEngine({
49
- routeGeometry: myRoute, // Coordinate[] from @ingissa/navcore-core
73
+ routeGeometry,
50
74
  speedKmh: 50,
51
- speedMultiplier: 4, // 4× real-time
75
+ speedMultiplier: 2, // 2× real-time playback
52
76
  noiseProfile: NoiseEngine.STANDARD,
53
77
  loop: false,
54
78
  onPositionUpdate: (pos) => {
55
- console.log(pos.coord, pos.bearing, pos.speed);
79
+ console.log(`GPS: [${pos.coord[0].toFixed(5)}, ${pos.coord[1].toFixed(5)}] | Heading: ${Math.round(pos.bearing)}° | Speed: ${pos.speed} m/s`);
80
+ },
81
+ onRouteComplete: () => {
82
+ console.log('Simulation journey finished.');
56
83
  },
57
- onRouteComplete: () => console.log('Route complete'),
58
84
  });
59
85
 
60
- engine.addScenario(ScenarioInjector.gpsBlackout(42, 5000));
61
- engine.startRecording();
86
+ // Inject a 5-second GPS blackout at geometry segment 10
87
+ engine.addScenario(ScenarioInjector.gpsBlackout(10, 5000));
88
+
89
+ // Start playback
62
90
  engine.start();
63
91
  ```
64
92
 
@@ -66,361 +94,138 @@ engine.start();
66
94
 
67
95
  ## Core Classes
68
96
 
69
- ### SimulatorEngine
70
-
71
- The main orchestrator. Headless — no React, no Expo APIs. Runs in Node.js CI.
97
+ ### 1. `SimulatorEngine`
98
+ The central stateful playback controller:
99
+ - `start()`: Begin or resume playback.
100
+ - `pause()`: Suspend playback at the current position.
101
+ - `stop()`: Halt simulation and reset to starting coordinate.
102
+ - `seek(geometryIndex: number)`: Jump directly to any waypoint along the route geometry.
103
+ - `setSpeed(multiplier: 1 | 2 | 4 | 8)`: Adjust playback speed.
104
+ - `addScenario(scenario)`: Schedule a fault or anomaly.
105
+ - `startRecording()` / `stopRecording()`: Capture recorded positions as a `SimSession`.
72
106
 
73
- ```typescript
74
- const engine = new SimulatorEngine(config: SimulatorConfig);
75
-
76
- // Playback
77
- engine.start()
78
- engine.stop()
79
- engine.pause()
80
- engine.resume()
81
- engine.seek(geometryIndex: number) // jump to any route index, recalculates bearing
82
- engine.setSpeed(1 | 2 | 4 | 8)
83
-
84
- // Scenarios
85
- engine.addScenario(scenario: Scenario)
86
- engine.removeScenario(name: string)
87
- engine.clearScenarios()
88
-
89
- // State
90
- engine.state // 'idle' | 'running' | 'paused' | 'complete'
91
- engine.position // SimPosition | null
92
- engine.progress // 0–1
93
-
94
- // Recording
95
- engine.startRecording(sessionId?: string)
96
- engine.stopRecording(): SimSession
97
-
98
- // Cleanup
99
- engine.destroy() // clears all timers — no leaks
100
- ```
107
+ ### 2. `RouteWalker`
108
+ Deterministic geometric interpolation engine that moves a virtual point along a polyline at a constant physical speed (metres per second), computing exact distance and forward bearing.
101
109
 
102
- **Timer internals:** `setInterval` at `1000ms / speedMultiplier`. Always cleaned up on `destroy()` and on natural completion.
110
+ ### 3. `NoiseEngine`
111
+ Applies Gaussian noise perturbations to latitude, longitude, and bearing based on configurable standard deviations.
103
112
 
104
- **Callback safety:** All callbacks (`onPositionUpdate`, `onRouteComplete`, `onScenarioStart`, `onScenarioEnd`) are wrapped in `try/catch` — a throwing callback never crashes the engine.
113
+ ### 4. `ScenarioInjector`
114
+ Factory for simulated road anomalies:
115
+ - `ScenarioInjector.gpsBlackout(index, durationMs)`: Emulates tunnels or signal loss by omitting position updates.
116
+ - `ScenarioInjector.accuracyDegradation(index, durationMs, accuracyM)`: Emulates urban canyons with high accuracy error bounds.
117
+ - `ScenarioInjector.speedSpike(index, durationMs, speedMultiplier)`: Simulates vehicle acceleration bursts.
118
+ - `ScenarioInjector.offRouteDrift(index, driftMeters, durationMs)`: Moves vehicle laterally outside the route corridor to trigger off-route FSMs.
105
119
 
106
- ---
120
+ ### 5. `GpxParser`
121
+ Parses `.gpx` XML tracks into arrays of `[longitude, latitude]` coordinates with elevation and timestamps.
107
122
 
108
- ### RouteWalker
123
+ ### 6. `SessionRecorder`
124
+ Records simulation output into JSON sessions for regression testing and replay.
109
125
 
110
- Manages geometry traversal. Extracted from the engine so it's independently testable.
111
-
112
- ```typescript
113
- const walker = new RouteWalker(geometry: Coordinate[], startIndex?: number);
114
-
115
- walker.advance(steps: number): WalkerPosition // fractional steps supported
116
- walker.seek(index: number): WalkerPosition
117
- walker.interpolate(index: number, fraction: number): Coordinate
118
- walker.bearingAt(index: number): number // forward-looking bearing
119
-
120
- walker.totalDistanceM // total route length in metres
121
- walker.totalPoints // geometry.length
122
- walker.isComplete // true when at last point
123
- walker.currentIndex // current floating-point index
124
- ```
126
+ ### 7. `FleetSimulator`
127
+ Simulates multiple concurrent vehicles along independent routes for backend ingestion testing.
125
128
 
126
129
  ---
127
130
 
128
- ### NoiseEngine
129
-
130
- GPS noise simulation using the Box-Muller transform. Zero external dependencies.
131
-
132
- ```typescript
133
- const engine = new NoiseEngine(profile: NoiseProfile);
134
-
135
- engine.apply(coord: Coordinate): Coordinate // returns noisy coord (never mutates input)
136
- engine.setProfile(profile: NoiseProfile) // hot-swap mid-simulation
131
+ ## React Native Integration
137
132
 
138
- // Static presets
139
- NoiseEngine.NONE // stdDevM: 0 — exact coordinates
140
- NoiseEngine.CLEAN // stdDevM: 3 — good urban GPS
141
- NoiseEngine.STANDARD // stdDevM: 12 — matches the original Go4Exams ±0.00011°
142
- NoiseEngine.URBAN // stdDevM: 25 — city canyon
143
- NoiseEngine.DEGRADED // stdDevM: 50 — bad conditions
144
- ```
145
-
146
- **Seeded mode:** Add `seed: number` to any profile for deterministic output — same seed + same route = identical noise trace (critical for regression tests).
147
-
148
- ```typescript
149
- const deterministicProfile = { ...NoiseEngine.STANDARD, seed: 42 };
150
- ```
151
-
152
- ---
153
-
154
- ### ScenarioInjector
155
-
156
- Registers scenarios to fire at geometry indices. Called internally by `SimulatorEngine` on every tick.
157
-
158
- ```typescript
159
- const injector = new ScenarioInjector(onStart?, onEnd?);
160
-
161
- injector.register(scenario: Scenario)
162
- injector.remove(name: string)
163
- injector.clear()
164
- injector.tick(currentIndex: number): string | undefined // returns active scenario name
165
-
166
- // Static factories
167
- ScenarioInjector.gpsBlackout(atIndex, durationMs)
168
- ScenarioInjector.wrongSideRoad(atIndex, offsetM)
169
- ScenarioInjector.uTurn(atIndex)
170
- ScenarioInjector.speedSpike(atIndex, spikeKmh, durationMs?)
171
- ScenarioInjector.accuracyDrop(atIndex, newAccuracyM, durationMs)
172
- ```
173
-
174
- **No double-fire:** Each scenario fires exactly once per simulation run, even if `tick()` is called with the same index multiple times.
175
-
176
- ---
177
-
178
- ### GpxParser
179
-
180
- Zero-dependency GPX 1.1 parser. Pure string/regex — no DOMParser, no xml2js.
181
-
182
- ```typescript
183
- // Parse GPX string
184
- const result = GpxParser.parse(gpxString: string): GpxParseResult
185
-
186
- // Load from file (Node.js only)
187
- const result = GpxParser.fromFile('/path/to/route.gpx')
188
-
189
- // Load from base64 (React Native file picker)
190
- const result = GpxParser.fromBase64(base64String)
191
- ```
192
-
193
- ```typescript
194
- interface GpxParseResult {
195
- coordinates: Coordinate[]; // [lng, lat] — NavCore convention
196
- timestamps: number[]; // ms since epoch per point
197
- metadata: {
198
- name?: string;
199
- totalPoints: number;
200
- durationMs: number;
201
- distanceM: number;
202
- avgSpeedKmh: number;
203
- };
204
- }
205
- ```
206
-
207
- Supports both `<trkpt>` (track points) and `<wpt>` (waypoints).
208
-
209
- ---
210
-
211
- ### SessionRecorder
212
-
213
- Records and exports simulation sessions.
214
-
215
- ```typescript
216
- const recorder = new SessionRecorder();
217
- recorder.start(sessionId?: string)
218
- recorder.stop(existing?: SimSession): SimSession
219
-
220
- // Export formats
221
- recorder.export('json') // full SimSession as JSON
222
- recorder.export('gpx') // standard GPX 1.1 — re-importable by GpxParser
223
- recorder.export('csv') // timestamp,lng,lat,bearing,speed,accuracy
224
- ```
225
-
226
- **The GPX export is critical:** a recorded session → GPX → `GpxParser` → `SimulatorEngine` creates a full regression test loop.
227
-
228
- ---
229
-
230
- ### FleetSimulator (Phase 2)
231
-
232
- ```typescript
233
- const fleet = FleetSimulator.spawn(5, {
234
- routeGeometry: myRoute,
235
- speedKmh: 50,
236
- noiseProfile: NoiseEngine.STANDARD,
237
- profiles: [
238
- { label: 'urban', noiseProfile: NoiseEngine.URBAN },
239
- { label: 'clean', noiseProfile: NoiseEngine.CLEAN },
240
- ],
241
- });
242
-
243
- fleet.start();
244
- // ... wait for completion
245
- const report = fleet.report();
246
- // report.comparison.avgScoreByProfile
247
- // report.comparison.deviationsBySegment (heatmap data)
248
- ```
249
-
250
- > **@experimental** — fully typed but not tested in Phase 1.
251
-
252
- ---
253
-
254
- ## React Native
255
-
256
- ### useSimulator
257
-
258
- Drop-in replacement for `useGpsSimulator`:
259
-
260
- ```typescript
261
- // BEFORE
262
- const simGps = useGpsSimulator(route, isNavigating, speedMultiplier);
263
-
264
- // AFTER
265
- const { position, controls, session, isRecording } = useSimulator({
266
- routeGeometry: route,
267
- active: isNavigating,
268
- config: {
269
- speedKmh: 50,
270
- speedMultiplier: 1,
271
- noiseProfile: NoiseEngine.STANDARD,
272
- loop: false,
273
- },
274
- scenarios: [
275
- ScenarioInjector.gpsBlackout(42, 5000),
276
- ScenarioInjector.wrongSideRoad(78, 18),
277
- ],
278
- onScenarioFired: (s) => console.log('Scenario:', s.name),
279
- });
280
-
281
- // position has the exact same shape as the old SimPosition
282
- // controls: { pause, resume, seek, setSpeed }
283
- // session: { start, stop } — for recording
284
- ```
285
-
286
- **The engine is recreated only when `routeGeometry` changes** (identity comparison). Destroyed automatically on unmount.
287
-
288
- ---
289
-
290
- ### SimulatorControls
291
-
292
- Self-contained control panel. Drop it into any screen:
133
+ In React Native apps, render the built-in control overlay:
293
134
 
294
135
  ```tsx
295
- <SimulatorControls
296
- simulator={controls}
297
- session={session}
298
- style="minimal" // 'minimal' | 'full' | 'floating'
299
- showScenarioLog={true}
300
- showSessionRecorder={true}
301
- currentSpeed={1}
302
- engineState="running"
303
- isRecording={isRecording}
304
- onExportSession={(s) => shareFile(JSON.stringify(s))}
305
- />
136
+ import React from 'react';
137
+ import { View } from 'react-native';
138
+ import { useSimulator, SimulatorControls } from '@ingissa/navcore-simulator/react-native';
139
+ import { useNavCoreContext } from '@ingissa/navcore-react-native';
140
+
141
+ export function SimulationScreen({ routeGeometry }) {
142
+ const { engine: navCore } = useNavCoreContext();
143
+
144
+ const { simulator, session, isRecording, currentSpeed } = useSimulator({
145
+ routeGeometry,
146
+ speedKmh: 60,
147
+ onPositionUpdate: (pos) => {
148
+ navCore.update({
149
+ coord: pos.coord,
150
+ accuracy: pos.accuracy,
151
+ bearing: pos.bearing,
152
+ speed: pos.speed,
153
+ timestamp: Date.now(),
154
+ });
155
+ },
156
+ });
157
+
158
+ return (
159
+ <View style={{ flex: 1 }}>
160
+ <SimulatorControls
161
+ simulator={simulator}
162
+ session={session}
163
+ currentSpeed={currentSpeed}
164
+ isRecording={isRecording}
165
+ />
166
+ </View>
167
+ );
168
+ }
306
169
  ```
307
170
 
308
- | Style | Description |
309
- |---|---|
310
- | `minimal` | Horizontal bar: speed selector (1×/2×/4×/8×) + pause/resume |
311
- | `full` | Card: speed, pause/resume, scenario log, session recorder |
312
- | `floating` | Collapsible bottom sheet — does not obscure the map |
313
-
314
- Uses Klaroways colour tokens (`#081525`, `#00BF76`, etc.) to match `NavCoreTestScreen`.
315
-
316
171
  ---
317
172
 
318
173
  ## Noise Profiles
319
174
 
320
- | Profile | stdDevM | Use case |
321
- |---|---|---|
322
- | `NONE` | 0 | Exact coordinates — CI tests |
323
- | `CLEAN` | 3 m | Good open-sky GPS |
324
- | `STANDARD` | 12 m | Matches original Go4Exams ±0.00011° |
325
- | `URBAN` | 25 m | Dense city canyon |
326
- | `DEGRADED` | 50 m | Tunnel exit / bad conditions |
327
- | `CUSTOM` | any | Provide `customFn(index) → [lngOff, latOff]` |
328
-
329
- ---
330
-
331
- ## Scenario Types
332
-
333
- | Type | Factory | Effect |
175
+ | Profile | Standard Deviation (Metres) | Description |
334
176
  |---|---|---|
335
- | `GPS_BLACKOUT` | `gpsBlackout(idx, ms)` | Pauses noise application (dead reckoning stress test) |
336
- | `WRONG_SIDE_ROAD` | `wrongSideRoad(idx, offsetM)` | Position offset perpendicular to route |
337
- | `UTURN` | `uTurn(idx)` | Fires at index — engine handles turn logic |
338
- | `SPEED_SPIKE` | `speedSpike(idx, kmh)` | Temporarily reports higher speed |
339
- | `ACCURACY_DROP` | `accuracyDrop(idx, m, ms)` | GPS accuracy degrades to `m` metres for `ms` |
340
- | `CUSTOM` | — | Provide `params` manually |
341
-
342
- ---
343
-
344
- ## GPX Round-Trip Loop
345
-
346
- ```
347
- real GPS trace
348
- ↓
349
- .gpx file
350
- ↓
351
- GpxParser.parse()
352
- ↓
353
- SimulatorEngine (replay with noise + scenarios)
354
- ↓
355
- SessionRecorder.export('gpx')
356
- ↓
357
- GpxParser.parse() ←── CI assertion: reparsed.coordinates.length === session.positions.length
358
- ↓
359
- Next regression run
360
- ```
361
-
362
- This loop is the foundation of the CI strategy. Every format change to `SessionRecorder._toGpx()` that breaks `GpxParser.parse()` will fail the integration test immediately.
363
-
364
- ---
365
-
366
- ## CI / Regression Testing
367
-
368
- ```bash
369
- cd packages/navcore-sdk
370
- npx vitest run --reporter=verbose
371
- ```
372
-
373
- Test files:
374
- - `packages/simulator/src/core/__tests__/RouteWalker.test.ts`
375
- - `packages/simulator/src/core/__tests__/NoiseEngine.test.ts`
376
- - `packages/simulator/src/core/__tests__/SimulatorEngine.test.ts`
377
- - `packages/simulator/src/scenarios/__tests__/ScenarioInjector.test.ts`
378
- - `packages/simulator/src/replay/__tests__/GpxParser.test.ts`
379
- - `packages/simulator/src/__tests__/integration/simulator-pipeline.test.ts`
380
-
381
- All tests use `vi.useFakeTimers()` — no real waiting, no flakiness.
177
+ | `NONE` | 0 m | Zero jitter; exact mathematical coordinates on the polyline |
178
+ | `LOW` | 4 m | High-quality open-sky dual-frequency GNSS |
179
+ | `STANDARD` | 12 m | Typical smartphone GPS accuracy in suburban conditions |
180
+ | `URBAN_CANYON` | 35 m | Severe multipath reflection and tall building interference |
382
181
 
383
182
  ---
384
183
 
385
- ## Migration from useGpsSimulator
386
-
387
- The migration is a **search-and-replace**, not a rewrite.
388
-
389
- ### 1. Remove the inline hook and local type
390
-
391
- Delete `useGpsSimulator` function (lines 114–174 in `NavCoreTestScreen`) and the local `SimPosition` interface.
392
-
393
- ### 2. Add import
184
+ ## CI Regression Testing
394
185
 
395
186
  ```typescript
396
- import {
397
- useSimulator,
398
- SimulatorControls,
399
- NoiseEngine,
400
- ScenarioInjector,
401
- } from '../../../../packages/navcore-sdk/packages/simulator/src';
402
- ```
403
-
404
- ### 3. Replace the hook call
405
-
406
- ```typescript
407
- // Before
408
- const simGps = useGpsSimulator(route, isNavigating, speed);
409
-
410
- // After
411
- const { position: simGps, controls } = useSimulator({
412
- routeGeometry: route,
413
- active: isNavigating,
414
- config: { speedKmh: 50, noiseProfile: NoiseEngine.STANDARD },
187
+ import { describe, it, expect } from 'vitest';
188
+ import { NavCore } from '@ingissa/navcore-core';
189
+ import { SimulatorEngine, NoiseEngine } from '@ingissa/navcore-simulator/node';
190
+
191
+ describe('Navigation Regression', () => {
192
+ it('navigates route without spurious off-route alerts under normal noise', () => {
193
+ const route = [[2.3522, 48.8566], [2.3400, 48.8650], [2.3009, 48.8741]];
194
+ const nav = new NavCore({ isDev: true });
195
+ nav.setRoute(route);
196
+ nav.startNavigation();
197
+
198
+ let offRouteFired = false;
199
+ nav.on('deviation', () => { offRouteFired = true; });
200
+
201
+ const sim = new SimulatorEngine({
202
+ routeGeometry: route,
203
+ noiseProfile: NoiseEngine.LOW,
204
+ speedMultiplier: 8,
205
+ onPositionUpdate: (pos) => {
206
+ nav.update({
207
+ coord: pos.coord,
208
+ accuracy: pos.accuracy,
209
+ bearing: pos.bearing,
210
+ speed: pos.speed,
211
+ timestamp: Date.now(),
212
+ });
213
+ },
214
+ });
215
+
216
+ sim.start();
217
+ // Verify engine state upon completion
218
+ expect(offRouteFired).toBe(false);
219
+ });
415
220
  });
416
221
  ```
417
222
 
418
- ### 4. Replace the speed UI
223
+ ---
419
224
 
420
- ```tsx
421
- // Before: 40 lines of inline speedRow JSX + speed state
422
- // After:
423
- <SimulatorControls simulator={controls} style="minimal" />
424
- ```
225
+ ## Licensing
226
+
227
+ - [Simulator Guide](../../docs/simulator.md)
228
+ - [API Reference](../../docs/api-reference/navcore-simulator.md)
229
+ - [Official Website](https://navcore-web.vercel.app/)
425
230
 
426
- The `position` object shape is **identical** to the original `SimPosition` — `coord`, `bearing`, `speed`, `accuracy` are all present.
231
+ Proprietary software. Copyright (c) 2026 Abdelnasser Aitabi. All rights reserved. No license is granted except by separate written agreement.
@@ -0,0 +1,87 @@
1
+ import { SimSession, SimulatorConfig, Scenario, SimPosition, SimLocationObject } from './node.mjs';
2
+ export { Coordinate, EngineState, ExportFormat, FleetReport, FleetSimulator, GpxParseResult, GpxParser, NoiseEngine, NoiseProfile, RouteWalker, ScenarioInjector, SessionRecorder, SimLocationCoords, SimulatorEngine, VehicleProfile, WalkerPosition } from './node.mjs';
3
+ import { Coordinate } from '@ingissa/navcore-core';
4
+ import * as react_jsx_runtime from 'react/jsx-runtime';
5
+
6
+ /**
7
+ * useSimulator — React Native hook wrapping SimulatorEngine.
8
+ *
9
+ * Drop-in replacement for useGpsSimulator.
10
+ * All engine logic lives in SimulatorEngine — this hook is purely a thin
11
+ * React adapter with no business logic of its own.
12
+ *
13
+ * BEFORE (current Go4Exams code):
14
+ * const simGps = useGpsSimulator(route, isNavigating, speedMultiplier)
15
+ *
16
+ * AFTER:
17
+ * const { position, controls, session, isRecording } = useSimulator({
18
+ * routeGeometry: route,
19
+ * active: isNavigating,
20
+ * config: { speedKmh: 50, noiseProfile: NoiseEngine.STANDARD },
21
+ * })
22
+ */
23
+
24
+ interface UseSimulatorOptions {
25
+ /** Route geometry to simulate along */
26
+ routeGeometry: Coordinate[] | null;
27
+ /** Whether the simulation should be active */
28
+ active: boolean;
29
+ /** Engine configuration (all fields optional) */
30
+ config?: Partial<Omit<SimulatorConfig, 'routeGeometry' | 'onPositionUpdate' | 'onRouteComplete'>>;
31
+ /** Pre-registered scenarios */
32
+ scenarios?: Scenario[];
33
+ /** Called when a scenario fires */
34
+ onScenarioFired?: (scenario: Scenario) => void;
35
+ /** Called when the route simulation finishes */
36
+ onRouteComplete?: () => void;
37
+ }
38
+ interface SimulatorControls$1 {
39
+ pause: () => void;
40
+ resume: () => void;
41
+ seek: (geometryIndex: number) => void;
42
+ setSpeed: (multiplier: 1 | 2 | 4 | 8) => void;
43
+ }
44
+ interface SessionControls {
45
+ start: (sessionId?: string) => void;
46
+ stop: () => SimSession | null;
47
+ }
48
+ interface UseSimulatorResult {
49
+ /** Current simulated position — null when inactive */
50
+ position: SimPosition | null;
51
+ /** Conforms to Expo LocationObject (null when inactive) */
52
+ location: SimLocationObject | null;
53
+ /** Alias for location conforming to Expo LocationObject */
54
+ rawLocation: SimLocationObject | null;
55
+ /** Playback controls */
56
+ controls: SimulatorControls$1;
57
+ /** Recording controls */
58
+ session: SessionControls;
59
+ /** Whether a recording is in progress */
60
+ isRecording: boolean;
61
+ }
62
+ declare function useSimulator(options: UseSimulatorOptions): UseSimulatorResult;
63
+
64
+ interface ScenarioLogEntry {
65
+ ts: number;
66
+ name: string;
67
+ type: string;
68
+ }
69
+ interface SimulatorControlsProps {
70
+ simulator: SimulatorControls$1;
71
+ session?: SessionControls;
72
+ style?: 'minimal' | 'full' | 'floating';
73
+ showScenarioLog?: boolean;
74
+ showSessionRecorder?: boolean;
75
+ scenarioLog?: ScenarioLogEntry[];
76
+ /** Current speed multiplier (controlled externally) */
77
+ currentSpeed?: 1 | 2 | 4 | 8;
78
+ /** Current engine state */
79
+ engineState?: 'idle' | 'running' | 'paused' | 'complete';
80
+ isRecording?: boolean;
81
+ onExportSession?: (session: SimSession) => void;
82
+ /** Additional style for the root container */
83
+ containerStyle?: object;
84
+ }
85
+ declare function SimulatorControls({ simulator, session, style, showScenarioLog, showSessionRecorder, scenarioLog, currentSpeed, engineState, isRecording, onExportSession, containerStyle, }: SimulatorControlsProps): react_jsx_runtime.JSX.Element;
86
+
87
+ export { Scenario, type ScenarioLogEntry, SimLocationObject, SimPosition, SimSession, SimulatorConfig, SimulatorControls, useSimulator };