@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.
- package/LICENSE +3 -3
- package/README.md +161 -356
- package/dist/index.d.mts +87 -0
- package/dist/{simulator/src/react-native/useSimulator.d.ts → index.d.ts} +37 -9
- package/dist/index.js +1393 -0
- package/dist/index.mjs +1383 -0
- package/dist/node.d.mts +527 -0
- package/dist/node.d.ts +527 -0
- package/dist/node.js +957 -0
- package/dist/node.mjs +949 -0
- package/dist/react-native.d.mts +4 -0
- package/dist/react-native.d.ts +4 -0
- package/dist/react-native.js +1393 -0
- package/dist/react-native.mjs +1383 -0
- package/package.json +61 -58
- package/dist/core/src/NavCore.d.ts +0 -142
- package/dist/core/src/NavCore.d.ts.map +0 -1
- package/dist/core/src/NavCore.js +0 -390
- package/dist/core/src/bearing/BearingEngine.d.ts +0 -24
- package/dist/core/src/bearing/BearingEngine.d.ts.map +0 -1
- package/dist/core/src/bearing/BearingEngine.js +0 -50
- package/dist/core/src/constants.d.ts +0 -34
- package/dist/core/src/constants.d.ts.map +0 -1
- package/dist/core/src/constants.js +0 -41
- package/dist/core/src/corridor/DeviationDetector.d.ts +0 -33
- package/dist/core/src/corridor/DeviationDetector.d.ts.map +0 -1
- package/dist/core/src/corridor/DeviationDetector.js +0 -83
- package/dist/core/src/corridor/DynamicCorridor.d.ts +0 -13
- package/dist/core/src/corridor/DynamicCorridor.d.ts.map +0 -1
- package/dist/core/src/corridor/DynamicCorridor.js +0 -26
- package/dist/core/src/dead-reckoning/DeadReckoning.d.ts +0 -35
- package/dist/core/src/dead-reckoning/DeadReckoning.d.ts.map +0 -1
- package/dist/core/src/dead-reckoning/DeadReckoning.js +0 -49
- package/dist/core/src/directions/DirectionsProvider.d.ts +0 -70
- package/dist/core/src/directions/DirectionsProvider.d.ts.map +0 -1
- package/dist/core/src/directions/DirectionsProvider.js +0 -7
- package/dist/core/src/directions/OSRMDirectionsProvider.d.ts +0 -26
- package/dist/core/src/directions/OSRMDirectionsProvider.d.ts.map +0 -1
- package/dist/core/src/directions/OSRMDirectionsProvider.js +0 -90
- package/dist/core/src/directions/OpenRouteServiceProvider.d.ts +0 -30
- package/dist/core/src/directions/OpenRouteServiceProvider.d.ts.map +0 -1
- package/dist/core/src/directions/OpenRouteServiceProvider.js +0 -111
- package/dist/core/src/directions/ValhallaDirectionsProvider.d.ts +0 -33
- package/dist/core/src/directions/ValhallaDirectionsProvider.d.ts.map +0 -1
- package/dist/core/src/directions/ValhallaDirectionsProvider.js +0 -155
- package/dist/core/src/eta/ETAEngine.d.ts +0 -61
- package/dist/core/src/eta/ETAEngine.d.ts.map +0 -1
- package/dist/core/src/eta/ETAEngine.js +0 -74
- package/dist/core/src/features.d.ts +0 -76
- package/dist/core/src/features.d.ts.map +0 -1
- package/dist/core/src/features.js +0 -76
- package/dist/core/src/geo/bearing.d.ts +0 -11
- package/dist/core/src/geo/bearing.d.ts.map +0 -1
- package/dist/core/src/geo/bearing.js +0 -25
- package/dist/core/src/geo/cumulative.d.ts +0 -9
- package/dist/core/src/geo/cumulative.d.ts.map +0 -1
- package/dist/core/src/geo/cumulative.js +0 -20
- package/dist/core/src/geo/haversine.d.ts +0 -13
- package/dist/core/src/geo/haversine.d.ts.map +0 -1
- package/dist/core/src/geo/haversine.js +0 -39
- package/dist/core/src/geo/projection.d.ts +0 -7
- package/dist/core/src/geo/projection.d.ts.map +0 -1
- package/dist/core/src/geo/projection.js +0 -17
- package/dist/core/src/geofencing/GeofencingEngine.d.ts +0 -93
- package/dist/core/src/geofencing/GeofencingEngine.d.ts.map +0 -1
- package/dist/core/src/geofencing/GeofencingEngine.js +0 -154
- package/dist/core/src/index.d.ts +0 -66
- package/dist/core/src/index.d.ts.map +0 -1
- package/dist/core/src/index.js +0 -62
- package/dist/core/src/kalman/KalmanFilter2D.d.ts +0 -46
- package/dist/core/src/kalman/KalmanFilter2D.d.ts.map +0 -1
- package/dist/core/src/kalman/KalmanFilter2D.js +0 -77
- package/dist/core/src/license/LicenseManager.d.ts +0 -40
- package/dist/core/src/license/LicenseManager.d.ts.map +0 -1
- package/dist/core/src/license/LicenseManager.js +0 -119
- package/dist/core/src/license/verifyP256Signature.d.ts +0 -2
- package/dist/core/src/license/verifyP256Signature.d.ts.map +0 -1
- package/dist/core/src/license/verifyP256Signature.js +0 -26
- package/dist/core/src/off-route/NavigationFSM.d.ts +0 -42
- package/dist/core/src/off-route/NavigationFSM.d.ts.map +0 -1
- package/dist/core/src/off-route/NavigationFSM.js +0 -56
- package/dist/core/src/off-route/OffRouteFSM.d.ts +0 -70
- package/dist/core/src/off-route/OffRouteFSM.d.ts.map +0 -1
- package/dist/core/src/off-route/OffRouteFSM.js +0 -151
- package/dist/core/src/predictor/RouteOffsetPredictor.d.ts +0 -24
- package/dist/core/src/predictor/RouteOffsetPredictor.d.ts.map +0 -1
- package/dist/core/src/predictor/RouteOffsetPredictor.js +0 -55
- package/dist/core/src/progress/InstructionEditor.d.ts +0 -77
- package/dist/core/src/progress/InstructionEditor.d.ts.map +0 -1
- package/dist/core/src/progress/InstructionEditor.js +0 -151
- package/dist/core/src/progress/InstructionResolver.d.ts +0 -25
- package/dist/core/src/progress/InstructionResolver.d.ts.map +0 -1
- package/dist/core/src/progress/InstructionResolver.js +0 -78
- package/dist/core/src/progress/ProgressTracker.d.ts +0 -12
- package/dist/core/src/progress/ProgressTracker.d.ts.map +0 -1
- package/dist/core/src/progress/ProgressTracker.js +0 -22
- package/dist/core/src/renderer/MapRendererAdapter.d.ts +0 -72
- package/dist/core/src/renderer/MapRendererAdapter.d.ts.map +0 -1
- package/dist/core/src/renderer/MapRendererAdapter.js +0 -19
- package/dist/core/src/route-builder/CustomRouteBuilder.d.ts +0 -83
- package/dist/core/src/route-builder/CustomRouteBuilder.d.ts.map +0 -1
- package/dist/core/src/route-builder/CustomRouteBuilder.js +0 -160
- package/dist/core/src/route-plan/RoutePlan.d.ts +0 -38
- package/dist/core/src/route-plan/RoutePlan.d.ts.map +0 -1
- package/dist/core/src/route-plan/RoutePlan.js +0 -102
- package/dist/core/src/session/NavigationSession.d.ts +0 -81
- package/dist/core/src/session/NavigationSession.d.ts.map +0 -1
- package/dist/core/src/session/NavigationSession.js +0 -115
- package/dist/core/src/snapper/ParallelRoadResolver.d.ts +0 -86
- package/dist/core/src/snapper/ParallelRoadResolver.d.ts.map +0 -1
- package/dist/core/src/snapper/ParallelRoadResolver.js +0 -83
- package/dist/core/src/snapper/RouteSnapper.d.ts +0 -38
- package/dist/core/src/snapper/RouteSnapper.d.ts.map +0 -1
- package/dist/core/src/snapper/RouteSnapper.js +0 -80
- package/dist/core/src/voice/VoiceTriggerEngine.d.ts +0 -80
- package/dist/core/src/voice/VoiceTriggerEngine.d.ts.map +0 -1
- package/dist/core/src/voice/VoiceTriggerEngine.js +0 -108
- package/dist/simulator/src/core/NoiseEngine.d.ts +0 -46
- package/dist/simulator/src/core/NoiseEngine.d.ts.map +0 -1
- package/dist/simulator/src/core/NoiseEngine.js +0 -106
- package/dist/simulator/src/core/RouteWalker.d.ts +0 -78
- package/dist/simulator/src/core/RouteWalker.d.ts.map +0 -1
- package/dist/simulator/src/core/RouteWalker.js +0 -180
- package/dist/simulator/src/core/SimulatorEngine.d.ts +0 -53
- package/dist/simulator/src/core/SimulatorEngine.d.ts.map +0 -1
- package/dist/simulator/src/core/SimulatorEngine.js +0 -298
- package/dist/simulator/src/core/types.d.ts +0 -157
- package/dist/simulator/src/core/types.d.ts.map +0 -1
- package/dist/simulator/src/core/types.js +0 -7
- package/dist/simulator/src/fleet/FleetSimulator.d.ts +0 -60
- package/dist/simulator/src/fleet/FleetSimulator.d.ts.map +0 -1
- package/dist/simulator/src/fleet/FleetSimulator.js +0 -114
- package/dist/simulator/src/index.d.ts +0 -28
- package/dist/simulator/src/index.d.ts.map +0 -1
- package/dist/simulator/src/index.js +0 -29
- package/dist/simulator/src/node.d.ts +0 -26
- package/dist/simulator/src/node.d.ts.map +0 -1
- package/dist/simulator/src/node.js +0 -25
- package/dist/simulator/src/react-native/SimulatorControls.d.ts +0 -35
- package/dist/simulator/src/react-native/SimulatorControls.d.ts.map +0 -1
- package/dist/simulator/src/react-native/SimulatorControls.js +0 -254
- package/dist/simulator/src/react-native/useSimulator.d.ts.map +0 -1
- package/dist/simulator/src/react-native/useSimulator.js +0 -100
- package/dist/simulator/src/replay/GpxParser.d.ts +0 -40
- package/dist/simulator/src/replay/GpxParser.d.ts.map +0 -1
- package/dist/simulator/src/replay/GpxParser.js +0 -95
- package/dist/simulator/src/replay/SessionRecorder.d.ts +0 -37
- package/dist/simulator/src/replay/SessionRecorder.d.ts.map +0 -1
- package/dist/simulator/src/replay/SessionRecorder.js +0 -120
- package/dist/simulator/src/scenarios/ScenarioInjector.d.ts +0 -55
- package/dist/simulator/src/scenarios/ScenarioInjector.d.ts.map +0 -1
- package/dist/simulator/src/scenarios/ScenarioInjector.js +0 -150
- package/src/__tests__/integration/simulator-pipeline.test.ts +0 -233
- package/src/core/NoiseEngine.ts +0 -137
- package/src/core/RouteWalker.ts +0 -227
- package/src/core/SimulatorEngine.ts +0 -364
- package/src/core/__tests__/NoiseEngine.test.ts +0 -131
- package/src/core/__tests__/RouteWalker.test.ts +0 -139
- package/src/core/__tests__/SimulatorEngine.test.ts +0 -317
- package/src/core/types.ts +0 -191
- package/src/fleet/FleetSimulator.ts +0 -162
- package/src/index.ts +0 -54
- package/src/node.ts +0 -48
- package/src/react-native/SimulatorControls.tsx +0 -473
- package/src/react-native/useSimulator.ts +0 -155
- package/src/replay/GpxParser.ts +0 -122
- package/src/replay/SessionRecorder.ts +0 -138
- package/src/replay/__tests__/GpxParser.test.ts +0 -136
- package/src/scenarios/ScenarioInjector.ts +0 -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
|
|
6
|
+
> Works in **Node.js** (CI/CD), **React Native**, and browser runtimes.
|
|
7
|
+
|
|
8
|
+
[](https://www.npmjs.com/package/@ingissa/navcore-simulator)
|
|
9
|
+

|
|
10
|
+
[](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
|
|
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
|
-
- [
|
|
25
|
-
- [
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
73
|
+
routeGeometry,
|
|
50
74
|
speedKmh: 50,
|
|
51
|
-
speedMultiplier:
|
|
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
|
|
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
|
-
|
|
61
|
-
engine.
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
110
|
+
### 3. `NoiseEngine`
|
|
111
|
+
Applies Gaussian noise perturbations to latitude, longitude, and bearing based on configurable standard deviations.
|
|
103
112
|
|
|
104
|
-
|
|
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
|
-
###
|
|
123
|
+
### 6. `SessionRecorder`
|
|
124
|
+
Records simulation output into JSON sessions for regression testing and replay.
|
|
109
125
|
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
isRecording={
|
|
304
|
-
|
|
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 |
|
|
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
|
-
| `
|
|
336
|
-
| `
|
|
337
|
-
| `
|
|
338
|
-
| `
|
|
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
|
-
##
|
|
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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
223
|
+
---
|
|
419
224
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
231
|
+
Proprietary software. Copyright (c) 2026 Abdelnasser Aitabi. All rights reserved. No license is granted except by separate written agreement.
|
package/dist/index.d.mts
ADDED
|
@@ -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 };
|