@ingissa/navcore-simulator 1.0.4 → 1.0.8

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 (173) hide show
  1. package/LICENSE +3 -0
  2. package/README.md +10 -18
  3. package/dist/core/src/NavCore.d.ts +142 -0
  4. package/dist/core/src/NavCore.d.ts.map +1 -0
  5. package/dist/core/src/NavCore.js +390 -0
  6. package/dist/core/src/bearing/BearingEngine.d.ts +24 -0
  7. package/dist/core/src/bearing/BearingEngine.d.ts.map +1 -0
  8. package/dist/core/src/bearing/BearingEngine.js +50 -0
  9. package/dist/core/src/constants.d.ts +34 -0
  10. package/dist/core/src/constants.d.ts.map +1 -0
  11. package/dist/core/src/constants.js +41 -0
  12. package/dist/core/src/corridor/DeviationDetector.d.ts +33 -0
  13. package/dist/core/src/corridor/DeviationDetector.d.ts.map +1 -0
  14. package/dist/core/src/corridor/DeviationDetector.js +83 -0
  15. package/dist/core/src/corridor/DynamicCorridor.d.ts +13 -0
  16. package/dist/core/src/corridor/DynamicCorridor.d.ts.map +1 -0
  17. package/dist/core/src/corridor/DynamicCorridor.js +26 -0
  18. package/dist/core/src/dead-reckoning/DeadReckoning.d.ts +35 -0
  19. package/dist/core/src/dead-reckoning/DeadReckoning.d.ts.map +1 -0
  20. package/dist/core/src/dead-reckoning/DeadReckoning.js +49 -0
  21. package/dist/core/src/directions/DirectionsProvider.d.ts +70 -0
  22. package/dist/core/src/directions/DirectionsProvider.d.ts.map +1 -0
  23. package/dist/core/src/directions/DirectionsProvider.js +7 -0
  24. package/dist/core/src/directions/OSRMDirectionsProvider.d.ts +26 -0
  25. package/dist/core/src/directions/OSRMDirectionsProvider.d.ts.map +1 -0
  26. package/dist/core/src/directions/OSRMDirectionsProvider.js +90 -0
  27. package/dist/core/src/directions/OpenRouteServiceProvider.d.ts +30 -0
  28. package/dist/core/src/directions/OpenRouteServiceProvider.d.ts.map +1 -0
  29. package/dist/core/src/directions/OpenRouteServiceProvider.js +111 -0
  30. package/dist/core/src/directions/ValhallaDirectionsProvider.d.ts +33 -0
  31. package/dist/core/src/directions/ValhallaDirectionsProvider.d.ts.map +1 -0
  32. package/dist/core/src/directions/ValhallaDirectionsProvider.js +155 -0
  33. package/dist/core/src/eta/ETAEngine.d.ts +61 -0
  34. package/dist/core/src/eta/ETAEngine.d.ts.map +1 -0
  35. package/dist/core/src/eta/ETAEngine.js +74 -0
  36. package/dist/core/src/features.d.ts +76 -0
  37. package/dist/core/src/features.d.ts.map +1 -0
  38. package/dist/core/src/features.js +76 -0
  39. package/dist/core/src/geo/bearing.d.ts +11 -0
  40. package/dist/core/src/geo/bearing.d.ts.map +1 -0
  41. package/dist/core/src/geo/bearing.js +25 -0
  42. package/dist/core/src/geo/cumulative.d.ts +9 -0
  43. package/dist/core/src/geo/cumulative.d.ts.map +1 -0
  44. package/dist/core/src/geo/cumulative.js +20 -0
  45. package/dist/core/src/geo/haversine.d.ts +13 -0
  46. package/dist/core/src/geo/haversine.d.ts.map +1 -0
  47. package/dist/core/src/geo/haversine.js +39 -0
  48. package/dist/core/src/geo/projection.d.ts +7 -0
  49. package/dist/core/src/geo/projection.d.ts.map +1 -0
  50. package/dist/core/src/geo/projection.js +17 -0
  51. package/dist/core/src/geofencing/GeofencingEngine.d.ts +93 -0
  52. package/dist/core/src/geofencing/GeofencingEngine.d.ts.map +1 -0
  53. package/dist/core/src/geofencing/GeofencingEngine.js +154 -0
  54. package/dist/core/src/index.d.ts +66 -0
  55. package/dist/core/src/index.d.ts.map +1 -0
  56. package/dist/core/src/index.js +62 -0
  57. package/dist/core/src/kalman/KalmanFilter2D.d.ts +46 -0
  58. package/dist/core/src/kalman/KalmanFilter2D.d.ts.map +1 -0
  59. package/dist/core/src/kalman/KalmanFilter2D.js +77 -0
  60. package/dist/core/src/license/LicenseManager.d.ts +40 -0
  61. package/dist/core/src/license/LicenseManager.d.ts.map +1 -0
  62. package/dist/core/src/license/LicenseManager.js +119 -0
  63. package/dist/core/src/license/verifyP256Signature.d.ts +2 -0
  64. package/dist/core/src/license/verifyP256Signature.d.ts.map +1 -0
  65. package/dist/core/src/license/verifyP256Signature.js +26 -0
  66. package/dist/core/src/off-route/NavigationFSM.d.ts +42 -0
  67. package/dist/core/src/off-route/NavigationFSM.d.ts.map +1 -0
  68. package/dist/core/src/off-route/NavigationFSM.js +56 -0
  69. package/dist/core/src/off-route/OffRouteFSM.d.ts +70 -0
  70. package/dist/core/src/off-route/OffRouteFSM.d.ts.map +1 -0
  71. package/dist/core/src/off-route/OffRouteFSM.js +151 -0
  72. package/dist/core/src/predictor/RouteOffsetPredictor.d.ts +24 -0
  73. package/dist/core/src/predictor/RouteOffsetPredictor.d.ts.map +1 -0
  74. package/dist/core/src/predictor/RouteOffsetPredictor.js +55 -0
  75. package/dist/core/src/progress/InstructionEditor.d.ts +77 -0
  76. package/dist/core/src/progress/InstructionEditor.d.ts.map +1 -0
  77. package/dist/core/src/progress/InstructionEditor.js +151 -0
  78. package/dist/core/src/progress/InstructionResolver.d.ts +25 -0
  79. package/dist/core/src/progress/InstructionResolver.d.ts.map +1 -0
  80. package/dist/core/src/progress/InstructionResolver.js +78 -0
  81. package/dist/core/src/progress/ProgressTracker.d.ts +12 -0
  82. package/dist/core/src/progress/ProgressTracker.d.ts.map +1 -0
  83. package/dist/core/src/progress/ProgressTracker.js +22 -0
  84. package/dist/core/src/renderer/MapRendererAdapter.d.ts +72 -0
  85. package/dist/core/src/renderer/MapRendererAdapter.d.ts.map +1 -0
  86. package/dist/core/src/renderer/MapRendererAdapter.js +19 -0
  87. package/dist/core/src/route-builder/CustomRouteBuilder.d.ts +83 -0
  88. package/dist/core/src/route-builder/CustomRouteBuilder.d.ts.map +1 -0
  89. package/dist/core/src/route-builder/CustomRouteBuilder.js +160 -0
  90. package/dist/core/src/route-plan/RoutePlan.d.ts +38 -0
  91. package/dist/core/src/route-plan/RoutePlan.d.ts.map +1 -0
  92. package/dist/core/src/route-plan/RoutePlan.js +102 -0
  93. package/dist/core/src/session/NavigationSession.d.ts +81 -0
  94. package/dist/core/src/session/NavigationSession.d.ts.map +1 -0
  95. package/dist/core/src/session/NavigationSession.js +115 -0
  96. package/dist/core/src/snapper/ParallelRoadResolver.d.ts +86 -0
  97. package/dist/core/src/snapper/ParallelRoadResolver.d.ts.map +1 -0
  98. package/dist/core/src/snapper/ParallelRoadResolver.js +83 -0
  99. package/dist/core/src/snapper/RouteSnapper.d.ts +38 -0
  100. package/dist/core/src/snapper/RouteSnapper.d.ts.map +1 -0
  101. package/dist/core/src/snapper/RouteSnapper.js +80 -0
  102. package/dist/core/src/voice/VoiceTriggerEngine.d.ts +80 -0
  103. package/dist/core/src/voice/VoiceTriggerEngine.d.ts.map +1 -0
  104. package/dist/core/src/voice/VoiceTriggerEngine.js +108 -0
  105. package/dist/simulator/src/core/NoiseEngine.d.ts +46 -0
  106. package/dist/simulator/src/core/NoiseEngine.d.ts.map +1 -0
  107. package/dist/simulator/src/core/NoiseEngine.js +106 -0
  108. package/dist/simulator/src/core/RouteWalker.d.ts +78 -0
  109. package/dist/simulator/src/core/RouteWalker.d.ts.map +1 -0
  110. package/dist/simulator/src/core/RouteWalker.js +180 -0
  111. package/dist/simulator/src/core/SimulatorEngine.d.ts +53 -0
  112. package/dist/simulator/src/core/SimulatorEngine.d.ts.map +1 -0
  113. package/dist/simulator/src/core/SimulatorEngine.js +298 -0
  114. package/dist/simulator/src/core/types.d.ts +157 -0
  115. package/dist/simulator/src/core/types.d.ts.map +1 -0
  116. package/dist/simulator/src/core/types.js +7 -0
  117. package/dist/simulator/src/fleet/FleetSimulator.d.ts +60 -0
  118. package/dist/simulator/src/fleet/FleetSimulator.d.ts.map +1 -0
  119. package/dist/simulator/src/fleet/FleetSimulator.js +114 -0
  120. package/dist/simulator/src/index.d.ts +28 -0
  121. package/dist/simulator/src/index.d.ts.map +1 -0
  122. package/dist/simulator/src/index.js +29 -0
  123. package/dist/simulator/src/node.d.ts +26 -0
  124. package/dist/simulator/src/node.d.ts.map +1 -0
  125. package/dist/simulator/src/node.js +25 -0
  126. package/dist/simulator/src/react-native/SimulatorControls.d.ts +35 -0
  127. package/dist/simulator/src/react-native/SimulatorControls.d.ts.map +1 -0
  128. package/dist/simulator/src/react-native/SimulatorControls.js +254 -0
  129. package/dist/simulator/src/react-native/useSimulator.d.ts +59 -0
  130. package/dist/simulator/src/react-native/useSimulator.d.ts.map +1 -0
  131. package/dist/simulator/src/react-native/useSimulator.js +100 -0
  132. package/dist/simulator/src/replay/GpxParser.d.ts +40 -0
  133. package/dist/simulator/src/replay/GpxParser.d.ts.map +1 -0
  134. package/dist/simulator/src/replay/GpxParser.js +95 -0
  135. package/dist/simulator/src/replay/SessionRecorder.d.ts +37 -0
  136. package/dist/simulator/src/replay/SessionRecorder.d.ts.map +1 -0
  137. package/dist/simulator/src/replay/SessionRecorder.js +120 -0
  138. package/dist/simulator/src/scenarios/ScenarioInjector.d.ts +55 -0
  139. package/dist/simulator/src/scenarios/ScenarioInjector.d.ts.map +1 -0
  140. package/dist/simulator/src/scenarios/ScenarioInjector.js +150 -0
  141. package/package.json +23 -23
  142. package/src/__tests__/integration/simulator-pipeline.test.ts +233 -0
  143. package/src/core/NoiseEngine.ts +137 -0
  144. package/src/core/RouteWalker.ts +227 -0
  145. package/src/core/SimulatorEngine.ts +364 -0
  146. package/src/core/__tests__/NoiseEngine.test.ts +131 -0
  147. package/src/core/__tests__/RouteWalker.test.ts +139 -0
  148. package/src/core/__tests__/SimulatorEngine.test.ts +317 -0
  149. package/src/core/types.ts +191 -0
  150. package/src/fleet/FleetSimulator.ts +162 -0
  151. package/src/index.ts +54 -0
  152. package/src/node.ts +48 -0
  153. package/src/react-native/SimulatorControls.tsx +473 -0
  154. package/src/react-native/useSimulator.ts +155 -0
  155. package/src/replay/GpxParser.ts +122 -0
  156. package/src/replay/SessionRecorder.ts +138 -0
  157. package/src/replay/__tests__/GpxParser.test.ts +136 -0
  158. package/src/scenarios/ScenarioInjector.ts +170 -0
  159. package/src/scenarios/__tests__/ScenarioInjector.test.ts +171 -0
  160. package/dist/index-i9TY71RY.d.mts +0 -494
  161. package/dist/index-i9TY71RY.d.ts +0 -494
  162. package/dist/index.d.mts +0 -1
  163. package/dist/index.d.ts +0 -1
  164. package/dist/index.js +0 -937
  165. package/dist/index.mjs +0 -929
  166. package/dist/node.d.mts +0 -1
  167. package/dist/node.d.ts +0 -1
  168. package/dist/node.js +0 -937
  169. package/dist/node.mjs +0 -929
  170. package/dist/react-native.d.mts +0 -82
  171. package/dist/react-native.d.ts +0 -82
  172. package/dist/react-native.js +0 -1370
  173. package/dist/react-native.mjs +0 -1360
