@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/package.json CHANGED
@@ -1,58 +1,61 @@
1
- {
2
- "name": "@ingissa/navcore-simulator",
3
- "version": "1.0.8",
4
- "description": "Headless GPS simulator for NavCore. Works in Node.js, React Native, and CI.",
5
- "author": "Ingissa <8abnav@gmail.com>",
6
- "license": "SEE LICENSE IN LICENSE",
7
- "homepage": "https://navcore.dev",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/ingissa/navcore-sdk.git"
11
- },
12
- "bugs": {
13
- "url": "https://github.com/ingissa/navcore-sdk/issues"
14
- },
15
- "main": "dist/index.js",
16
- "module": "dist/index.mjs",
17
- "types": "dist/index.d.ts",
18
- "exports": {
19
- ".": {
20
- "import": "./dist/index.mjs",
21
- "require": "./dist/index.js",
22
- "types": "./dist/index.d.ts"
23
- }
24
- },
25
- "files": [
26
- "dist",
27
- "src",
28
- "LICENSE"
29
- ],
30
- "sideEffects": false,
31
- "publishConfig": {
32
- "access": "public"
33
- },
34
- "engines": {
35
- "node": ">=16"
36
- },
37
- "scripts": {
38
- "build": "tsc -p tsconfig.build.json",
39
- "lint": "tsc --noEmit",
40
- "prepublishOnly": "npm run build"
41
- },
42
- "peerDependencies": {
43
- "@ingissa/navcore-core": "^1.0.8",
44
- "react": ">=18",
45
- "react-native": ">=0.72"
46
- },
47
- "peerDependenciesMeta": {
48
- "react": {
49
- "optional": true
50
- },
51
- "react-native": {
52
- "optional": true
53
- }
54
- },
55
- "devDependencies": {
56
- "@types/node": "^25.8.0"
57
- }
58
- }
1
+ {
2
+ "name": "@ingissa/navcore-simulator",
3
+ "version": "1.0.10",
4
+ "description": "Headless GPS simulator for NavCore. Works in Node.js, React Native, and CI.",
5
+ "author": "Ingissa <8abnav@gmail.com>",
6
+ "license": "SEE LICENSE IN LICENSE",
7
+ "homepage": "https://navcore-web.vercel.app/",
8
+ "main": "dist/index.js",
9
+ "module": "dist/index.mjs",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js"
16
+ },
17
+ "./node": {
18
+ "types": "./dist/node.d.ts",
19
+ "import": "./dist/node.mjs",
20
+ "require": "./dist/node.js"
21
+ },
22
+ "./react-native": {
23
+ "types": "./dist/react-native.d.ts",
24
+ "import": "./dist/react-native.mjs",
25
+ "require": "./dist/react-native.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "LICENSE",
31
+ "README.md"
32
+ ],
33
+ "sideEffects": false,
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "engines": {
38
+ "node": ">=16"
39
+ },
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "lint": "tsc --noEmit",
43
+ "prepublishOnly": "npm run build --prefix ../.. && node ../../scripts/check-release.mjs"
44
+ },
45
+ "peerDependencies": {
46
+ "@ingissa/navcore-core": "^1.0.10",
47
+ "react": ">=18",
48
+ "react-native": ">=0.72"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "react": {
52
+ "optional": true
53
+ },
54
+ "react-native": {
55
+ "optional": true
56
+ }
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^25.8.0"
60
+ }
61
+ }
@@ -1,142 +0,0 @@
1
- /**
2
- * NavCore — Main Orchestrator
3
- *
4
- * Fuses all extracted modules into a single engine class.
5
- * Framework-agnostic; license verification does not require platform crypto APIs.
6
- *
7
- * Usage:
8
- * const engine = new NavCore({ licenseKey: '...' });
9
- * engine.setRoute(routeCoords, instructions);
10
- * engine.update(gpsUpdate);
11
- * const state = engine.getState();
12
- */
13
- import type { Coordinate } from './geo/haversine';
14
- import { type NavInstruction } from './progress/InstructionResolver';
15
- import { NavigationFSM } from './off-route/NavigationFSM';
16
- import { OffRouteFSM } from './off-route/OffRouteFSM';
17
- import { LicenseManager } from './license/LicenseManager';
18
- import type { RoutePlan } from './route-plan/RoutePlan';
19
- export interface NavGpsUpdate {
20
- coord: Coordinate;
21
- accuracy: number | null;
22
- bearing: number | null;
23
- speed: number;
24
- timestamp: number;
25
- }
26
- export interface NavCoreOptions {
27
- licenseKey?: string;
28
- isDev?: boolean;
29
- isCircuit?: boolean;
30
- kalmanQ?: number;
31
- kalmanRBase?: number;
32
- baseCorridorMeters?: number;
33
- maxCorridorMeters?: number;
34
- arrivalThresholdMeters?: number;
35
- bearingSmoothingAlpha?: number;
36
- }
37
- export interface NavCoreState {
38
- snappedCoord: Coordinate | null;
39
- ghostCoord: Coordinate | null;
40
- rawGpsCoord: Coordinate | null;
41
- bearing: number;
42
- rawGpsBearing: number | null;
43
- routeIndex: number;
44
- anchorRouteIndex: number | null;
45
- distanceToRoute: number;
46
- corridor: number;
47
- confidence: number;
48
- isOffRoute: boolean;
49
- navFsmState: string;
50
- progressIndex: number;
51
- hasArrived: boolean;
52
- rejoinCandidate: {
53
- coord: Coordinate;
54
- index: number;
55
- } | null;
56
- confidenceCone: {
57
- center: Coordinate;
58
- bearing: number;
59
- angle: number;
60
- length: number;
61
- } | null;
62
- alertState: 'NONE' | 'DEVIATING' | 'RECALCULATING' | 'REJOINING' | 'REJOINED';
63
- detourMode: 'NONE' | 'FREE' | 'COMMITTED';
64
- detourCommitProgress: number;
65
- distanceToDestination: number | null;
66
- distanceToNextInstruction: number | null;
67
- nextInstrIdx: number | null;
68
- nextInstructionIndex: number | null;
69
- currentSpeed: number;
70
- isGpsStale: boolean;
71
- gpsAgeMs: number;
72
- licenseStatus: string;
73
- offRouteState: string;
74
- lifecycleState: string;
75
- stability?: {
76
- oscillation: number;
77
- drift: number;
78
- jump: number;
79
- };
80
- }
81
- type EventHandler = (...args: unknown[]) => void;
82
- export declare class NavCore {
83
- private kalman;
84
- private snapper;
85
- private deadReckoning;
86
- private deviationDetector;
87
- private lifecycleFSM;
88
- private offRouteFSM;
89
- private license;
90
- private route;
91
- private instructions;
92
- private forwardCum;
93
- private reverseCum;
94
- private snappedCoord;
95
- private ghostCoord;
96
- private rawGpsCoord;
97
- private bearing;
98
- private rawGpsBearing;
99
- private routeIndex;
100
- private maxProgressIdx;
101
- private prevSegment;
102
- private distanceToRoute;
103
- private corridor;
104
- private prevDistToNext;
105
- private currentSpeed;
106
- private lastGpsTimestamp;
107
- private instrIdx;
108
- private linearOffset;
109
- private hasArrived;
110
- private readonly options;
111
- private listeners;
112
- constructor(options?: NavCoreOptions);
113
- /**
114
- * Returns true if the feature is unlocked (dev mode OR valid license).
115
- * Emits a one-time console warning when a Pro feature is blocked.
116
- */
117
- private has;
118
- /** Load route geometry and instructions */
119
- setRoute(route: ReadonlyArray<Coordinate>, instructions?: ReadonlyArray<NavInstruction>): void;
120
- /** Load a provider-normalized route plan without exposing mutable route state. */
121
- loadRoute(plan: RoutePlan): void;
122
- /** Start navigation */
123
- startNavigation(): void;
124
- /** Process a GPS update — the main engine tick */
125
- update(gps: NavGpsUpdate): NavCoreState;
126
- /** Time-advance tick for dead reckoning (call at RAF rate) */
127
- tick(nowMs: number): NavCoreState;
128
- /** Get the current engine state snapshot */
129
- getState(): NavCoreState;
130
- /** Destroy the engine */
131
- destroy(): void;
132
- on(event: string, handler: EventHandler): void;
133
- off(event: string, handler: EventHandler): void;
134
- private emit;
135
- getLicenseManager(): LicenseManager;
136
- getRoute(): ReadonlyArray<Coordinate>;
137
- getInstructions(): ReadonlyArray<NavInstruction>;
138
- getOffRouteFSM(): OffRouteFSM;
139
- getLifecycleFSM(): NavigationFSM;
140
- }
141
- export {};
142
- //# sourceMappingURL=NavCore.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"NavCore.d.ts","sourceRoot":"","sources":["../../../../core/src/NavCore.ts"],"names":[],"mappings":"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"}
@@ -1,390 +0,0 @@
1
- /**
2
- * NavCore — Main Orchestrator
3
- *
4
- * Fuses all extracted modules into a single engine class.
5
- * Framework-agnostic; license verification does not require platform crypto APIs.
6
- *
7
- * Usage:
8
- * const engine = new NavCore({ licenseKey: '...' });
9
- * engine.setRoute(routeCoords, instructions);
10
- * engine.update(gpsUpdate);
11
- * const state = engine.getState();
12
- */
13
- import { haversineDistance } from './geo/haversine';
14
- import { buildForwardCumulativeDistances, buildReverseCumulativeDistances } from './geo/cumulative';
15
- import { KalmanFilter2D } from './kalman/KalmanFilter2D';
16
- import { RouteSnapper } from './snapper/RouteSnapper';
17
- import { DeadReckoningEngine } from './dead-reckoning/DeadReckoning';
18
- import { calculateDynamicCorridor } from './corridor/DynamicCorridor';
19
- import { DeviationDetector, findClosestSegment } from './corridor/DeviationDetector';
20
- import { applyBearingSmoothing } from './bearing/BearingEngine';
21
- import { validateProgressUpdate } from './progress/ProgressTracker';
22
- import { resolveNextInstruction } from './progress/InstructionResolver';
23
- import { advancePredictor, applyGpsCorrection } from './predictor/RouteOffsetPredictor';
24
- import { NavigationFSM } from './off-route/NavigationFSM';
25
- import { OffRouteFSM } from './off-route/OffRouteFSM';
26
- import { LicenseManager } from './license/LicenseManager';
27
- import * as C from './constants';
28
- import { FEATURES } from './features';
29
- // ── NavCore class ─────────────────────────────────────────────
30
- export class NavCore {
31
- constructor(options) {
32
- // ── Route data ───────────────────────────────────────────────
33
- this.route = [];
34
- this.instructions = [];
35
- this.forwardCum = new Float64Array(0);
36
- this.reverseCum = new Float64Array(0);
37
- // ── State ────────────────────────────────────────────────────
38
- this.snappedCoord = null;
39
- this.ghostCoord = null;
40
- this.rawGpsCoord = null;
41
- this.bearing = 0;
42
- this.rawGpsBearing = null;
43
- this.routeIndex = 0;
44
- this.maxProgressIdx = 0;
45
- this.prevSegment = null;
46
- this.distanceToRoute = 0;
47
- this.corridor = C.BASE_CORRIDOR_METERS;
48
- this.prevDistToNext = null;
49
- this.currentSpeed = 0;
50
- this.lastGpsTimestamp = 0;
51
- this.instrIdx = 0;
52
- this.linearOffset = 0;
53
- this.hasArrived = false;
54
- // ── Events ───────────────────────────────────────────────────
55
- this.listeners = new Map();
56
- this.options = {
57
- licenseKey: options?.licenseKey ?? '',
58
- isDev: options?.isDev ?? false,
59
- isCircuit: options?.isCircuit ?? false,
60
- kalmanQ: options?.kalmanQ ?? 0.00001,
61
- kalmanRBase: options?.kalmanRBase ?? 0.00005,
62
- baseCorridorMeters: options?.baseCorridorMeters ?? C.BASE_CORRIDOR_METERS,
63
- maxCorridorMeters: options?.maxCorridorMeters ?? C.MAX_CORRIDOR_METERS,
64
- arrivalThresholdMeters: options?.arrivalThresholdMeters ?? C.ARRIVAL_THRESHOLD_METERS,
65
- bearingSmoothingAlpha: options?.bearingSmoothingAlpha ?? C.BEARING_SMOOTHING_ALPHA,
66
- };
67
- this.kalman = new KalmanFilter2D(this.options.kalmanQ, this.options.kalmanRBase);
68
- this.snapper = new RouteSnapper({ useBearingPenalty: true, useInertiaPenalty: true });
69
- this.deadReckoning = new DeadReckoningEngine();
70
- this.deviationDetector = new DeviationDetector();
71
- this.lifecycleFSM = new NavigationFSM();
72
- this.offRouteFSM = new OffRouteFSM();
73
- this.license = new LicenseManager(this.options.licenseKey, this.options.isDev);
74
- }
75
- // ── License gate helper ──────────────────────────────────────
76
- /**
77
- * Returns true if the feature is unlocked (dev mode OR valid license).
78
- * Emits a one-time console warning when a Pro feature is blocked.
79
- */
80
- has(feature) {
81
- const ok = this.license.hasFeature(feature);
82
- if (!ok)
83
- this.license.warnOnce(feature);
84
- return ok;
85
- }
86
- // ── Public API ───────────────────────────────────────────────
87
- /** Load route geometry and instructions */
88
- setRoute(route, instructions) {
89
- // Keep engine state isolated from consumer-owned provider responses.
90
- this.route = route.map((coord) => [coord[0], coord[1]]);
91
- this.instructions = (instructions ?? []).map((instruction) => ({
92
- ...instruction,
93
- location: [instruction.location[0], instruction.location[1]],
94
- }));
95
- this.forwardCum = buildForwardCumulativeDistances(this.route);
96
- this.reverseCum = buildReverseCumulativeDistances(this.route);
97
- this.routeIndex = 0;
98
- this.maxProgressIdx = 0;
99
- this.prevSegment = null;
100
- this.instrIdx = 0;
101
- this.linearOffset = 0;
102
- this.hasArrived = false;
103
- this.snappedCoord = null;
104
- this.prevDistToNext = null;
105
- this.lifecycleFSM.dispatch({ type: 'ROUTE_LOADED' });
106
- // Auto-fill missing cumulative offsets for instructions
107
- for (const instr of this.instructions) {
108
- if (instr.cumulativeOffset === undefined && instr.geometryIndex < this.forwardCum.length) {
109
- instr.cumulativeOffset = this.forwardCum[instr.geometryIndex];
110
- }
111
- }
112
- this.emit('routeLoaded', { routeLength: this.route.length });
113
- }
114
- /** Load a provider-normalized route plan without exposing mutable route state. */
115
- loadRoute(plan) {
116
- this.setRoute(plan.geometry, plan.instructions);
117
- }
118
- /** Start navigation */
119
- startNavigation() {
120
- this.lifecycleFSM.dispatch({ type: 'USER_START_NAVIGATION' });
121
- this.emit('navigationStarted');
122
- }
123
- /** Process a GPS update — the main engine tick */
124
- update(gps) {
125
- this.rawGpsCoord = gps.coord;
126
- this.rawGpsBearing = gps.bearing;
127
- this.currentSpeed = gps.speed;
128
- this.lastGpsTimestamp = gps.timestamp;
129
- if (this.route.length < 2)
130
- return this.getState();
131
- // 1. Kalman filter
132
- const [filteredLng, filteredLat] = this.kalman.update(gps.coord[0], gps.coord[1], gps.accuracy ?? 10, gps.timestamp);
133
- const filteredCoord = [filteredLng, filteredLat];
134
- // 2. Snap to route
135
- const startIdx = Math.max(0, this.routeIndex - 2);
136
- const snap = this.snapper.snap(filteredCoord, this.route, startIdx, C.SNAP_WINDOW_SIZE, this.options.isCircuit, this.maxProgressIdx, this.routeIndex, gps.bearing, this.prevSegment);
137
- this.ghostCoord = snap.proj;
138
- // Look ahead to upcoming maneuver for dynamic corridor widening
139
- let distToUpcomingManeuver = null;
140
- if (this.instructions.length > 0 && this.instrIdx < this.instructions.length) {
141
- const upcoming = this.instructions[this.instrIdx];
142
- distToUpcomingManeuver = upcoming.cumulativeOffset !== undefined
143
- ? Math.max(0, upcoming.cumulativeOffset - this.linearOffset)
144
- : haversineDistance(filteredCoord[1], filteredCoord[0], upcoming.location[1], upcoming.location[0]);
145
- }
146
- // 3. Dynamic corridor
147
- this.corridor = calculateDynamicCorridor(gps.speed, gps.accuracy, this.options.baseCorridorMeters, this.options.maxCorridorMeters, C.SPEED_CORRIDOR_FACTOR, C.GPS_ACCURACY_FACTOR, distToUpcomingManeuver);
148
- // 4. Progress validation
149
- const validatedIdx = validateProgressUpdate(this.routeIndex, snap.idx, this.route, snap.dist, C.MAX_SNAP_DISTANCE);
150
- this.prevSegment = this.routeIndex;
151
- this.routeIndex = validatedIdx;
152
- this.maxProgressIdx = Math.max(this.maxProgressIdx, validatedIdx);
153
- this.snappedCoord = snap.proj;
154
- this.distanceToRoute = snap.dist;
155
- // 5. Linear offset update
156
- if (this.forwardCum.length > validatedIdx) {
157
- const measuredOffset = this.forwardCum[validatedIdx] ?? 0;
158
- this.linearOffset = applyGpsCorrection(this.linearOffset, measuredOffset, C.BETA_CORRECTION);
159
- }
160
- // 6. Bearing smoothing (PRO: ADVANCED_BEARING)
161
- if (this.has(FEATURES.ADVANCED_BEARING)) {
162
- this.bearing = applyBearingSmoothing(this.bearing, this.route, this.routeIndex, gps.speed, gps.bearing, this.options.bearingSmoothingAlpha);
163
- }
164
- else if (gps.bearing !== null) {
165
- this.bearing = gps.bearing;
166
- }
167
- // 7. Deviation detection & Route Rejoin Handling
168
- const isProDeviation = this.has(FEATURES.COMPOUND_DEVIATION);
169
- if (this.offRouteFSM.state === 'ON_ROUTE' || this.offRouteFSM.state === 'DEVIATING') {
170
- if (isProDeviation) {
171
- const closest = findClosestSegment(filteredCoord, this.route, startIdx, C.SNAP_WINDOW_SIZE);
172
- const signals = this.deviationDetector.evaluate(filteredCoord, gps.bearing, gps.speed, closest, this.route, this.corridor, this.prevDistToNext);
173
- // Update moving-away tracker
174
- if (closest.segmentIndex < this.route.length - 1) {
175
- const nextEnd = this.route[Math.min(closest.segmentIndex + 2, this.route.length - 1)];
176
- this.prevDistToNext = haversineDistance(filteredCoord[1], filteredCoord[0], nextEnd[1], nextEnd[0]);
177
- }
178
- if (signals.confirmed) {
179
- if (this.offRouteFSM.state === 'ON_ROUTE') {
180
- this.offRouteFSM.dispatch({
181
- type: 'DEVIATION_SUSPECTED',
182
- deviationSegmentIdx: closest.segmentIndex
183
- });
184
- }
185
- else if (this.offRouteFSM.isDeviationConfirmed()) {
186
- this.offRouteFSM.dispatch({
187
- type: 'DEVIATION_CONFIRMED',
188
- deviationSegmentIdx: closest.segmentIndex
189
- });
190
- this.emit('deviation', {
191
- anchorIndex: closest.segmentIndex,
192
- ghostCoord: snap.proj,
193
- });
194
- }
195
- }
196
- else if (this.offRouteFSM.state === 'DEVIATING') {
197
- this.offRouteFSM.dispatch({ type: 'RESUME' });
198
- }
199
- }
200
- else {
201
- // Core fallback: simple distance-based off-route check
202
- if (snap.dist > this.corridor && this.offRouteFSM.state === 'ON_ROUTE') {
203
- this.offRouteFSM.dispatch({ type: 'DEVIATION_SUSPECTED', deviationSegmentIdx: snap.idx });
204
- this.emit('deviation', { anchorIndex: snap.idx, ghostCoord: snap.proj });
205
- }
206
- else if (snap.dist <= this.corridor && this.offRouteFSM.state === 'DEVIATING') {
207
- this.offRouteFSM.dispatch({ type: 'RESUME' });
208
- }
209
- }
210
- }
211
- else {
212
- // Vehicle is in OFF_ROUTE, REJOINING, or REJOINED state.
213
- // If the vehicle re-enters the valid corridor, resume ON_ROUTE and emit 'rejoined'.
214
- if (snap.dist <= this.corridor) {
215
- const wasOffRoute = this.offRouteFSM.state === 'OFF_ROUTE' ||
216
- this.offRouteFSM.state === 'REJOINING' ||
217
- this.offRouteFSM.state === 'REJOINED';
218
- this.offRouteFSM.dispatch({ type: 'RESUME' });
219
- if (wasOffRoute) {
220
- this.emit('rejoined', { resumeIndex: snap.idx });
221
- }
222
- }
223
- }
224
- // 8. Lifecycle join detection
225
- if (this.lifecycleFSM.state === 'APPROACHING_ROUTE' && snap.dist < this.corridor) {
226
- this.lifecycleFSM.dispatch({ type: 'ROUTE_JOINED', data: { index: snap.idx } });
227
- }
228
- // 9. Instruction resolution
229
- if (this.instructions.length > 0) {
230
- const instrResult = resolveNextInstruction(this.routeIndex, this.instructions, this.instrIdx, this.route, filteredCoord, gps.bearing, this.linearOffset, gps.speed, this.options.isCircuit);
231
- if (instrResult.nextIdx !== this.instrIdx) {
232
- this.instrIdx = instrResult.nextIdx;
233
- }
234
- if (instrResult.wasTriggered && instrResult.instruction) {
235
- this.emit('instruction', instrResult.instruction);
236
- this.instrIdx = instrResult.nextIdx + 1;
237
- }
238
- }
239
- // 10. Arrival detection
240
- if (!this.hasArrived && this.route.length > 0) {
241
- const dest = this.route[this.route.length - 1];
242
- const distToDest = haversineDistance(filteredCoord[1], filteredCoord[0], dest[1], dest[0]);
243
- // Prevent immediate arrival on circuits by ensuring we've traversed at least 80% of the waypoints
244
- const hasTraveledFarEnough = this.options.isCircuit ?
245
- (this.routeIndex >= this.route.length * 0.8) : true;
246
- if (distToDest < this.options.arrivalThresholdMeters && hasTraveledFarEnough) {
247
- this.hasArrived = true;
248
- this.lifecycleFSM.dispatch({ type: 'NAVIGATION_FINISHED' });
249
- this.emit('arrival');
250
- }
251
- }
252
- // 11. Dead reckoning anchor (PRO: DEAD_RECKONING)
253
- if (this.has(FEATURES.DEAD_RECKONING)) {
254
- this.deadReckoning.setAnchor(snap.proj, snap.idx, gps.timestamp);
255
- }
256
- const state = this.getState();
257
- this.emit('update', state);
258
- return state;
259
- }
260
- /** Time-advance tick for dead reckoning (call at RAF rate) */
261
- tick(nowMs) {
262
- if (this.has(FEATURES.DEAD_RECKONING)) {
263
- const dr = this.deadReckoning.predict(this.currentSpeed, this.bearing, nowMs);
264
- if (dr.isActive) {
265
- this.snappedCoord = dr.predictedCoord;
266
- }
267
- }
268
- // Advance linear offset predictor
269
- if (this.lastGpsTimestamp > 0) {
270
- const dt = (nowMs - this.lastGpsTimestamp) / 1000;
271
- if (dt > 0 && dt < C.GPS_AGE_CAP_SECONDS) {
272
- this.linearOffset = advancePredictor(this.linearOffset, this.currentSpeed, dt);
273
- }
274
- }
275
- const state = this.getState();
276
- this.emit('update', state);
277
- return state;
278
- }
279
- /** Get the current engine state snapshot */
280
- getState() {
281
- const distToDest = this.route.length > 0 && this.reverseCum.length > this.routeIndex
282
- ? (this.reverseCum[this.routeIndex] ?? null)
283
- : null;
284
- const distToNextInstr = this.instrIdx < this.instructions.length
285
- ? (() => {
286
- const instr = this.instructions[this.instrIdx];
287
- return instr.cumulativeOffset !== undefined
288
- ? Math.max(0, instr.cumulativeOffset - this.linearOffset)
289
- : null;
290
- })()
291
- : null;
292
- const gpsAgeMs = this.lastGpsTimestamp > 0 ? Date.now() - this.lastGpsTimestamp : 0;
293
- const confidence = Math.max(0, Math.min(1, 1 - (this.distanceToRoute / Math.max(1, this.corridor))));
294
- const alertState = this.offRouteFSM.state === 'DEVIATING' ? 'DEVIATING' :
295
- this.offRouteFSM.state === 'OFF_ROUTE' || this.offRouteFSM.state === 'REROUTING' ? 'RECALCULATING' :
296
- this.offRouteFSM.state === 'REJOINING' ? 'REJOINING' :
297
- this.offRouteFSM.state === 'REJOINED' ? 'REJOINED' : 'NONE';
298
- const isOff = this.offRouteFSM.state !== 'ON_ROUTE' && this.offRouteFSM.state !== 'DEVIATING';
299
- const detourMode = this.offRouteFSM.state === 'OFF_ROUTE' || this.offRouteFSM.state === 'REROUTING' ? 'FREE' : 'NONE';
300
- const detourCommitProgress = this.offRouteFSM.state === 'OFF_ROUTE' ? 1.0 : 0.0;
301
- const rejoinCandidate = this.offRouteFSM.context.rejoinTarget
302
- ? {
303
- coord: this.offRouteFSM.context.rejoinTarget.coordinates,
304
- index: this.offRouteFSM.context.rejoinTarget.index,
305
- }
306
- : null;
307
- const confidenceCone = this.snappedCoord
308
- ? {
309
- center: this.snappedCoord,
310
- bearing: this.bearing,
311
- angle: 30,
312
- length: Math.max(10, Math.min(50, this.currentSpeed * 3)),
313
- }
314
- : null;
315
- const nextIdx = this.instrIdx < this.instructions.length ? this.instrIdx : null;
316
- return {
317
- snappedCoord: this.snappedCoord,
318
- ghostCoord: this.ghostCoord ?? this.snappedCoord,
319
- rawGpsCoord: this.rawGpsCoord,
320
- bearing: this.bearing,
321
- rawGpsBearing: this.rawGpsBearing,
322
- routeIndex: this.routeIndex,
323
- anchorRouteIndex: this.prevSegment ?? this.routeIndex,
324
- distanceToRoute: this.distanceToRoute,
325
- corridor: this.corridor,
326
- confidence,
327
- isOffRoute: isOff,
328
- navFsmState: this.lifecycleFSM.state,
329
- progressIndex: this.routeIndex,
330
- hasArrived: this.hasArrived,
331
- rejoinCandidate,
332
- confidenceCone,
333
- alertState,
334
- detourMode,
335
- detourCommitProgress,
336
- distanceToDestination: distToDest,
337
- distanceToNextInstruction: distToNextInstr,
338
- nextInstrIdx: nextIdx,
339
- nextInstructionIndex: nextIdx,
340
- currentSpeed: this.currentSpeed,
341
- isGpsStale: gpsAgeMs > C.GPS_STALE_MS,
342
- gpsAgeMs,
343
- licenseStatus: this.license.getStatus(),
344
- offRouteState: this.offRouteFSM.state,
345
- lifecycleState: this.lifecycleFSM.state,
346
- };
347
- }
348
- /** Destroy the engine */
349
- destroy() {
350
- this.listeners.clear();
351
- this.route = [];
352
- this.instructions = [];
353
- this.snappedCoord = null;
354
- this.ghostCoord = null;
355
- this.rawGpsCoord = null;
356
- this.kalman.reset();
357
- this.deadReckoning.clearAnchor();
358
- this.lifecycleFSM.reset();
359
- this.offRouteFSM.reset();
360
- }
361
- // ── Event system ─────────────────────────────────────────────
362
- on(event, handler) {
363
- if (!this.listeners.has(event)) {
364
- this.listeners.set(event, []);
365
- }
366
- this.listeners.get(event).push(handler);
367
- }
368
- off(event, handler) {
369
- const handlers = this.listeners.get(event);
370
- if (handlers) {
371
- const idx = handlers.indexOf(handler);
372
- if (idx >= 0)
373
- handlers.splice(idx, 1);
374
- }
375
- }
376
- emit(event, ...args) {
377
- const handlers = this.listeners.get(event);
378
- if (handlers) {
379
- for (const handler of handlers) {
380
- handler(...args);
381
- }
382
- }
383
- }
384
- // ── Getters ──────────────────────────────────────────────────
385
- getLicenseManager() { return this.license; }
386
- getRoute() { return this.route; }
387
- getInstructions() { return this.instructions; }
388
- getOffRouteFSM() { return this.offRouteFSM; }
389
- getLifecycleFSM() { return this.lifecycleFSM; }
390
- }