@ingissa/navcore-simulator 1.0.9 → 1.0.11

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