@@ -0,0 +1,40 @@
1
+ /**
2
+ * LicenseManager — Handles offline asymmetric license verification
3
+ * using P-256 + granular feature licensing.
4
+ *
5
+ * Matching nav-sdk/src/shared/LicenseManager.ts contract exactly.
6
+ */
7
+ import { FeatureKey } from '../features';
8
+ export type LicenseStatus = 'VALID' | 'INVALID' | 'EXPIRED' | 'MISSING';
9
+ export interface LicensePayload {
10
+ /** Legacy tier — 'pro' grants all features ('*') */
11
+ t?: 'core' | 'pro';
12
+ /** Expiration timestamp (ms since epoch) */
13
+ exp: number;
14
+ /** Bundle ID / domain */
15
+ bid: string;
16
+ /** Granular feature flags */
17
+ f?: string[];
18
+ }
19
+ export declare class LicenseManager {
20
+ private status;
21
+ private payload;
22
+ private enabledFeatures;
23
+ private warnedFeatures;
24
+ private isDev;
25
+ private static PUBLIC_KEY_RAW;
26
+ constructor(licenseKey?: string, isDev?: boolean);
27
+ validate(key: string): Promise<LicenseStatus>;
28
+ private extractFeatures;
29
+ hasFeature(feature: FeatureKey): boolean;
30
+ /** Warn once per feature when a consumer tries to use a gated feature. */
31
+ warnOnce(feature: FeatureKey): void;
32
+ /** Returns true if the license grants the full Pro tier (all features). */
33
+ isProEnabled(): boolean;
34
+ isAIEnabled(): boolean;
35
+ getStatus(): LicenseStatus;
36
+ getPayload(): LicensePayload | null;
37
+ getEnabledFeatures(): string[];
38
+ private verifySignature;
39
+ }
40
+ //# sourceMappingURL=LicenseManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LicenseManager.d.ts","sourceRoot":"","sources":["../../../../../core/src/license/LicenseManager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAY,UAAU,EAAE,MAAM,aAAa,CAAC;AAGnD,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAExE,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,CAAC,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACnB,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,6BAA6B;IAC7B,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;CACd;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,KAAK,CAAkB;IAG/B,OAAO,CAAC,MAAM,CAAC,cAAc,CACgE;gBAEjF,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;IASnC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA4C1D,OAAO,CAAC,eAAe;IAiBhB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO;IAO/C,0EAA0E;IACnE,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI;IAU1C,2EAA2E;IACpE,YAAY,IAAI,OAAO;IAKvB,WAAW,IAAI,OAAO;IAItB,SAAS,IAAI,aAAa;IAI1B,UAAU,IAAI,cAAc,GAAG,IAAI;IAInC,kBAAkB,IAAI,MAAM,EAAE;YAMvB,eAAe;CAe9B"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * LicenseManager — Handles offline asymmetric license verification
3
+ * using P-256 + granular feature licensing.
4
+ *
5
+ * Matching nav-sdk/src/shared/LicenseManager.ts contract exactly.
6
+ */
7
+ import { FEATURES } from '../features';
8
+ import { verifyP256Signature } from './verifyP256Signature';
9
+ export class LicenseManager {
10
+ constructor(licenseKey, isDev) {
11
+ this.status = 'MISSING';
12
+ this.payload = null;
13
+ this.enabledFeatures = new Set();
14
+ this.warnedFeatures = new Set();
15
+ this.isDev = false;
16
+ this.isDev = isDev ?? false;
17
+ if (licenseKey) {
18
+ this.validate(licenseKey);
19
+ }
20
+ }
21
+ // ── Validation ──────────────────────────────────────────────────────────────
22
+ async validate(key) {
23
+ try {
24
+ if (this.status === 'VALID' && this.payload?.bid === 'dev.bypass') {
25
+ return 'VALID'; // Already bypassed synchronously
26
+ }
27
+ const [payloadB64, signatureB64] = key.split('.');
28
+ if (!payloadB64 || !signatureB64) {
29
+ this.status = 'INVALID';
30
+ return this.status;
31
+ }
32
+ const payloadStr = atob(payloadB64);
33
+ this.payload = JSON.parse(payloadStr);
34
+ // Expiration check
35
+ if (!this.payload || this.payload.exp < Date.now()) {
36
+ this.status = 'EXPIRED';
37
+ return this.status;
38
+ }
39
+ // Signature check
40
+ const isValid = await this.verifySignature(payloadB64, signatureB64);
41
+ if (!isValid) {
42
+ this.status = 'INVALID';
43
+ return this.status;
44
+ }
45
+ this.status = 'VALID';
46
+ this.extractFeatures();
47
+ console.log(`🔑 [NavCore] License validation complete. Plan: ${this.isProEnabled() ? 'pro' : 'core'}`);
48
+ }
49
+ catch {
50
+ this.status = 'INVALID';
51
+ }
52
+ return this.status;
53
+ }
54
+ // ── Feature system ──────────────────────────────────────────────────────────
55
+ extractFeatures() {
56
+ this.enabledFeatures.clear();
57
+ if (!this.payload)
58
+ return;
59
+ // New granular feature flags
60
+ if (Array.isArray(this.payload.f)) {
61
+ for (const f of this.payload.f) {
62
+ this.enabledFeatures.add(f);
63
+ }
64
+ }
65
+ // Backward compatibility: pro tier = all features
66
+ if (this.payload.t === 'pro') {
67
+ this.enabledFeatures.add('*');
68
+ }
69
+ }
70
+ hasFeature(feature) {
71
+ if (this.isDev)
72
+ return true;
73
+ if (this.status !== 'VALID')
74
+ return false;
75
+ if (this.enabledFeatures.has('*'))
76
+ return true;
77
+ return this.enabledFeatures.has(feature);
78
+ }
79
+ /** Warn once per feature when a consumer tries to use a gated feature. */
80
+ warnOnce(feature) {
81
+ if (this.warnedFeatures.has(feature))
82
+ return;
83
+ console.warn(`[NavCore] Feature '${feature}' requires a Pro license`);
84
+ this.warnedFeatures.add(feature);
85
+ }
86
+ // ── Legacy / convenience helpers ────────────────────────────────────────────
87
+ /** Returns true if the license grants the full Pro tier (all features). */
88
+ isProEnabled() {
89
+ // Matches nav-sdk: checks for the wildcard '*' grant, not a specific feature
90
+ return this.hasFeature('*');
91
+ }
92
+ isAIEnabled() {
93
+ return this.hasFeature(FEATURES.NAV_AI);
94
+ }
95
+ getStatus() {
96
+ return this.status;
97
+ }
98
+ getPayload() {
99
+ return this.payload;
100
+ }
101
+ getEnabledFeatures() {
102
+ return Array.from(this.enabledFeatures);
103
+ }
104
+ // ── Crypto ──────────────────────────────────────────────────────────────────
105
+ async verifySignature(data, signature) {
106
+ try {
107
+ const encoder = new TextEncoder();
108
+ const encodedData = encoder.encode(data);
109
+ const signatureBin = Uint8Array.from(atob(signature), c => c.charCodeAt(0));
110
+ const publicKeyBin = Uint8Array.from(atob(LicenseManager.PUBLIC_KEY_RAW), c => c.charCodeAt(0));
111
+ return await verifyP256Signature(encodedData, signatureBin, publicKeyBin);
112
+ }
113
+ catch {
114
+ return false;
115
+ }
116
+ }
117
+ }
118
+ // SEC1 uncompressed form of the shared ECDSA P-256 public key.
119
+ LicenseManager.PUBLIC_KEY_RAW = 'BJOqJyZiZFrURbrIkkuvXXHTjV+MS9kp5uAGTvnWI0sp3yYi+Me7axMvNDCJi37ZJwVk/6iNM+6MeEO1TS97Xug=';
@@ -0,0 +1,2 @@
1
+ export declare function verifyP256Signature(message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): Promise<boolean>;
2
+ //# sourceMappingURL=verifyP256Signature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifyP256Signature.d.ts","sourceRoot":"","sources":["../../../../../core/src/license/verifyP256Signature.ts"],"names":[],"mappings":"AAQA,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,UAAU,EACrB,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC,OAAO,CAAC,CA+BlB"}
@@ -0,0 +1,26 @@
1
+ import { p256 } from '@noble/curves/nist.js';
2
+ function toArrayBuffer(bytes) {
3
+ const buffer = new ArrayBuffer(bytes.byteLength);
4
+ new Uint8Array(buffer).set(bytes);
5
+ return buffer;
6
+ }
7
+ export async function verifyP256Signature(message, signature, publicKey) {
8
+ if (signature.length !== 64 || publicKey.length !== 65 || publicKey[0] !== 4) {
9
+ return false;
10
+ }
11
+ try {
12
+ const subtle = typeof crypto !== 'undefined' ? crypto.subtle : undefined;
13
+ if (subtle) {
14
+ const importedKey = await subtle.importKey('raw', toArrayBuffer(publicKey), { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
15
+ return await subtle.verify({ name: 'ECDSA', hash: { name: 'SHA-256' } }, importedKey, toArrayBuffer(signature), toArrayBuffer(message));
16
+ }
17
+ return p256.verify(signature, message, publicKey, {
18
+ format: 'compact',
19
+ lowS: false,
20
+ prehash: true,
21
+ });
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Module 9a: NavigationFSM
3
+ * Navigation lifecycle state machine. Pure deterministic transitions.
4
+ */
5
+ export type NavLifecycleState = 'IDLE' | 'OVERVIEW' | 'APPROACHING_ROUTE' | 'ON_ROUTE';
6
+ export type LifecycleEvent = {
7
+ type: 'ROUTE_LOADED';
8
+ } | {
9
+ type: 'USER_START_NAVIGATION';
10
+ } | {
11
+ type: 'ROUTE_JOINED';
12
+ data: {
13
+ index: number;
14
+ };
15
+ } | {
16
+ type: 'NAVIGATION_FINISHED';
17
+ } | {
18
+ type: 'CANCEL_NAVIGATION';
19
+ } | {
20
+ type: 'RESET';
21
+ };
22
+ export interface LifecycleContext {
23
+ resumeIndex: number | null;
24
+ }
25
+ export interface LifecycleOutput {
26
+ state: NavLifecycleState;
27
+ context: LifecycleContext;
28
+ }
29
+ /**
30
+ * Pure lifecycle FSM — no React, no side effects.
31
+ */
32
+ export declare class NavigationFSM {
33
+ private _state;
34
+ private _context;
35
+ get state(): NavLifecycleState;
36
+ get context(): LifecycleContext;
37
+ /** Dispatch an event and return the new output */
38
+ dispatch(event: LifecycleEvent): LifecycleOutput;
39
+ /** Reset to initial state */
40
+ reset(): void;
41
+ }
42
+ //# sourceMappingURL=NavigationFSM.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NavigationFSM.d.ts","sourceRoot":"","sources":["../../../../../core/src/off-route/NavigationFSM.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,iBAAiB,GACzB,MAAM,GACN,UAAU,GACV,mBAAmB,GACnB,UAAU,CAAC;AAEf,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,GACxB;IAAE,IAAI,EAAE,uBAAuB,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,qBAAqB,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,mBAAmB,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,iBAAiB,CAAC;IACzB,OAAO,EAAE,gBAAgB,CAAC;CAC3B;AAED;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,QAAQ,CAA2C;IAE3D,IAAI,KAAK,IAAI,iBAAiB,CAAwB;IACtD,IAAI,OAAO,IAAI,gBAAgB,CAAiC;IAEhE,kDAAkD;IAClD,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG,eAAe;IAqChD,6BAA6B;IAC7B,KAAK,IAAI,IAAI;CAId"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Module 9a: NavigationFSM
3
+ * Navigation lifecycle state machine. Pure deterministic transitions.
4
+ */
5
+ /**
6
+ * Pure lifecycle FSM — no React, no side effects.
7
+ */
8
+ export class NavigationFSM {
9
+ constructor() {
10
+ this._state = 'IDLE';
11
+ this._context = { resumeIndex: null };
12
+ }
13
+ get state() { return this._state; }
14
+ get context() { return { ...this._context }; }
15
+ /** Dispatch an event and return the new output */
16
+ dispatch(event) {
17
+ const prev = this._state;
18
+ switch (prev) {
19
+ case 'IDLE':
20
+ if (event.type === 'ROUTE_LOADED')
21
+ this._state = 'OVERVIEW';
22
+ if (event.type === 'USER_START_NAVIGATION')
23
+ this._state = 'APPROACHING_ROUTE';
24
+ break;
25
+ case 'OVERVIEW':
26
+ if (event.type === 'USER_START_NAVIGATION')
27
+ this._state = 'APPROACHING_ROUTE';
28
+ break;
29
+ case 'APPROACHING_ROUTE':
30
+ if (event.type === 'ROUTE_JOINED') {
31
+ this._state = 'ON_ROUTE';
32
+ this._context.resumeIndex = event.data.index;
33
+ }
34
+ if (event.type === 'NAVIGATION_FINISHED')
35
+ this._state = 'IDLE';
36
+ break;
37
+ case 'ON_ROUTE':
38
+ if (event.type === 'NAVIGATION_FINISHED')
39
+ this._state = 'IDLE';
40
+ if (event.type === 'ROUTE_JOINED') {
41
+ this._context.resumeIndex = event.data.index;
42
+ }
43
+ break;
44
+ }
45
+ if (event.type === 'RESET' || event.type === 'CANCEL_NAVIGATION') {
46
+ this._state = 'IDLE';
47
+ this._context = { resumeIndex: null };
48
+ }
49
+ return { state: this._state, context: { ...this._context } };
50
+ }
51
+ /** Reset to initial state */
52
+ reset() {
53
+ this._state = 'IDLE';
54
+ this._context = { resumeIndex: null };
55
+ }
56
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Module 9b: OffRouteFSM
3
+ * Deviation → Reroute → Rejoin state machine. Pure deterministic transitions.
4
+ * Extracted from useOffRouteSystem / NavigationStateManager (stripped of React).
5
+ */
6
+ import type { Coordinate } from '../geo/haversine';
7
+ export type OffRouteState = 'ON_ROUTE' | 'DEVIATING' | 'OFF_ROUTE' | 'REROUTING' | 'REJOINING' | 'REJOINED' | 'FAILED';
8
+ export interface TempRoute {
9
+ geometry: Coordinate[];
10
+ distance: number;
11
+ duration: number;
12
+ }
13
+ export interface RejoinTarget {
14
+ coordinates: Coordinate;
15
+ index: number;
16
+ }
17
+ export interface OffRouteContext {
18
+ deviationSegmentIdx: number | null;
19
+ deviationStartMs: number | null;
20
+ tempRoute: TempRoute | null;
21
+ rejoinTarget: RejoinTarget | null;
22
+ lastRerouteMs: number | null;
23
+ rejoinedAtMs: number | null;
24
+ rejoinDwellFrames: number;
25
+ failureReason: string | null;
26
+ }
27
+ export type OffRouteEvent = {
28
+ type: 'DEVIATION_SUSPECTED';
29
+ deviationSegmentIdx: number;
30
+ } | {
31
+ type: 'DEVIATION_CONFIRMED';
32
+ deviationSegmentIdx: number;
33
+ } | {
34
+ type: 'REROUTE_STARTED';
35
+ } | {
36
+ type: 'REROUTE_SUCCESS';
37
+ tempRoute: TempRoute;
38
+ rejoinTarget: RejoinTarget;
39
+ } | {
40
+ type: 'REROUTE_FAILED';
41
+ reason: string;
42
+ } | {
43
+ type: 'REJOIN_DETECTED';
44
+ } | {
45
+ type: 'REJOIN_CONFIRMED';
46
+ } | {
47
+ type: 'RESUME';
48
+ } | {
49
+ type: 'RESET';
50
+ };
51
+ export interface OffRouteOutput {
52
+ state: OffRouteState;
53
+ context: OffRouteContext;
54
+ }
55
+ /**
56
+ * Pure off-route FSM — no React, no side effects.
57
+ */
58
+ export declare class OffRouteFSM {
59
+ private _state;
60
+ private _context;
61
+ get state(): OffRouteState;
62
+ get context(): OffRouteContext;
63
+ /** Dispatch an event and return the new output */
64
+ dispatch(event: OffRouteEvent, nowMs?: number): OffRouteOutput;
65
+ /** Full reset */
66
+ reset(): void;
67
+ /** Check if deviation confirmation time has elapsed */
68
+ isDeviationConfirmed(nowMs?: number): boolean;
69
+ }
70
+ //# sourceMappingURL=OffRouteFSM.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OffRouteFSM.d.ts","sourceRoot":"","sources":["../../../../../core/src/off-route/OffRouteFSM.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,MAAM,aAAa,GACrB,UAAU,GACV,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,UAAU,GACV,QAAQ,CAAC;AAEb,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,UAAU,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,mBAAmB,EAAE,MAAM,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,mBAAmB,EAAE,MAAM,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE,GAC3B;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,SAAS,EAAE,SAAS,CAAC;IAAC,YAAY,EAAE,YAAY,CAAA;CAAE,GAC7E;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE,GAC3B;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,eAAe,CAAC;CAC1B;AAgBD;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,QAAQ,CAA2C;IAE3D,IAAI,KAAK,IAAI,aAAa,CAAwB;IAClD,IAAI,OAAO,IAAI,eAAe,CAAiC;IAE/D,kDAAkD;IAClD,QAAQ,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,cAAc;IAwH9D,iBAAiB;IACjB,KAAK,IAAI,IAAI;IAKb,uDAAuD;IACvD,oBAAoB,CAAC,KAAK,GAAE,MAAmB,GAAG,OAAO;CAI1D"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Module 9b: OffRouteFSM
3
+ * Deviation → Reroute → Rejoin state machine. Pure deterministic transitions.
4
+ * Extracted from useOffRouteSystem / NavigationStateManager (stripped of React).
5
+ */
6
+ const INITIAL_CONTEXT = {
7
+ deviationSegmentIdx: null,
8
+ deviationStartMs: null,
9
+ tempRoute: null,
10
+ rejoinTarget: null,
11
+ lastRerouteMs: null,
12
+ rejoinedAtMs: null,
13
+ rejoinDwellFrames: 0,
14
+ failureReason: null,
15
+ };
16
+ const DEVIATION_CONFIRM_MS = 2000;
17
+ const REJOIN_DWELL_THRESHOLD = 3;
18
+ /**
19
+ * Pure off-route FSM — no React, no side effects.
20
+ */
21
+ export class OffRouteFSM {
22
+ constructor() {
23
+ this._state = 'ON_ROUTE';
24
+ this._context = { ...INITIAL_CONTEXT };
25
+ }
26
+ get state() { return this._state; }
27
+ get context() { return { ...this._context }; }
28
+ /** Dispatch an event and return the new output */
29
+ dispatch(event, nowMs) {
30
+ const now = nowMs ?? Date.now();
31
+ switch (this._state) {
32
+ case 'ON_ROUTE':
33
+ if (event.type === 'DEVIATION_SUSPECTED') {
34
+ this._state = 'DEVIATING';
35
+ this._context.deviationSegmentIdx = event.deviationSegmentIdx;
36
+ this._context.deviationStartMs = now;
37
+ }
38
+ break;
39
+ case 'DEVIATING':
40
+ if (event.type === 'DEVIATION_CONFIRMED') {
41
+ const elapsed = this._context.deviationStartMs !== null
42
+ ? now - this._context.deviationStartMs
43
+ : 0;
44
+ if (elapsed >= DEVIATION_CONFIRM_MS) {
45
+ this._state = 'OFF_ROUTE';
46
+ this._context.deviationSegmentIdx = event.deviationSegmentIdx;
47
+ }
48
+ }
49
+ if (event.type === 'RESUME' || event.type === 'RESET') {
50
+ this._state = 'ON_ROUTE';
51
+ // RESUME preserves lastRerouteMs (cooldown), RESET clears everything
52
+ if (event.type === 'RESET') {
53
+ this._context = { ...INITIAL_CONTEXT };
54
+ }
55
+ else {
56
+ this._context.deviationSegmentIdx = null;
57
+ this._context.deviationStartMs = null;
58
+ }
59
+ }
60
+ break;
61
+ case 'OFF_ROUTE':
62
+ if (event.type === 'REROUTE_STARTED') {
63
+ this._state = 'REROUTING';
64
+ this._context.lastRerouteMs = now;
65
+ }
66
+ if (event.type === 'RESUME') {
67
+ this._state = 'ON_ROUTE';
68
+ this._context.deviationSegmentIdx = null;
69
+ this._context.deviationStartMs = null;
70
+ }
71
+ if (event.type === 'RESET') {
72
+ this._state = 'ON_ROUTE';
73
+ this._context = { ...INITIAL_CONTEXT };
74
+ }
75
+ break;
76
+ case 'REROUTING':
77
+ if (event.type === 'REROUTE_SUCCESS') {
78
+ this._state = 'REJOINING';
79
+ this._context.tempRoute = event.tempRoute;
80
+ this._context.rejoinTarget = event.rejoinTarget;
81
+ }
82
+ if (event.type === 'REROUTE_FAILED') {
83
+ this._state = 'FAILED';
84
+ this._context.failureReason = event.reason;
85
+ }
86
+ break;
87
+ case 'REJOINING':
88
+ if (event.type === 'REJOIN_DETECTED') {
89
+ this._context.rejoinDwellFrames++;
90
+ }
91
+ if (event.type === 'REJOIN_CONFIRMED' ||
92
+ this._context.rejoinDwellFrames >= REJOIN_DWELL_THRESHOLD) {
93
+ this._state = 'REJOINED';
94
+ this._context.rejoinedAtMs = now;
95
+ this._context.rejoinDwellFrames = 0;
96
+ }
97
+ if (event.type === 'RESUME') {
98
+ this._state = 'ON_ROUTE';
99
+ this._context.tempRoute = null;
100
+ this._context.rejoinTarget = null;
101
+ this._context.rejoinedAtMs = null;
102
+ this._context.deviationSegmentIdx = null;
103
+ this._context.deviationStartMs = null;
104
+ this._context.rejoinDwellFrames = 0;
105
+ }
106
+ if (event.type === 'RESET') {
107
+ this._state = 'ON_ROUTE';
108
+ this._context = { ...INITIAL_CONTEXT };
109
+ }
110
+ break;
111
+ case 'REJOINED':
112
+ if (event.type === 'RESUME') {
113
+ this._state = 'ON_ROUTE';
114
+ this._context.tempRoute = null;
115
+ this._context.rejoinTarget = null;
116
+ this._context.rejoinedAtMs = null;
117
+ this._context.deviationSegmentIdx = null;
118
+ this._context.deviationStartMs = null;
119
+ }
120
+ if (event.type === 'RESET') {
121
+ this._state = 'ON_ROUTE';
122
+ this._context = { ...INITIAL_CONTEXT };
123
+ }
124
+ break;
125
+ case 'FAILED':
126
+ if (event.type === 'RESUME') {
127
+ this._state = 'ON_ROUTE';
128
+ this._context.failureReason = null;
129
+ this._context.deviationSegmentIdx = null;
130
+ this._context.deviationStartMs = null;
131
+ }
132
+ if (event.type === 'RESET') {
133
+ this._state = 'ON_ROUTE';
134
+ this._context = { ...INITIAL_CONTEXT };
135
+ }
136
+ break;
137
+ }
138
+ return { state: this._state, context: { ...this._context } };
139
+ }
140
+ /** Full reset */
141
+ reset() {
142
+ this._state = 'ON_ROUTE';
143
+ this._context = { ...INITIAL_CONTEXT };
144
+ }
145
+ /** Check if deviation confirmation time has elapsed */
146
+ isDeviationConfirmed(nowMs = Date.now()) {
147
+ if (!this._context.deviationStartMs)
148
+ return false;
149
+ return (nowMs - this._context.deviationStartMs) >= DEVIATION_CONFIRM_MS;
150
+ }
151
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Module 8: RouteOffsetPredictor
3
+ * Linear distance-based position predictor for smooth rendering between GPS fixes.
4
+ */
5
+ import { type Coordinate } from '../geo/haversine';
6
+ /**
7
+ * Advance the predictor's offset by speed * dt.
8
+ */
9
+ export declare function advancePredictor(currentOffset: number, speedMps: number, dtSec: number): number;
10
+ /**
11
+ * Correct the predictor when a new GPS fix arrives.
12
+ * Blend towards the actual measured offset using betaCorrection.
13
+ */
14
+ export declare function applyGpsCorrection(predictedOffset: number, measuredOffset: number, betaCorrection?: number): number;
15
+ /**
16
+ * Given a linear offset (metres from route start), find the [lng, lat] on the route.
17
+ *
18
+ * @param offset Linear distance from route start in metres
19
+ * @param cumulativeDistances Forward cumulative distance array
20
+ * @param route Route coordinates
21
+ * @returns Interpolated coordinate on the route
22
+ */
23
+ export declare function pointAtOffset(offset: number, cumulativeDistances: Float64Array, route: Coordinate[]): Coordinate;
24
+ //# sourceMappingURL=RouteOffsetPredictor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RouteOffsetPredictor.d.ts","sourceRoot":"","sources":["../../../../../core/src/predictor/RouteOffsetPredictor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,MAAM,CAER;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,eAAe,EAAE,MAAM,EACvB,cAAc,EAAE,MAAM,EACtB,cAAc,GAAE,MAAa,GAC5B,MAAM,CAGR;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,mBAAmB,EAAE,YAAY,EACjC,KAAK,EAAE,UAAU,EAAE,GAClB,UAAU,CA2BZ"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Module 8: RouteOffsetPredictor
3
+ * Linear distance-based position predictor for smooth rendering between GPS fixes.
4
+ */
5
+ import { interpolatePoint } from '../geo/bearing';
6
+ /**
7
+ * Advance the predictor's offset by speed * dt.
8
+ */
9
+ export function advancePredictor(currentOffset, speedMps, dtSec) {
10
+ return currentOffset + speedMps * dtSec;
11
+ }
12
+ /**
13
+ * Correct the predictor when a new GPS fix arrives.
14
+ * Blend towards the actual measured offset using betaCorrection.
15
+ */
16
+ export function applyGpsCorrection(predictedOffset, measuredOffset, betaCorrection = 0.15) {
17
+ const error = measuredOffset - predictedOffset;
18
+ return predictedOffset + betaCorrection * error;
19
+ }
20
+ /**
21
+ * Given a linear offset (metres from route start), find the [lng, lat] on the route.
22
+ *
23
+ * @param offset Linear distance from route start in metres
24
+ * @param cumulativeDistances Forward cumulative distance array
25
+ * @param route Route coordinates
26
+ * @returns Interpolated coordinate on the route
27
+ */
28
+ export function pointAtOffset(offset, cumulativeDistances, route) {
29
+ if (route.length === 0)
30
+ return [0, 0];
31
+ if (offset <= 0)
32
+ return route[0];
33
+ const totalLength = cumulativeDistances[cumulativeDistances.length - 1] ?? 0;
34
+ if (offset >= totalLength)
35
+ return route[route.length - 1];
36
+ // Binary search for the segment containing this offset
37
+ let lo = 0;
38
+ let hi = cumulativeDistances.length - 1;
39
+ while (lo < hi - 1) {
40
+ const mid = (lo + hi) >> 1;
41
+ if (cumulativeDistances[mid] <= offset) {
42
+ lo = mid;
43
+ }
44
+ else {
45
+ hi = mid;
46
+ }
47
+ }
48
+ const segStart = cumulativeDistances[lo];
49
+ const segEnd = cumulativeDistances[hi];
50
+ const segLength = segEnd - segStart;
51
+ if (segLength === 0)
52
+ return route[lo];
53
+ const t = (offset - segStart) / segLength;
54
+ return interpolatePoint(route[lo], route[hi], t);
55
+ }