@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/src/index.ts DELETED
@@ -1,54 +0,0 @@
1
- /**
2
- * @navcore/simulator — Public API barrel.
3
- *
4
- * Import everything from this file. Do NOT import from sub-paths directly.
5
- *
6
- * Example:
7
- * import { SimulatorEngine, NoiseEngine, ScenarioInjector, GpxParser } from '@ingissa/navcore-simulator'
8
- *
9
- * React Native consumers also get:
10
- * import { useSimulator, SimulatorControls } from '@ingissa/navcore-simulator'
11
- */
12
-
13
- // ── Core types ────────────────────────────────────────────────────────────────
14
- export type {
15
- Coordinate,
16
- SimPosition,
17
- SimLocationCoords,
18
- SimLocationObject,
19
- SimulatorConfig,
20
- NoiseProfile,
21
- Scenario,
22
- SimSession,
23
- } from './core/types';
24
-
25
- // ── Engine ────────────────────────────────────────────────────────────────────
26
- export { SimulatorEngine } from './core/SimulatorEngine';
27
- export type { EngineState } from './core/SimulatorEngine';
28
-
29
- // ── RouteWalker ───────────────────────────────────────────────────────────────
30
- export { RouteWalker } from './core/RouteWalker';
31
- export type { WalkerPosition } from './core/RouteWalker';
32
-
33
- // ── NoiseEngine ───────────────────────────────────────────────────────────────
34
- export { NoiseEngine } from './core/NoiseEngine';
35
-
36
- // ── Scenarios ─────────────────────────────────────────────────────────────────
37
- export { ScenarioInjector } from './scenarios/ScenarioInjector';
38
-
39
- // ── Replay ────────────────────────────────────────────────────────────────────
40
- export { GpxParser } from './replay/GpxParser';
41
- export type { GpxParseResult } from './replay/GpxParser';
42
- export { SessionRecorder } from './replay/SessionRecorder';
43
- export type { ExportFormat } from './replay/SessionRecorder';
44
-
45
- // ── Fleet (Phase 2) ───────────────────────────────────────────────────────────
46
- export { FleetSimulator } from './fleet/FleetSimulator';
47
- export type { VehicleProfile, FleetReport } from './fleet/FleetSimulator';
48
-
49
- // ── React Native ─────────────────────────────────────────────────────────────
50
- // Import these from the top-level package — no sub-path imports required:
51
- // import { useSimulator, SimulatorControls } from '@ingissa/navcore-simulator'
52
- export { useSimulator } from './react-native/useSimulator';
53
- export { SimulatorControls } from './react-native/SimulatorControls';
54
- export type { ScenarioLogEntry } from './react-native/SimulatorControls';
package/src/node.ts DELETED
@@ -1,48 +0,0 @@
1
- /**
2
- * @navcore/simulator — Node.js / CI barrel
3
- *
4
- * This barrel exports ONLY the headless classes that have no React Native
5
- * dependencies. It is used by vitest (via the @navcore/simulator alias in
6
- * vitest.config.ts) so that unit tests never pull in react-native and
7
- * trigger Vite's Flow-syntax parser failure.
8
- *
9
- * React Native consumers (useSimulator, SimulatorControls) must import
10
- * from the main barrel:
11
- * import { useSimulator } from '@ingissa/navcore-simulator' ← full barrel
12
- */
13
-
14
- // ── Core types ─────────────────────────────────────────────────────────────────
15
- export type {
16
- Coordinate,
17
- SimPosition,
18
- SimLocationCoords,
19
- SimLocationObject,
20
- SimulatorConfig,
21
- NoiseProfile,
22
- Scenario,
23
- SimSession,
24
- } from './core/types';
25
-
26
- // ── Engine ─────────────────────────────────────────────────────────────────────
27
- export { SimulatorEngine } from './core/SimulatorEngine';
28
- export type { EngineState } from './core/SimulatorEngine';
29
-
30
- // ── RouteWalker ────────────────────────────────────────────────────────────────
31
- export { RouteWalker } from './core/RouteWalker';
32
- export type { WalkerPosition } from './core/RouteWalker';
33
-
34
- // ── NoiseEngine ────────────────────────────────────────────────────────────────
35
- export { NoiseEngine } from './core/NoiseEngine';
36
-
37
- // ── Scenarios ──────────────────────────────────────────────────────────────────
38
- export { ScenarioInjector } from './scenarios/ScenarioInjector';
39
-
40
- // ── Replay ─────────────────────────────────────────────────────────────────────
41
- export { GpxParser } from './replay/GpxParser';
42
- export type { GpxParseResult } from './replay/GpxParser';
43
- export { SessionRecorder } from './replay/SessionRecorder';
44
- export type { ExportFormat } from './replay/SessionRecorder';
45
-
46
- // ── Fleet (Phase 2 skeleton) ───────────────────────────────────────────────────
47
- export { FleetSimulator } from './fleet/FleetSimulator';
48
- export type { VehicleProfile, FleetReport } from './fleet/FleetSimulator';
@@ -1,473 +0,0 @@
1
- /**
2
- * SimulatorControls — Self-contained React Native control panel for SimulatorEngine.
3
- *
4
- * Three style variants:
5
- * 'minimal' — compact horizontal bar: speed selector + pause/resume
6
- * 'full' — full card with speed, seek, scenario log, recorder, stats
7
- * 'floating' — collapsible bottom sheet (Animated, no external libs)
8
- *
9
- * Colour tokens match the Klaroways design system used in NavCoreTestScreen.
10
- */
11
-
12
- import React, { useState, useRef, useCallback } from 'react';
13
- import {
14
- View,
15
- Text,
16
- TouchableOpacity,
17
- StyleSheet,
18
- ScrollView,
19
- Animated,
20
- Platform,
21
- } from 'react-native';
22
- import type { SimulatorControls as Controls, SessionControls } from './useSimulator';
23
- import type { SimSession } from '../core/types';
24
-
25
- // ─── Colour Tokens ────────────────────────────────────────────────────────────
26
- const KW = {
27
- navyDeep: '#081525',
28
- navyCard: '#152240',
29
- navyBorder: '#1E3255',
30
- emerald: '#00BF76',
31
- emeraldDark: '#04342C',
32
- emeraldMid: '#009A5F',
33
- amber: '#F5A623',
34
- amberDark: '#412402',
35
- slate: '#6B7FA3',
36
- slateLight: '#8FA3C8',
37
- light: '#EBEFF8',
38
- fault: '#FF4848',
39
- faultDark: '#3D0000',
40
- white: '#FFFFFF',
41
- mono: Platform.OS === 'ios' ? 'Courier New' : 'monospace',
42
- } as const;
43
-
44
- // ─── Types ────────────────────────────────────────────────────────────────────
45
-
46
- export interface ScenarioLogEntry {
47
- ts: number;
48
- name: string;
49
- type: string;
50
- }
51
-
52
- export interface SimulatorControlsProps {
53
- simulator: Controls;
54
- session?: SessionControls;
55
- style?: 'minimal' | 'full' | 'floating';
56
- showScenarioLog?: boolean;
57
- showSessionRecorder?: boolean;
58
- scenarioLog?: ScenarioLogEntry[];
59
- /** Current speed multiplier (controlled externally) */
60
- currentSpeed?: 1 | 2 | 4 | 8;
61
- /** Current engine state */
62
- engineState?: 'idle' | 'running' | 'paused' | 'complete';
63
- isRecording?: boolean;
64
- onExportSession?: (session: SimSession) => void;
65
- /** Additional style for the root container */
66
- containerStyle?: object;
67
- }
68
-
69
- // ─── Speed Selector ───────────────────────────────────────────────────────────
70
-
71
- function SpeedSelector({
72
- current,
73
- onChange,
74
- }: {
75
- current: 1 | 2 | 4 | 8;
76
- onChange: (v: 1 | 2 | 4 | 8) => void;
77
- }) {
78
- return (
79
- <View style={spd.row}>
80
- <Text style={spd.label}>SIM SPEED</Text>
81
- {([1, 2, 4, 8] as const).map((v) => (
82
- <TouchableOpacity
83
- key={v}
84
- style={[spd.btn, current === v && spd.btnActive]}
85
- onPress={() => onChange(v)}
86
- activeOpacity={0.7}
87
- >
88
- <Text style={[spd.btnText, current === v && { color: KW.emeraldDark }]}>
89
- {v}×
90
- </Text>
91
- </TouchableOpacity>
92
- ))}
93
- </View>
94
- );
95
- }
96
-
97
- const spd = StyleSheet.create({
98
- row: { flexDirection: 'row', alignItems: 'center', gap: 6 },
99
- label: { fontSize: 9, letterSpacing: 1.2, color: KW.slate, marginRight: 4 },
100
- btn: {
101
- paddingHorizontal: 10,
102
- paddingVertical: 5,
103
- borderRadius: 6,
104
- borderWidth: 1,
105
- borderColor: KW.navyBorder,
106
- },
107
- btnActive: { backgroundColor: KW.emerald, borderColor: KW.emerald },
108
- btnText: { fontSize: 11, fontWeight: '700', color: KW.slate },
109
- });
110
-
111
- // ─── Pause / Resume ───────────────────────────────────────────────────────────
112
-
113
- function PauseResumeButton({
114
- state,
115
- onPause,
116
- onResume,
117
- }: {
118
- state: 'idle' | 'running' | 'paused' | 'complete';
119
- onPause: () => void;
120
- onResume: () => void;
121
- }) {
122
- if (state !== 'running' && state !== 'paused') return null;
123
- return (
124
- <TouchableOpacity
125
- style={[pr.btn, state === 'paused' && pr.btnResume]}
126
- onPress={state === 'running' ? onPause : onResume}
127
- activeOpacity={0.75}
128
- >
129
- <Text style={pr.text}>{state === 'running' ? '⏸' : '▶'}</Text>
130
- </TouchableOpacity>
131
- );
132
- }
133
-
134
- const pr = StyleSheet.create({
135
- btn: {
136
- paddingHorizontal: 14,
137
- paddingVertical: 7,
138
- borderRadius: 8,
139
- backgroundColor: KW.navyDeep,
140
- borderWidth: 1,
141
- borderColor: KW.navyBorder,
142
- alignItems: 'center',
143
- },
144
- btnResume: { borderColor: KW.emerald },
145
- text: { fontSize: 16 },
146
- });
147
-
148
- // ─── Scenario Log ─────────────────────────────────────────────────────────────
149
-
150
- function ScenarioLog({ entries }: { entries: ScenarioLogEntry[] }) {
151
- return (
152
- <View style={log.container}>
153
- <Text style={log.title}>// scenario log</Text>
154
- <ScrollView style={log.scroll} showsVerticalScrollIndicator={false}>
155
- {entries.length === 0 ? (
156
- <Text style={log.empty}>No scenarios fired yet…</Text>
157
- ) : (
158
- entries.map((e, i) => {
159
- const d = new Date(e.ts);
160
- const time = `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;
161
- return (
162
- <Text key={i} style={log.entry}>
163
- <Text style={log.time}>{time} </Text>
164
- <Text style={{ color: KW.amber }}>[{e.type}]</Text>
165
- {' '}{e.name}
166
- </Text>
167
- );
168
- })
169
- )}
170
- </ScrollView>
171
- </View>
172
- );
173
- }
174
-
175
- const log = StyleSheet.create({
176
- container: {
177
- backgroundColor: KW.navyDeep,
178
- borderRadius: 8,
179
- padding: 8,
180
- borderWidth: 1,
181
- borderColor: KW.navyBorder,
182
- maxHeight: 100,
183
- marginTop: 8,
184
- },
185
- title: {
186
- fontSize: 9,
187
- letterSpacing: 1,
188
- color: KW.slate,
189
- fontFamily: KW.mono,
190
- marginBottom: 4,
191
- },
192
- scroll: { flex: 1 },
193
- empty: { fontSize: 10, color: KW.slate, fontFamily: KW.mono },
194
- entry: { fontSize: 10, color: KW.slateLight, fontFamily: KW.mono, marginBottom: 2 },
195
- time: { color: KW.slate },
196
- });
197
-
198
- // ─── Session Recorder Controls ────────────────────────────────────────────────
199
-
200
- function SessionRecorderControls({
201
- isRecording,
202
- session,
203
- onExport,
204
- lastSession,
205
- }: {
206
- isRecording: boolean;
207
- session?: SessionControls;
208
- onExport?: (s: SimSession) => void;
209
- lastSession: SimSession | null;
210
- }) {
211
- if (!session) return null;
212
-
213
- return (
214
- <View style={rec.row}>
215
- <TouchableOpacity
216
- style={[rec.btn, isRecording && rec.btnActive]}
217
- onPress={() => {
218
- if (isRecording) {
219
- // session stop returns SimSession | null from hook
220
- } else {
221
- session.start();
222
- }
223
- }}
224
- activeOpacity={0.75}
225
- >
226
- <Text style={rec.btnText}>{isRecording ? '⏹ STOP REC' : '⏺ RECORD'}</Text>
227
- </TouchableOpacity>
228
-
229
- {lastSession && onExport && (
230
- <TouchableOpacity
231
- style={rec.exportBtn}
232
- onPress={() => onExport(lastSession)}
233
- activeOpacity={0.75}
234
- >
235
- <Text style={rec.exportText}>EXPORT JSON</Text>
236
- </TouchableOpacity>
237
- )}
238
- </View>
239
- );
240
- }
241
-
242
- const rec = StyleSheet.create({
243
- row: { flexDirection: 'row', gap: 8, marginTop: 8 },
244
- btn: {
245
- flex: 1,
246
- paddingVertical: 8,
247
- borderRadius: 8,
248
- backgroundColor: KW.navyDeep,
249
- borderWidth: 1,
250
- borderColor: KW.navyBorder,
251
- alignItems: 'center',
252
- },
253
- btnActive: { borderColor: KW.fault, backgroundColor: KW.faultDark },
254
- btnText: { fontSize: 11, fontWeight: '700', color: KW.slateLight, letterSpacing: 0.8 },
255
- exportBtn: {
256
- paddingHorizontal: 12,
257
- paddingVertical: 8,
258
- borderRadius: 8,
259
- backgroundColor: KW.navyDeep,
260
- borderWidth: 1,
261
- borderColor: KW.emeraldMid,
262
- alignItems: 'center',
263
- },
264
- exportText: { fontSize: 10, fontWeight: '700', color: KW.emerald, letterSpacing: 0.5 },
265
- });
266
-
267
- // ─── Main Component ───────────────────────────────────────────────────────────
268
-
269
- export function SimulatorControls({
270
- simulator,
271
- session,
272
- style = 'minimal',
273
- showScenarioLog = false,
274
- showSessionRecorder = false,
275
- scenarioLog = [],
276
- currentSpeed = 1,
277
- engineState = 'idle',
278
- isRecording = false,
279
- onExportSession,
280
- containerStyle,
281
- }: SimulatorControlsProps) {
282
- const [speed, setSpeedState] = useState<1 | 2 | 4 | 8>(currentSpeed);
283
- const [lastSession, setLastSession] = useState<SimSession | null>(null);
284
- // Floating sheet animation
285
- const sheetAnim = useRef(new Animated.Value(0)).current;
286
- const [isExpanded, setIsExpanded] = useState(false);
287
-
288
- const handleSpeedChange = useCallback(
289
- (v: 1 | 2 | 4 | 8) => {
290
- setSpeedState(v);
291
- simulator.setSpeed(v);
292
- },
293
- [simulator],
294
- );
295
-
296
- const handleStopRecording = useCallback(() => {
297
- const s = session?.stop();
298
- if (s) setLastSession(s);
299
- }, [session]);
300
-
301
- const toggleSheet = useCallback(() => {
302
- const toValue = isExpanded ? 0 : 1;
303
- setIsExpanded(!isExpanded);
304
- Animated.spring(sheetAnim, {
305
- toValue,
306
- useNativeDriver: false,
307
- bounciness: 4,
308
- }).start();
309
- }, [isExpanded, sheetAnim]);
310
-
311
- // ── Minimal ────────────────────────────────────────────────────────────────
312
- if (style === 'minimal') {
313
- return (
314
- <View style={[s.minimal, containerStyle]}>
315
- <SpeedSelector current={speed} onChange={handleSpeedChange} />
316
- <PauseResumeButton
317
- state={engineState}
318
- onPause={simulator.pause}
319
- onResume={simulator.resume}
320
- />
321
- </View>
322
- );
323
- }
324
-
325
- // ── Full ───────────────────────────────────────────────────────────────────
326
- if (style === 'full') {
327
- return (
328
- <View style={[s.full, containerStyle]}>
329
- <View style={s.fullHeader}>
330
- <Text style={s.fullTitle}>SIMULATOR</Text>
331
- <View style={[s.stateBadge, engineState === 'running' && s.stateBadgeActive]}>
332
- <Text style={s.stateText}>{engineState.toUpperCase()}</Text>
333
- </View>
334
- </View>
335
-
336
- <SpeedSelector current={speed} onChange={handleSpeedChange} />
337
-
338
- <View style={s.pauseRow}>
339
- <PauseResumeButton
340
- state={engineState}
341
- onPause={simulator.pause}
342
- onResume={simulator.resume}
343
- />
344
- </View>
345
-
346
- {showScenarioLog && <ScenarioLog entries={scenarioLog} />}
347
-
348
- {showSessionRecorder && (
349
- <SessionRecorderControls
350
- isRecording={isRecording}
351
- session={session}
352
- onExport={onExportSession}
353
- lastSession={lastSession}
354
- />
355
- )}
356
- </View>
357
- );
358
- }
359
-
360
- // ── Floating ───────────────────────────────────────────────────────────────
361
- const sheetHeight = sheetAnim.interpolate({
362
- inputRange: [0, 1],
363
- outputRange: [44, showScenarioLog ? 260 : 160],
364
- });
365
-
366
- return (
367
- <Animated.View style={[s.floating, { height: sheetHeight }, containerStyle]}>
368
- {/* Handle */}
369
- <TouchableOpacity style={s.floatHandle} onPress={toggleSheet} activeOpacity={0.8}>
370
- <View style={s.handleBar} />
371
- <Text style={s.floatTitle}>SIMULATOR {isExpanded ? '▼' : '▲'}</Text>
372
- </TouchableOpacity>
373
-
374
- {/* Content (only visible when expanded) */}
375
- {isExpanded && (
376
- <View style={s.floatContent}>
377
- <SpeedSelector current={speed} onChange={handleSpeedChange} />
378
- <View style={s.pauseRow}>
379
- <PauseResumeButton
380
- state={engineState}
381
- onPause={simulator.pause}
382
- onResume={simulator.resume}
383
- />
384
- </View>
385
- {showScenarioLog && <ScenarioLog entries={scenarioLog} />}
386
- </View>
387
- )}
388
- </Animated.View>
389
- );
390
- }
391
-
392
- // ─── Styles ───────────────────────────────────────────────────────────────────
393
- const s = StyleSheet.create({
394
- // Minimal
395
- minimal: {
396
- flexDirection: 'row',
397
- alignItems: 'center',
398
- gap: 12,
399
- paddingHorizontal: 4,
400
- paddingVertical: 6,
401
- },
402
-
403
- // Full
404
- full: {
405
- backgroundColor: KW.navyCard,
406
- borderRadius: 12,
407
- padding: 14,
408
- borderWidth: 1,
409
- borderColor: KW.navyBorder,
410
- },
411
- fullHeader: {
412
- flexDirection: 'row',
413
- justifyContent: 'space-between',
414
- alignItems: 'center',
415
- marginBottom: 10,
416
- },
417
- fullTitle: {
418
- fontSize: 11,
419
- fontWeight: '800',
420
- color: KW.slateLight,
421
- letterSpacing: 1.5,
422
- },
423
- stateBadge: {
424
- paddingHorizontal: 8,
425
- paddingVertical: 3,
426
- borderRadius: 4,
427
- backgroundColor: KW.navyDeep,
428
- borderWidth: 1,
429
- borderColor: KW.navyBorder,
430
- },
431
- stateBadgeActive: { borderColor: KW.emerald },
432
- stateText: { fontSize: 9, fontWeight: '700', color: KW.slate, letterSpacing: 1 },
433
- pauseRow: { marginTop: 8 },
434
-
435
- // Floating
436
- floating: {
437
- position: 'absolute',
438
- bottom: 0,
439
- left: 0,
440
- right: 0,
441
- backgroundColor: KW.navyCard,
442
- borderTopLeftRadius: 16,
443
- borderTopRightRadius: 16,
444
- borderTopWidth: 1,
445
- borderColor: KW.navyBorder,
446
- overflow: 'hidden',
447
- zIndex: 100,
448
- },
449
- floatHandle: {
450
- height: 44,
451
- alignItems: 'center',
452
- justifyContent: 'center',
453
- flexDirection: 'row',
454
- gap: 10,
455
- },
456
- handleBar: {
457
- width: 36,
458
- height: 4,
459
- backgroundColor: KW.navyBorder,
460
- borderRadius: 2,
461
- },
462
- floatTitle: {
463
- fontSize: 10,
464
- fontWeight: '700',
465
- color: KW.slate,
466
- letterSpacing: 1.2,
467
- },
468
- floatContent: {
469
- paddingHorizontal: 16,
470
- paddingBottom: 16,
471
- gap: 8,
472
- },
473
- });