@ziztechnology/dial-library 0.0.10 → 0.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.
package/dist/index.mjs CHANGED
@@ -45,40 +45,55 @@ const dialSdkDebugWarn = (moduleName, scope, phase, data) => {
45
45
  //#endregion
46
46
  //#region src/sensor/check_status.ts
47
47
  const DEBUG_MODULE$5 = "check_status.ts";
48
+ const DEVICE_RIGHT = {
49
+ x: 1,
50
+ y: 0,
51
+ z: 0
52
+ };
53
+ const ZERO_VECTOR$1 = {
54
+ x: 0,
55
+ y: 0,
56
+ z: 0
57
+ };
48
58
  const DEFAULT_DRIVING_STATUS_TUNING = Object.freeze({
49
- version: "driving-status-v1",
59
+ version: "driving-status-calibrated-2026-07",
50
60
  maxSampleAgeMs: 1e3,
51
- rapidLongitudinalThreshold: 3,
52
- longitudinalThreshold: 1,
53
- turnYawThreshold: .5,
54
- turnLateralThreshold: 2.5,
61
+ gravityFilterTimeConstantMs: 1e3,
62
+ biasFilterTimeConstantMs: 1e4,
63
+ vehicleRightMinimumNorm: .2,
55
64
  motionWindowMs: 1e3,
56
65
  motionMinimumSamples: 5,
57
- drivingMotionThreshold: .4,
58
- stoppedMotionThreshold: .25,
59
- stoppedGyroThreshold: .1,
60
- hysteresisRatio: .75,
61
- smoothingWindowSize: 3,
62
- confirmationSamples: Object.freeze({
63
- STOPPED: 5,
64
- STEADY_DRIVING: 3,
65
- ACCELERATION: 2,
66
- RAPID_ACCELERATION: 1,
67
- BRAKING: 2,
68
- SUDDEN_BRAKING: 1,
69
- LEFT_TURN: 2,
70
- RIGHT_TURN: 2
71
- }),
72
- minimumDisplayMs: Object.freeze({
73
- STOPPED: 1200,
74
- STEADY_DRIVING: 1200,
75
- ACCELERATION: 700,
76
- RAPID_ACCELERATION: 1200,
77
- BRAKING: 700,
78
- SUDDEN_BRAKING: 1200,
79
- LEFT_TURN: 800,
80
- RIGHT_TURN: 800
81
- })
66
+ stoppedMotionThreshold: .12,
67
+ drivingMotionThreshold: .18,
68
+ stoppedGyroThreshold: .025,
69
+ stoppedRawGyroThreshold: .065,
70
+ drivingGyroThreshold: .04,
71
+ drivingRawGyroThreshold: .08,
72
+ stoppedConfirmationMs: 5e3,
73
+ drivingConfirmationMs: 600,
74
+ longitudinalWindowMs: 800,
75
+ longitudinalMinimumSamples: 5,
76
+ longitudinalThreshold: .7,
77
+ longitudinalExitThreshold: .35,
78
+ longitudinalSignCoherence: .8,
79
+ longitudinalReleaseMs: 500,
80
+ rapidLongitudinalWindowMs: 600,
81
+ rapidLongitudinalMinimumSamples: 4,
82
+ rapidLongitudinalPeakThreshold: 2.2,
83
+ rapidLongitudinalMeanThreshold: 1.4,
84
+ rapidLongitudinalSignCoherence: .75,
85
+ rapidMinimumDisplayMs: 500,
86
+ verticalBumpRatio: 1.25,
87
+ longitudinalReversalWindowMs: 400,
88
+ turnWindowMs: 1500,
89
+ turnMinimumSamples: 4,
90
+ turnYawThreshold: .12,
91
+ turnConfirmationMs: 600,
92
+ turnIntegratedAngleThreshold: .12,
93
+ turnSignCoherence: .7,
94
+ turnExitYawThreshold: .04,
95
+ turnExitConfirmationMs: 700,
96
+ turnClosureWindowMs: 1500
82
97
  });
83
98
  const classifyDrivingSnapshot = (info, tuningOverrides) => {
84
99
  dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingSnapshot", "called", {
@@ -87,52 +102,91 @@ const classifyDrivingSnapshot = (info, tuningOverrides) => {
87
102
  });
88
103
  const tuning = resolveDrivingStatusTuning(tuningOverrides);
89
104
  const metrics = extractDrivingSnapshotMetrics(info, tuning);
90
- const result = metrics === null ? null : classifyDrivingMetrics(metrics, tuning);
91
- dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingSnapshot", "returned", {
92
- tuning,
105
+ if (metrics === null) {
106
+ dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingSnapshot", "returned", { result: null });
107
+ return null;
108
+ }
109
+ const { candidateStatus, rejectionReason } = classifyInstantaneousEvidence(metrics, tuning);
110
+ const result = {
111
+ capturedAtMs: info.capturedAtMs,
93
112
  metrics,
94
- result
95
- });
113
+ candidateStatus,
114
+ rejectionReason,
115
+ quality: {
116
+ frameValid: true,
117
+ maximumSampleAgeMs: Math.max(info.capturedAtMs - metrics.linearAccelerationSampledAtMs, info.capturedAtMs - metrics.gyroscopeSampledAtMs, info.capturedAtMs - metrics.gravitySampledAtMs)
118
+ }
119
+ };
120
+ dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingSnapshot", "returned", { result });
96
121
  return result;
97
122
  };
98
- const checkRunningStatus = classifyDrivingSnapshot;
99
123
  const resolveDrivingStatusTuning = (overrides = {}) => {
100
124
  dialSdkDebugWarn(DEBUG_MODULE$5, "resolveDrivingStatusTuning", "called", { overrides });
101
125
  const tuning = {
102
126
  ...DEFAULT_DRIVING_STATUS_TUNING,
103
- ...overrides,
104
- confirmationSamples: {
105
- ...DEFAULT_DRIVING_STATUS_TUNING.confirmationSamples,
106
- ...overrides.confirmationSamples
107
- },
108
- minimumDisplayMs: {
109
- ...DEFAULT_DRIVING_STATUS_TUNING.minimumDisplayMs,
110
- ...overrides.minimumDisplayMs
111
- }
127
+ ...overrides
112
128
  };
113
- assertPositiveFinite("maxSampleAgeMs", tuning.maxSampleAgeMs);
114
- assertPositiveFinite("rapidLongitudinalThreshold", tuning.rapidLongitudinalThreshold);
115
- assertPositiveFinite("longitudinalThreshold", tuning.longitudinalThreshold);
116
- assertPositiveFinite("turnYawThreshold", tuning.turnYawThreshold);
117
- assertPositiveFinite("turnLateralThreshold", tuning.turnLateralThreshold);
118
- assertPositiveFinite("motionWindowMs", tuning.motionWindowMs);
119
- if (!Number.isInteger(tuning.motionMinimumSamples) || tuning.motionMinimumSamples < 1) throw new RangeError("motionMinimumSamples must be a positive integer");
120
- assertPositiveFinite("drivingMotionThreshold", tuning.drivingMotionThreshold);
121
- assertPositiveFinite("stoppedMotionThreshold", tuning.stoppedMotionThreshold);
122
- assertPositiveFinite("stoppedGyroThreshold", tuning.stoppedGyroThreshold);
123
- if (tuning.drivingMotionThreshold <= tuning.stoppedMotionThreshold) throw new RangeError("drivingMotionThreshold must be greater than stoppedMotionThreshold");
124
- if (!Number.isInteger(tuning.smoothingWindowSize) || tuning.smoothingWindowSize < 1) throw new RangeError("smoothingWindowSize must be a positive integer");
125
- if (tuning.rapidLongitudinalThreshold < tuning.longitudinalThreshold) throw new RangeError("rapidLongitudinalThreshold must be greater than or equal to longitudinalThreshold");
126
- if (!Number.isFinite(tuning.hysteresisRatio) || tuning.hysteresisRatio <= 0 || tuning.hysteresisRatio >= 1) throw new RangeError("hysteresisRatio must be greater than 0 and less than 1");
127
- for (const [status, value] of Object.entries(tuning.confirmationSamples)) if (!Number.isInteger(value) || value < 1) throw new RangeError(`confirmationSamples.${status} must be a positive integer`);
128
- for (const [status, value] of Object.entries(tuning.minimumDisplayMs)) if (!Number.isFinite(value) || value < 0) throw new RangeError(`minimumDisplayMs.${status} must be a non-negative finite number`);
129
+ for (const field of [
130
+ "maxSampleAgeMs",
131
+ "gravityFilterTimeConstantMs",
132
+ "biasFilterTimeConstantMs",
133
+ "vehicleRightMinimumNorm",
134
+ "motionWindowMs",
135
+ "stoppedMotionThreshold",
136
+ "drivingMotionThreshold",
137
+ "stoppedGyroThreshold",
138
+ "stoppedRawGyroThreshold",
139
+ "drivingGyroThreshold",
140
+ "drivingRawGyroThreshold",
141
+ "stoppedConfirmationMs",
142
+ "drivingConfirmationMs",
143
+ "longitudinalWindowMs",
144
+ "longitudinalThreshold",
145
+ "longitudinalExitThreshold",
146
+ "longitudinalReleaseMs",
147
+ "rapidLongitudinalWindowMs",
148
+ "rapidLongitudinalPeakThreshold",
149
+ "rapidLongitudinalMeanThreshold",
150
+ "rapidMinimumDisplayMs",
151
+ "verticalBumpRatio",
152
+ "longitudinalReversalWindowMs",
153
+ "turnWindowMs",
154
+ "turnYawThreshold",
155
+ "turnConfirmationMs",
156
+ "turnIntegratedAngleThreshold",
157
+ "turnExitYawThreshold",
158
+ "turnExitConfirmationMs",
159
+ "turnClosureWindowMs"
160
+ ]) assertPositiveFinite(field, tuning[field]);
161
+ for (const field of [
162
+ "motionMinimumSamples",
163
+ "longitudinalMinimumSamples",
164
+ "rapidLongitudinalMinimumSamples",
165
+ "turnMinimumSamples"
166
+ ]) if (!Number.isInteger(tuning[field]) || tuning[field] < 1) throwLoggedRangeError("resolveDrivingStatusTuning", `${field} must be a positive integer`);
167
+ for (const field of [
168
+ "longitudinalSignCoherence",
169
+ "rapidLongitudinalSignCoherence",
170
+ "turnSignCoherence"
171
+ ]) {
172
+ const value = tuning[field];
173
+ if (!Number.isFinite(value) || value <= .5 || value > 1) throwLoggedRangeError("resolveDrivingStatusTuning", `${field} must be greater than 0.5 and less than or equal to 1`);
174
+ }
175
+ if (tuning.drivingMotionThreshold <= tuning.stoppedMotionThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "drivingMotionThreshold must be greater than stoppedMotionThreshold");
176
+ if (tuning.drivingGyroThreshold <= tuning.stoppedGyroThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "drivingGyroThreshold must be greater than stoppedGyroThreshold");
177
+ if (tuning.drivingRawGyroThreshold <= tuning.stoppedRawGyroThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "drivingRawGyroThreshold must be greater than stoppedRawGyroThreshold");
178
+ if (tuning.longitudinalThreshold <= tuning.longitudinalExitThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "longitudinalThreshold must be greater than longitudinalExitThreshold");
179
+ if (tuning.rapidLongitudinalMeanThreshold <= tuning.longitudinalThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "rapidLongitudinalMeanThreshold must be greater than longitudinalThreshold");
180
+ if (tuning.rapidLongitudinalPeakThreshold < tuning.rapidLongitudinalMeanThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "rapidLongitudinalPeakThreshold must be greater than or equal to rapidLongitudinalMeanThreshold");
181
+ if (tuning.turnYawThreshold <= tuning.turnExitYawThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "turnYawThreshold must be greater than turnExitYawThreshold");
129
182
  dialSdkDebugWarn(DEBUG_MODULE$5, "resolveDrivingStatusTuning", "returned", { tuning });
130
183
  return tuning;
131
184
  };
132
- const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUNING) => {
185
+ const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUNING, correction = {}) => {
133
186
  dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "called", {
134
187
  info,
135
- tuning
188
+ tuning,
189
+ correction
136
190
  });
137
191
  if (!info || !Number.isFinite(info.capturedAtMs)) {
138
192
  dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "returned invalid capture time", { info });
@@ -140,149 +194,148 @@ const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUN
140
194
  }
141
195
  const linearAcceleration = availableFreshVector(info.linearAcceleration, info.capturedAtMs, tuning.maxSampleAgeMs);
142
196
  const gyroscope = availableFreshVector(info.gyroscope, info.capturedAtMs, tuning.maxSampleAgeMs);
143
- if (linearAcceleration === null || gyroscope === null) {
197
+ const gravityMetric = availableFreshVector(info.gravity, info.capturedAtMs, tuning.maxSampleAgeMs);
198
+ if (linearAcceleration === null || gyroscope === null || gravityMetric === null) {
144
199
  dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "returned unavailable vectors", {
145
200
  linearAcceleration,
146
- gyroscope
201
+ gyroscope,
202
+ gravity: gravityMetric
147
203
  });
148
204
  return null;
149
205
  }
150
- const longitudinal = -linearAcceleration.z;
151
- const lateral = linearAcceleration.x;
152
- const yaw = gyroscope.y;
153
- const motionIntensity = info.motionIntensity === void 0 ? vectorMagnitude(linearAcceleration) : availableFreshNumber(info.motionIntensity, info.capturedAtMs, tuning.maxSampleAgeMs);
154
- if (motionIntensity === null) {
155
- dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "returned unavailable motion intensity", { motionIntensity: info.motionIntensity });
206
+ const gravity = correction.filteredGravity ?? gravityMetric.value;
207
+ const frame = buildVehicleFrame(gravity, tuning.vehicleRightMinimumNorm);
208
+ if (frame === null) {
209
+ dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "returned invalid vehicle frame", {
210
+ gravity,
211
+ vehicleRightMinimumNorm: tuning.vehicleRightMinimumNorm
212
+ });
156
213
  return null;
157
214
  }
215
+ const correctedLinear = subtract(linearAcceleration.value, correction.linearAccelerationBias ?? ZERO_VECTOR$1);
216
+ const correctedGyroscope = subtract(gyroscope.value, correction.gyroscopeBias ?? ZERO_VECTOR$1);
158
217
  const result = {
159
218
  capturedAtMs: info.capturedAtMs,
160
- longitudinal,
161
- lateral,
162
- yaw,
163
- motionIntensity,
164
- gyroMagnitude: vectorMagnitude(gyroscope)
219
+ linearAccelerationSampledAtMs: linearAcceleration.sampledAtMs,
220
+ gyroscopeSampledAtMs: gyroscope.sampledAtMs,
221
+ gravitySampledAtMs: gravityMetric.sampledAtMs,
222
+ longitudinal: dot(correctedLinear, frame.forward),
223
+ lateral: dot(correctedLinear, frame.right),
224
+ vertical: dot(correctedLinear, frame.up),
225
+ yaw: dot(correctedGyroscope, frame.up),
226
+ gyroMagnitude: magnitude(correctedGyroscope),
227
+ rawGyroMagnitude: magnitude(gyroscope.value),
228
+ rawLinearAcceleration: linearAcceleration.value,
229
+ rawGyroscope: gyroscope.value,
230
+ frame
165
231
  };
166
232
  dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "returned", { result });
167
233
  return result;
168
234
  };
169
- const classifyDrivingMetrics = (metrics, tuning = DEFAULT_DRIVING_STATUS_TUNING) => {
170
- dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingMetrics", "called", {
171
- metrics,
172
- tuning
235
+ const buildVehicleFrame = (gravity, rightMinimumNorm = DEFAULT_DRIVING_STATUS_TUNING.vehicleRightMinimumNorm) => {
236
+ dialSdkDebugWarn(DEBUG_MODULE$5, "buildVehicleFrame", "called", {
237
+ gravity,
238
+ rightMinimumNorm
173
239
  });
174
- const { longitudinal, lateral, yaw, motionIntensity, gyroMagnitude } = metrics;
175
- if (longitudinal <= -tuning.rapidLongitudinalThreshold) {
176
- dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingMetrics", "returned", { result: "SUDDEN_BRAKING" });
177
- return "SUDDEN_BRAKING";
178
- }
179
- if (longitudinal >= tuning.rapidLongitudinalThreshold) {
180
- dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingMetrics", "returned", { result: "RAPID_ACCELERATION" });
181
- return "RAPID_ACCELERATION";
182
- }
183
- if (Math.abs(yaw) >= tuning.turnYawThreshold) {
184
- const result = yaw > 0 ? "LEFT_TURN" : "RIGHT_TURN";
185
- dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingMetrics", "returned", {
186
- result,
187
- basis: "yaw"
188
- });
189
- return result;
190
- }
191
- if (Math.abs(lateral) >= tuning.turnLateralThreshold) {
192
- const result = lateral < 0 ? "LEFT_TURN" : "RIGHT_TURN";
193
- dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingMetrics", "returned", {
194
- result,
195
- basis: "lateral"
196
- });
197
- return result;
240
+ const gravityDirection = normalize(gravity);
241
+ if (gravityDirection === null) {
242
+ dialSdkDebugWarn(DEBUG_MODULE$5, "buildVehicleFrame", "returned invalid gravity", { result: null });
243
+ return null;
198
244
  }
199
- if (longitudinal <= -tuning.longitudinalThreshold) {
200
- dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingMetrics", "returned", { result: "BRAKING" });
201
- return "BRAKING";
245
+ const up = gravityDirection;
246
+ const projectedRight = subtract(DEVICE_RIGHT, scale(up, dot(DEVICE_RIGHT, up)));
247
+ if (magnitude(projectedRight) < rightMinimumNorm) {
248
+ dialSdkDebugWarn(DEBUG_MODULE$5, "buildVehicleFrame", "returned degenerate right axis", { result: null });
249
+ return null;
202
250
  }
203
- if (longitudinal >= tuning.longitudinalThreshold) {
204
- dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingMetrics", "returned", { result: "ACCELERATION" });
205
- return "ACCELERATION";
251
+ const right = normalize(projectedRight);
252
+ if (right === null) {
253
+ dialSdkDebugWarn(DEBUG_MODULE$5, "buildVehicleFrame", "returned invalid right axis", { result: null });
254
+ return null;
206
255
  }
207
- if (motionIntensity < tuning.drivingMotionThreshold && gyroMagnitude <= tuning.stoppedGyroThreshold) {
208
- dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingMetrics", "returned", { result: "STOPPED" });
209
- return "STOPPED";
256
+ const forward = normalize(cross(right, up));
257
+ const result = forward === null ? null : {
258
+ up,
259
+ right,
260
+ forward
261
+ };
262
+ dialSdkDebugWarn(DEBUG_MODULE$5, "buildVehicleFrame", "returned", { result });
263
+ return result;
264
+ };
265
+ const classifyInstantaneousEvidence = (metrics, tuning) => {
266
+ const longitudinalMagnitude = Math.abs(metrics.longitudinal);
267
+ if (longitudinalMagnitude >= tuning.rapidLongitudinalPeakThreshold) {
268
+ if (Math.abs(metrics.vertical) > longitudinalMagnitude * tuning.verticalBumpRatio) return {
269
+ candidateStatus: null,
270
+ rejectionReason: "vertical_bump"
271
+ };
272
+ return {
273
+ candidateStatus: metrics.longitudinal > 0 ? "RAPID_ACCELERATION" : "SUDDEN_BRAKING",
274
+ rejectionReason: null
275
+ };
210
276
  }
211
- dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingMetrics", "returned", { result: "STEADY_DRIVING" });
212
- return "STEADY_DRIVING";
277
+ if (Math.abs(metrics.yaw) >= tuning.turnYawThreshold) return {
278
+ candidateStatus: metrics.yaw > 0 ? "LEFT_TURN" : "RIGHT_TURN",
279
+ rejectionReason: null
280
+ };
281
+ if (longitudinalMagnitude >= tuning.longitudinalThreshold) return {
282
+ candidateStatus: metrics.longitudinal > 0 ? "ACCELERATION" : "BRAKING",
283
+ rejectionReason: null
284
+ };
285
+ return {
286
+ candidateStatus: null,
287
+ rejectionReason: null
288
+ };
213
289
  };
214
290
  const availableFreshVector = (metric, capturedAtMs, maxSampleAgeMs) => {
215
- dialSdkDebugWarn(DEBUG_MODULE$5, "availableFreshVector", "called", {
216
- metric,
217
- capturedAtMs,
218
- maxSampleAgeMs
219
- });
220
- if (!metric?.available || !isFresh(metric.sampledAtMs, capturedAtMs, maxSampleAgeMs)) {
221
- dialSdkDebugWarn(DEBUG_MODULE$5, "availableFreshVector", "returned", { result: null });
222
- return null;
223
- }
291
+ if (!metric?.available || !isFresh(metric.sampledAtMs, capturedAtMs, maxSampleAgeMs)) return null;
224
292
  const { x, y, z } = metric.value;
225
- const result = [
293
+ return [
226
294
  x,
227
295
  y,
228
296
  z
229
- ].every(Number.isFinite) ? metric.value : null;
230
- dialSdkDebugWarn(DEBUG_MODULE$5, "availableFreshVector", "returned", { result });
231
- return result;
232
- };
233
- const availableFreshNumber = (metric, capturedAtMs, maxSampleAgeMs) => {
234
- dialSdkDebugWarn(DEBUG_MODULE$5, "availableFreshNumber", "called", {
235
- metric,
236
- capturedAtMs,
237
- maxSampleAgeMs
238
- });
239
- if (!metric?.available || !isFresh(metric.sampledAtMs, capturedAtMs, maxSampleAgeMs)) {
240
- dialSdkDebugWarn(DEBUG_MODULE$5, "availableFreshNumber", "returned", { result: null });
241
- return null;
242
- }
243
- const result = Number.isFinite(metric.value) && metric.value >= 0 ? metric.value : null;
244
- dialSdkDebugWarn(DEBUG_MODULE$5, "availableFreshNumber", "returned", { result });
245
- return result;
297
+ ].every(Number.isFinite) ? {
298
+ value: metric.value,
299
+ sampledAtMs: metric.sampledAtMs
300
+ } : null;
246
301
  };
247
302
  const isFresh = (sampledAtMs, capturedAtMs, maxSampleAgeMs) => {
248
- dialSdkDebugWarn(DEBUG_MODULE$5, "isFresh", "called", {
249
- sampledAtMs,
250
- capturedAtMs,
251
- maxSampleAgeMs
252
- });
253
- if (sampledAtMs === null || !Number.isFinite(sampledAtMs)) {
254
- dialSdkDebugWarn(DEBUG_MODULE$5, "isFresh", "returned", { result: false });
255
- return false;
256
- }
303
+ if (sampledAtMs === null || !Number.isFinite(sampledAtMs)) return false;
257
304
  const age = capturedAtMs - sampledAtMs;
258
- const result = age >= 0 && age <= maxSampleAgeMs;
259
- dialSdkDebugWarn(DEBUG_MODULE$5, "isFresh", "returned", {
260
- age,
261
- result
262
- });
263
- return result;
305
+ return age >= 0 && age <= maxSampleAgeMs;
264
306
  };
265
- const vectorMagnitude = ({ x, y, z }) => {
266
- const result = Math.hypot(x, y, z);
267
- dialSdkDebugWarn(DEBUG_MODULE$5, "vectorMagnitude", "returned", {
268
- x,
269
- y,
270
- z,
271
- result
272
- });
273
- return result;
307
+ const dot = (left, right) => left.x * right.x + left.y * right.y + left.z * right.z;
308
+ const cross = (left, right) => ({
309
+ x: left.y * right.z - left.z * right.y,
310
+ y: left.z * right.x - left.x * right.z,
311
+ z: left.x * right.y - left.y * right.x
312
+ });
313
+ const subtract = (left, right) => ({
314
+ x: left.x - right.x,
315
+ y: left.y - right.y,
316
+ z: left.z - right.z
317
+ });
318
+ const scale = (value, factor) => ({
319
+ x: value.x * factor,
320
+ y: value.y * factor,
321
+ z: value.z * factor
322
+ });
323
+ const magnitude = (value) => Math.hypot(value.x, value.y, value.z);
324
+ const normalize = (value) => {
325
+ const length = magnitude(value);
326
+ return Number.isFinite(length) && length > Number.EPSILON ? scale(value, 1 / length) : null;
274
327
  };
275
328
  const assertPositiveFinite = (name, value) => {
276
- dialSdkDebugWarn(DEBUG_MODULE$5, "assertPositiveFinite", "called", {
277
- name,
278
- value
279
- });
280
329
  if (!Number.isFinite(value) || value <= 0) {
281
330
  const error = /* @__PURE__ */ new RangeError(`${name} must be a positive finite number`);
282
331
  dialSdkDebugWarn(DEBUG_MODULE$5, "assertPositiveFinite", "threw", error);
283
332
  throw error;
284
333
  }
285
- dialSdkDebugWarn(DEBUG_MODULE$5, "assertPositiveFinite", "returned");
334
+ };
335
+ const throwLoggedRangeError = (scope, message) => {
336
+ const error = new RangeError(message);
337
+ dialSdkDebugWarn(DEBUG_MODULE$5, scope, "threw", error);
338
+ throw error;
286
339
  };
287
340
  //#endregion
288
341
  //#region src/sensor/unified_info.ts
@@ -1272,6 +1325,11 @@ const safeInvoke = (callback) => {
1272
1325
  //#endregion
1273
1326
  //#region src/sensor/driving_status_controller.ts
1274
1327
  const DEBUG_MODULE$1 = "driving_status_controller.ts";
1328
+ const ZERO_VECTOR = {
1329
+ x: 0,
1330
+ y: 0,
1331
+ z: 0
1332
+ };
1275
1333
  const createDrivingStatusController = (options = {}) => {
1276
1334
  dialSdkDebugWarn(DEBUG_MODULE$1, "createDrivingStatusController", "called", { options });
1277
1335
  const result = new DefaultDrivingStatusController(options);
@@ -1281,14 +1339,30 @@ const createDrivingStatusController = (options = {}) => {
1281
1339
  var DefaultDrivingStatusController = class {
1282
1340
  constructor(options) {
1283
1341
  this.listeners = /* @__PURE__ */ new Set();
1284
- this.metricWindow = [];
1342
+ this.longitudinalWindow = [];
1343
+ this.yawWindow = [];
1285
1344
  this.motionWindow = [];
1286
1345
  this.candidate = null;
1287
- this.candidateConfirmationCount = 0;
1346
+ this.candidateSinceMs = null;
1288
1347
  this.lastAvailableAtMs = null;
1289
1348
  this.unavailableSinceMs = null;
1290
1349
  this.fallbackApplied = false;
1291
- this.heldCandidate = null;
1350
+ this.filteredGravity = null;
1351
+ this.lastGravitySampledAtMs = null;
1352
+ this.linearAccelerationBias = { ...ZERO_VECTOR };
1353
+ this.gyroscopeBias = { ...ZERO_VECTOR };
1354
+ this.biasCalibrated = false;
1355
+ this.lastBiasSampledAtMs = null;
1356
+ this.stoppedConditionSinceMs = null;
1357
+ this.drivingConditionSinceMs = null;
1358
+ this.stationaryConfirmed = false;
1359
+ this.urgentStatus = null;
1360
+ this.urgentStartedAtMs = null;
1361
+ this.normalLongitudinalStatus = null;
1362
+ this.longitudinalClearSinceMs = null;
1363
+ this.turnStatus = null;
1364
+ this.turnQuietSinceMs = null;
1365
+ this.oppositeTurnSinceMs = null;
1292
1366
  this.requestedRunning = false;
1293
1367
  this.polling = false;
1294
1368
  this.lifecycleSuspended = false;
@@ -1297,12 +1371,12 @@ var DefaultDrivingStatusController = class {
1297
1371
  this.pendingRead = null;
1298
1372
  this.unsubscribePageLifecycle = null;
1299
1373
  this.handleLifecyclePause = () => {
1300
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecyclePause", "called", {
1301
- requestedRunning: this.requestedRunning,
1302
- lifecycleSuspended: this.lifecycleSuspended
1303
- });
1374
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecyclePause", "called");
1304
1375
  if (!this.requestedRunning || this.lifecycleSuspended) {
1305
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecyclePause", "ignored");
1376
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecyclePause", "ignored", {
1377
+ requestedRunning: this.requestedRunning,
1378
+ lifecycleSuspended: this.lifecycleSuspended
1379
+ });
1306
1380
  return;
1307
1381
  }
1308
1382
  this.lifecycleSuspended = true;
@@ -1311,13 +1385,15 @@ var DefaultDrivingStatusController = class {
1311
1385
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecyclePause", "returned");
1312
1386
  };
1313
1387
  this.handleLifecycleResume = () => {
1314
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecycleResume", "called", {
1315
- requestedRunning: this.requestedRunning,
1316
- lifecycleSuspended: this.lifecycleSuspended,
1317
- destroyed: this.destroyed
1318
- });
1319
- if (!this.requestedRunning || !this.lifecycleSuspended || this.destroyed || isDrivingPageHidden()) {
1320
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecycleResume", "ignored");
1388
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecycleResume", "called");
1389
+ const pageHidden = this.requestedRunning && this.lifecycleSuspended && !this.destroyed ? isDrivingPageHidden() : false;
1390
+ if (!this.requestedRunning || !this.lifecycleSuspended || this.destroyed || pageHidden) {
1391
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecycleResume", "ignored", {
1392
+ requestedRunning: this.requestedRunning,
1393
+ lifecycleSuspended: this.lifecycleSuspended,
1394
+ destroyed: this.destroyed,
1395
+ pageHidden
1396
+ });
1321
1397
  return;
1322
1398
  }
1323
1399
  this.lifecycleSuspended = false;
@@ -1333,18 +1409,15 @@ var DefaultDrivingStatusController = class {
1333
1409
  this.onDiagnostic = options.onDiagnostic;
1334
1410
  this.managePageLifecycle = options.managePageLifecycle ?? true;
1335
1411
  const initialStatus = options.initialStatus ?? "STOPPED";
1336
- if (!CAR_RUNNING_STATUSES.includes(initialStatus)) throw new TypeError(`Unsupported initialStatus: ${initialStatus}`);
1412
+ if (!CAR_RUNNING_STATUSES.includes(initialStatus)) {
1413
+ const error = /* @__PURE__ */ new TypeError(`Unsupported initialStatus: ${initialStatus}`);
1414
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.constructor", "threw", error);
1415
+ throw error;
1416
+ }
1337
1417
  this.status = initialStatus;
1338
1418
  this.baseStatus = isBaseStatus(initialStatus) ? initialStatus : "STOPPED";
1339
1419
  this.statusCommittedAtMs = this.clock.now();
1340
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.constructor", "created", {
1341
- initialStatus,
1342
- baseStatus: this.baseStatus,
1343
- pollIntervalMs: this.pollIntervalMs,
1344
- unavailableFallbackMs: this.unavailableFallbackMs,
1345
- tuning: this.tuning,
1346
- managePageLifecycle: this.managePageLifecycle
1347
- });
1420
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.constructor", "created", this.getSnapshot());
1348
1421
  }
1349
1422
  start() {
1350
1423
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.start", "called", this.getSnapshot());
@@ -1389,16 +1462,17 @@ var DefaultDrivingStatusController = class {
1389
1462
  }
1390
1463
  this.destroyed = true;
1391
1464
  this.listeners.clear();
1392
- this.metricWindow.length = 0;
1393
- this.motionWindow.length = 0;
1465
+ this.resetTransientRecognition();
1394
1466
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.destroy", "returned", this.getSnapshot());
1395
1467
  }
1396
1468
  getSnapshot() {
1397
1469
  const result = {
1398
1470
  lifecycle: this.destroyed ? "destroyed" : this.polling ? "running" : "stopped",
1399
1471
  status: this.status,
1472
+ baseStatus: this.baseStatus,
1473
+ activeTurn: this.turnStatus,
1400
1474
  candidate: this.candidate,
1401
- candidateConfirmationCount: this.candidateConfirmationCount,
1475
+ candidateSinceMs: this.candidateSinceMs,
1402
1476
  statusCommittedAtMs: this.statusCommittedAtMs,
1403
1477
  lastAvailableAtMs: this.lastAvailableAtMs,
1404
1478
  unavailableSinceMs: this.unavailableSinceMs,
@@ -1409,16 +1483,16 @@ var DefaultDrivingStatusController = class {
1409
1483
  }
1410
1484
  subscribe(listener) {
1411
1485
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.subscribe", "called", {
1412
- destroyed: this.destroyed,
1413
- listenerCount: this.listeners.size
1486
+ listener,
1487
+ destroyed: this.destroyed
1414
1488
  });
1415
1489
  if (this.destroyed) {
1416
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.subscribe", "returned noop");
1490
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.subscribe", "ignored destroyed controller");
1417
1491
  return noop;
1418
1492
  }
1419
1493
  this.listeners.add(listener);
1494
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.subscribe", "returned", { listenerCount: this.listeners.size });
1420
1495
  let subscribed = true;
1421
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.subscribe", "subscribed", { listenerCount: this.listeners.size });
1422
1496
  return () => {
1423
1497
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.unsubscribe", "called", { subscribed });
1424
1498
  if (!subscribed) {
@@ -1431,15 +1505,14 @@ var DefaultDrivingStatusController = class {
1431
1505
  };
1432
1506
  }
1433
1507
  activatePolling() {
1434
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.activatePolling", "called", {
1435
- destroyed: this.destroyed,
1436
- polling: this.polling,
1437
- requestedRunning: this.requestedRunning,
1438
- lifecycleSuspended: this.lifecycleSuspended,
1439
- generation: this.generation
1440
- });
1508
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.activatePolling", "called");
1441
1509
  if (this.destroyed || this.polling || !this.requestedRunning || this.lifecycleSuspended) {
1442
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.activatePolling", "ignored");
1510
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.activatePolling", "ignored", {
1511
+ destroyed: this.destroyed,
1512
+ polling: this.polling,
1513
+ requestedRunning: this.requestedRunning,
1514
+ lifecycleSuspended: this.lifecycleSuspended
1515
+ });
1443
1516
  return;
1444
1517
  }
1445
1518
  this.polling = true;
@@ -1449,14 +1522,10 @@ var DefaultDrivingStatusController = class {
1449
1522
  }
1450
1523
  deactivatePolling() {
1451
1524
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.deactivatePolling", "called", {
1452
- polling: this.polling,
1453
- timer: this.timer,
1525
+ generation: this.generation,
1526
+ hasTimer: this.timer !== void 0,
1454
1527
  hasPendingRead: this.pendingRead !== null
1455
1528
  });
1456
- if (!this.polling && this.timer === void 0 && this.pendingRead === null) {
1457
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.deactivatePolling", "ignored");
1458
- return;
1459
- }
1460
1529
  this.polling = false;
1461
1530
  this.generation += 1;
1462
1531
  if (this.timer !== void 0) {
@@ -1468,72 +1537,86 @@ var DefaultDrivingStatusController = class {
1468
1537
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.deactivatePolling", "returned", { generation: this.generation });
1469
1538
  }
1470
1539
  schedulePoll(delayMs, generation) {
1471
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.schedulePoll", "called", {
1472
- delayMs,
1473
- generation,
1474
- currentGeneration: this.generation,
1475
- polling: this.polling,
1476
- timer: this.timer
1477
- });
1478
- if (!this.polling || generation !== this.generation || this.timer !== void 0) {
1479
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.schedulePoll", "ignored");
1480
- return;
1481
- }
1540
+ if (!this.polling || generation !== this.generation || this.timer !== void 0) return;
1482
1541
  this.timer = this.clock.setTimeout(() => {
1483
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.schedulePoll.callback", "called", { generation });
1484
1542
  this.timer = void 0;
1485
1543
  this.poll(generation);
1486
1544
  }, delayMs);
1487
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.schedulePoll", "scheduled", {
1488
- delayMs,
1489
- generation,
1490
- timer: this.timer
1491
- });
1492
1545
  }
1493
1546
  async poll(generation) {
1494
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "called", {
1495
- generation,
1496
- currentGeneration: this.generation,
1497
- polling: this.polling,
1498
- hasPendingRead: this.pendingRead !== null
1499
- });
1547
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "called", { generation });
1500
1548
  if (!this.polling || generation !== this.generation || this.pendingRead !== null) {
1501
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "ignored");
1549
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "ignored", {
1550
+ polling: this.polling,
1551
+ generation,
1552
+ currentGeneration: this.generation,
1553
+ hasPendingRead: this.pendingRead !== null
1554
+ });
1502
1555
  return;
1503
1556
  }
1504
1557
  const pendingRead = new AbortController();
1505
1558
  this.pendingRead = pendingRead;
1506
1559
  try {
1507
1560
  const payload = await this.sensorProvider(pendingRead.signal);
1508
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "provider resolved", { payload });
1509
1561
  if (!this.polling || generation !== this.generation || pendingRead.signal.aborted) {
1510
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "discarded provider result");
1562
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "discarded provider result", {
1563
+ generation,
1564
+ currentGeneration: this.generation,
1565
+ polling: this.polling,
1566
+ aborted: pendingRead.signal.aborted
1567
+ });
1511
1568
  return;
1512
1569
  }
1513
- const metrics = extractDrivingSnapshotMetrics(payload, this.tuning);
1514
- const accelerometerSample = extractFreshAccelerometerSample(payload, this.tuning.maxSampleAgeMs);
1515
- if (metrics === null || accelerometerSample === null) this.processUnavailable("invalid_or_stale_snapshot");
1516
- else this.processAvailable(metrics, accelerometerSample);
1570
+ const gravity = this.updateFilteredGravity(payload);
1571
+ const metrics = gravity === null ? null : extractDrivingSnapshotMetrics(payload, this.tuning, {
1572
+ filteredGravity: gravity,
1573
+ linearAccelerationBias: this.linearAccelerationBias,
1574
+ gyroscopeBias: this.gyroscopeBias
1575
+ });
1576
+ const accelerometerSample = extractFreshVectorMetric(payload.accelerometer, payload.capturedAtMs, this.tuning.maxSampleAgeMs);
1577
+ if (metrics === null || accelerometerSample === null) {
1578
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "invalid or stale snapshot", {
1579
+ metrics,
1580
+ accelerometerSample
1581
+ });
1582
+ this.processUnavailable("invalid_or_stale_snapshot");
1583
+ } else {
1584
+ this.processAvailable(metrics, accelerometerSample);
1585
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "processed sample", { capturedAtMs: metrics.capturedAtMs });
1586
+ }
1517
1587
  } catch (error) {
1518
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "provider rejected", error);
1519
1588
  if (!this.polling || generation !== this.generation || pendingRead.signal.aborted || isAbortError$1(error)) {
1520
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "ignored rejection");
1589
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "ignored provider error", { error });
1521
1590
  return;
1522
1591
  }
1592
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "caught provider error", { error });
1523
1593
  this.processUnavailable("provider_error");
1524
1594
  } finally {
1525
1595
  if (this.pendingRead === pendingRead) this.pendingRead = null;
1526
1596
  if (this.polling && generation === this.generation) this.schedulePoll(this.pollIntervalMs, generation);
1527
1597
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "completed", {
1528
1598
  generation,
1529
- polling: this.polling,
1530
- currentGeneration: this.generation
1599
+ polling: this.polling
1531
1600
  });
1532
1601
  }
1533
1602
  }
1534
- processAvailable(rawMetrics, accelerometerSample) {
1603
+ updateFilteredGravity(info) {
1604
+ const sample = extractFreshVectorMetric(info.gravity, info.capturedAtMs, this.tuning.maxSampleAgeMs);
1605
+ if (sample === null) return null;
1606
+ if (sample.sampledAtMs === this.lastGravitySampledAtMs) return this.filteredGravity;
1607
+ if (this.filteredGravity === null || this.lastGravitySampledAtMs === null) this.filteredGravity = { ...sample.value };
1608
+ else {
1609
+ if (sample.sampledAtMs < this.lastGravitySampledAtMs) return this.filteredGravity;
1610
+ const elapsedMs = Math.max(0, sample.sampledAtMs - this.lastGravitySampledAtMs);
1611
+ const alpha = 1 - Math.exp(-elapsedMs / this.tuning.gravityFilterTimeConstantMs);
1612
+ this.filteredGravity = lerpVector(this.filteredGravity, sample.value, alpha);
1613
+ }
1614
+ this.lastGravitySampledAtMs = sample.sampledAtMs;
1615
+ return this.filteredGravity;
1616
+ }
1617
+ processAvailable(metrics, accelerometerSample) {
1535
1618
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processAvailable", "called", {
1536
- rawMetrics,
1619
+ metrics,
1537
1620
  accelerometerSample
1538
1621
  });
1539
1622
  const now = this.clock.now();
@@ -1543,56 +1626,156 @@ var DefaultDrivingStatusController = class {
1543
1626
  this.emitDiagnostic({
1544
1627
  type: "sample_available",
1545
1628
  atMs: now,
1546
- capturedAtMs: rawMetrics.capturedAtMs,
1629
+ capturedAtMs: metrics.capturedAtMs,
1547
1630
  tuningVersion: this.tuning.version
1548
1631
  });
1549
- this.metricWindow.push(rawMetrics);
1550
- while (this.metricWindow.length > Math.floor(this.tuning.smoothingWindowSize)) this.metricWindow.shift();
1551
- const smoothedMetrics = averageMetrics(this.metricWindow);
1552
- const motionIntensity = this.recordMotionSample(accelerometerSample, rawMetrics.capturedAtMs);
1553
- const nextCandidate = classifyControllerCandidate(rawMetrics, smoothedMetrics, motionIntensity, this.status, this.baseStatus, this.tuning);
1554
- this.processCandidate(nextCandidate, now);
1555
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processAvailable", "returned", {
1556
- rawMetrics,
1557
- smoothedMetrics,
1558
- motionIntensity,
1559
- nextCandidate,
1560
- snapshot: this.getSnapshot()
1632
+ const addedLongitudinal = pushUniqueMetric(this.longitudinalWindow, metrics, "linearAccelerationSampledAtMs", Math.max(this.tuning.longitudinalWindowMs, this.tuning.rapidLongitudinalWindowMs));
1633
+ const addedYaw = pushUniqueMetric(this.yawWindow, metrics, "gyroscopeSampledAtMs", Math.max(this.tuning.turnWindowMs, this.tuning.turnClosureWindowMs));
1634
+ const addedMotion = pushUniqueVector(this.motionWindow, accelerometerSample, this.tuning.motionWindowMs);
1635
+ if (!addedLongitudinal && !addedYaw && !addedMotion) {
1636
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processAvailable", "ignored duplicate sample");
1637
+ return;
1638
+ }
1639
+ const motionMad = calculateCombinedAxisMad(this.motionWindow, this.tuning.motionMinimumSamples);
1640
+ const baseCondition = this.updateBaseDetector(motionMad, metrics, accelerometerSample.sampledAtMs, addedMotion);
1641
+ const longitudinalEvidence = detectLongitudinalEvidence(this.longitudinalWindow, this.tuning);
1642
+ if (addedLongitudinal) this.updateLongitudinalDetector(longitudinalEvidence, metrics.linearAccelerationSampledAtMs);
1643
+ const turnEvidence = detectTurnEvidence(this.yawWindow, this.tuning);
1644
+ if (addedYaw) this.updateTurnDetector(turnEvidence);
1645
+ if (this.urgentStatus !== null || this.turnStatus !== null || this.normalLongitudinalStatus !== null) {
1646
+ this.baseStatus = "STEADY_DRIVING";
1647
+ this.stoppedConditionSinceMs = null;
1648
+ this.stationaryConfirmed = false;
1649
+ }
1650
+ if (this.stationaryConfirmed) this.updateBias(metrics);
1651
+ const pendingCandidate = instantaneousCandidate(metrics, this.tuning);
1652
+ this.setCandidate(pendingCandidate === this.status ? null : pendingCandidate, observationTime(metrics));
1653
+ this.emitDiagnostic({
1654
+ type: "detector_evidence",
1655
+ atMs: now,
1656
+ motionMad,
1657
+ baseCondition,
1658
+ rapidCandidate: longitudinalEvidence.rapidCandidate,
1659
+ longitudinalCandidate: longitudinalEvidence.normalCandidate,
1660
+ turnCandidate: turnEvidence.candidate,
1661
+ rejectionReason: longitudinalEvidence.rejectionReason,
1662
+ tuningVersion: this.tuning.version
1561
1663
  });
1664
+ const hasAction = this.urgentStatus !== null || this.turnStatus !== null || this.normalLongitudinalStatus !== null;
1665
+ if (motionMad !== null || hasAction) this.commitSelectedStatus(now);
1666
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processAvailable", "returned", {
1667
+ motionMad,
1668
+ baseCondition,
1669
+ status: this.status,
1670
+ baseStatus: this.baseStatus,
1671
+ urgentStatus: this.urgentStatus,
1672
+ turnStatus: this.turnStatus,
1673
+ normalLongitudinalStatus: this.normalLongitudinalStatus
1674
+ });
1675
+ }
1676
+ updateBaseDetector(motionMad, metrics, sampledAtMs, isNewMotionSample) {
1677
+ if (motionMad === null || !isNewMotionSample) return "warming_up";
1678
+ const gyroForStop = this.biasCalibrated ? metrics.gyroMagnitude : metrics.rawGyroMagnitude;
1679
+ const stoppedGyroThreshold = this.biasCalibrated ? this.tuning.stoppedGyroThreshold : this.tuning.stoppedRawGyroThreshold;
1680
+ const drivingGyroThreshold = this.biasCalibrated ? this.tuning.drivingGyroThreshold : this.tuning.drivingRawGyroThreshold;
1681
+ const stationary = motionMad <= this.tuning.stoppedMotionThreshold && gyroForStop <= stoppedGyroThreshold;
1682
+ const moving = motionMad >= this.tuning.drivingMotionThreshold || gyroForStop >= drivingGyroThreshold;
1683
+ if (stationary) {
1684
+ this.drivingConditionSinceMs = null;
1685
+ if (this.stoppedConditionSinceMs === null) this.stoppedConditionSinceMs = this.motionWindow[0]?.sampledAtMs ?? sampledAtMs;
1686
+ if (sampledAtMs - this.stoppedConditionSinceMs >= this.tuning.stoppedConfirmationMs) {
1687
+ this.baseStatus = "STOPPED";
1688
+ this.stationaryConfirmed = true;
1689
+ }
1690
+ return "stationary";
1691
+ }
1692
+ this.stoppedConditionSinceMs = null;
1693
+ this.stationaryConfirmed = false;
1694
+ if (moving) {
1695
+ if (this.drivingConditionSinceMs === null) this.drivingConditionSinceMs = this.motionWindow[0]?.sampledAtMs ?? sampledAtMs;
1696
+ if (sampledAtMs - this.drivingConditionSinceMs >= this.tuning.drivingConfirmationMs) this.baseStatus = "STEADY_DRIVING";
1697
+ return "moving";
1698
+ }
1699
+ this.drivingConditionSinceMs = null;
1700
+ return "hysteresis";
1701
+ }
1702
+ updateLongitudinalDetector(evidence, sampledAtMs) {
1703
+ if (evidence.rapidCandidate !== null) {
1704
+ if (this.urgentStatus !== evidence.rapidCandidate) {
1705
+ this.urgentStatus = evidence.rapidCandidate;
1706
+ this.urgentStartedAtMs = sampledAtMs;
1707
+ }
1708
+ } else if (this.urgentStatus !== null && this.urgentStartedAtMs !== null && sampledAtMs - this.urgentStartedAtMs >= this.tuning.rapidMinimumDisplayMs) {
1709
+ this.urgentStatus = null;
1710
+ this.urgentStartedAtMs = null;
1711
+ }
1712
+ if (evidence.normalCandidate !== null) {
1713
+ this.normalLongitudinalStatus = evidence.normalCandidate;
1714
+ this.longitudinalClearSinceMs = null;
1715
+ } else if (this.normalLongitudinalStatus !== null) if (evidence.retainedNormal) this.longitudinalClearSinceMs = null;
1716
+ else {
1717
+ if (this.longitudinalClearSinceMs === null) this.longitudinalClearSinceMs = sampledAtMs;
1718
+ if (sampledAtMs - this.longitudinalClearSinceMs >= this.tuning.longitudinalReleaseMs) {
1719
+ this.normalLongitudinalStatus = null;
1720
+ this.longitudinalClearSinceMs = null;
1721
+ }
1722
+ }
1562
1723
  }
1563
- recordMotionSample(sample, capturedAtMs) {
1564
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.recordMotionSample", "called", {
1565
- sample,
1566
- capturedAtMs,
1567
- motionWindow: this.motionWindow
1568
- });
1569
- if (!this.motionWindow.some((existing) => existing.sampledAtMs === sample.sampledAtMs)) {
1570
- this.motionWindow.push(sample);
1571
- this.motionWindow.sort((left, right) => left.sampledAtMs - right.sampledAtMs);
1724
+ updateTurnDetector(evidence) {
1725
+ if (this.turnStatus === null) {
1726
+ if (evidence.candidate !== null) this.turnStatus = evidence.candidate;
1727
+ this.turnQuietSinceMs = null;
1728
+ this.oppositeTurnSinceMs = null;
1729
+ return;
1572
1730
  }
1573
- const cutoffMs = capturedAtMs - this.tuning.motionWindowMs;
1574
- while (this.motionWindow[0] && this.motionWindow[0].sampledAtMs <= cutoffMs) this.motionWindow.shift();
1575
- if (this.motionWindow.length < this.tuning.motionMinimumSamples) {
1576
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.recordMotionSample", "returned warmup", {
1577
- result: null,
1578
- motionWindow: this.motionWindow
1579
- });
1580
- return null;
1731
+ const direction = evidence.latestYaw > 0 ? "LEFT_TURN" : "RIGHT_TURN";
1732
+ if (!(Math.abs(evidence.latestYaw) >= this.tuning.turnExitYawThreshold)) {
1733
+ this.oppositeTurnSinceMs = null;
1734
+ if (this.turnQuietSinceMs === null) this.turnQuietSinceMs = evidence.sampledAtMs;
1735
+ if (evidence.sampledAtMs - this.turnQuietSinceMs >= this.tuning.turnExitConfirmationMs) {
1736
+ this.turnStatus = null;
1737
+ this.turnQuietSinceMs = null;
1738
+ this.yawWindow.length = 0;
1739
+ }
1740
+ return;
1581
1741
  }
1582
- const result = combinedAxisStandardDeviation(this.motionWindow);
1583
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.recordMotionSample", "returned", {
1584
- result,
1585
- motionWindow: this.motionWindow
1586
- });
1587
- return result;
1742
+ this.turnQuietSinceMs = null;
1743
+ if (direction === this.turnStatus) {
1744
+ this.oppositeTurnSinceMs = null;
1745
+ return;
1746
+ }
1747
+ if (this.oppositeTurnSinceMs === null) this.oppositeTurnSinceMs = evidence.sampledAtMs;
1748
+ if (evidence.sampledAtMs - this.oppositeTurnSinceMs >= this.tuning.turnClosureWindowMs) {
1749
+ this.turnStatus = direction;
1750
+ this.oppositeTurnSinceMs = null;
1751
+ }
1752
+ }
1753
+ updateBias(metrics) {
1754
+ const sampledAtMs = Math.min(metrics.linearAccelerationSampledAtMs, metrics.gyroscopeSampledAtMs);
1755
+ if (sampledAtMs === this.lastBiasSampledAtMs) return;
1756
+ if (this.lastBiasSampledAtMs !== null && sampledAtMs < this.lastBiasSampledAtMs) return;
1757
+ if (!this.biasCalibrated || this.lastBiasSampledAtMs === null) {
1758
+ this.linearAccelerationBias = { ...metrics.rawLinearAcceleration };
1759
+ this.gyroscopeBias = { ...metrics.rawGyroscope };
1760
+ this.biasCalibrated = true;
1761
+ } else {
1762
+ const elapsedMs = Math.max(0, sampledAtMs - this.lastBiasSampledAtMs);
1763
+ const alpha = 1 - Math.exp(-elapsedMs / this.tuning.biasFilterTimeConstantMs);
1764
+ this.linearAccelerationBias = lerpVector(this.linearAccelerationBias, metrics.rawLinearAcceleration, alpha);
1765
+ this.gyroscopeBias = lerpVector(this.gyroscopeBias, metrics.rawGyroscope, alpha);
1766
+ }
1767
+ this.lastBiasSampledAtMs = sampledAtMs;
1768
+ }
1769
+ commitSelectedStatus(now) {
1770
+ const selected = this.urgentStatus ?? this.turnStatus ?? this.normalLongitudinalStatus ?? this.baseStatus;
1771
+ this.commit(selected, now, "candidate_confirmed");
1588
1772
  }
1589
1773
  processUnavailable(category) {
1590
1774
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processUnavailable", "called", { category });
1591
1775
  const now = this.clock.now();
1592
1776
  if (this.unavailableSinceMs === null) this.unavailableSinceMs = now;
1593
1777
  const unavailableForMs = Math.max(0, now - this.unavailableSinceMs);
1594
- this.metricWindow.length = 0;
1595
- this.motionWindow.length = 0;
1778
+ this.clearRecognitionWindows();
1596
1779
  this.setCandidate(null, now);
1597
1780
  this.emitDiagnostic({
1598
1781
  type: "sample_unavailable",
@@ -1602,97 +1785,39 @@ var DefaultDrivingStatusController = class {
1602
1785
  tuningVersion: this.tuning.version
1603
1786
  });
1604
1787
  if (unavailableForMs < this.unavailableFallbackMs || this.fallbackApplied) {
1605
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processUnavailable", "returned while holding", {
1788
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processUnavailable", "holding previous status", {
1789
+ category,
1606
1790
  unavailableForMs,
1607
1791
  fallbackApplied: this.fallbackApplied
1608
1792
  });
1609
1793
  return;
1610
1794
  }
1611
1795
  this.fallbackApplied = true;
1796
+ this.baseStatus = "STOPPED";
1612
1797
  this.emitDiagnostic({
1613
1798
  type: "fallback_to_stopped",
1614
1799
  atMs: now,
1615
1800
  unavailableForMs,
1616
1801
  tuningVersion: this.tuning.version
1617
1802
  });
1618
- this.commit("STOPPED", now, "unavailable_fallback", true);
1619
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processUnavailable", "returned after fallback", {
1803
+ this.commit("STOPPED", now, "unavailable_fallback");
1804
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processUnavailable", "returned fallback", {
1620
1805
  unavailableForMs,
1621
- snapshot: this.getSnapshot()
1806
+ status: this.status
1622
1807
  });
1623
1808
  }
1624
- processCandidate(nextCandidate, now) {
1625
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processCandidate", "called", {
1626
- nextCandidate,
1627
- now,
1628
- currentStatus: this.status,
1629
- candidate: this.candidate,
1630
- candidateConfirmationCount: this.candidateConfirmationCount
1631
- });
1632
- if (nextCandidate === this.status) {
1633
- this.setCandidate(null, now);
1634
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processCandidate", "returned current status");
1635
- return;
1636
- }
1637
- if (nextCandidate !== this.candidate) {
1638
- this.setCandidate(nextCandidate, now);
1639
- this.candidateConfirmationCount = 1;
1640
- this.heldCandidate = null;
1641
- } else this.candidateConfirmationCount += 1;
1642
- if (this.candidateConfirmationCount < this.tuning.confirmationSamples[nextCandidate]) {
1643
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processCandidate", "awaiting confirmation", {
1644
- nextCandidate,
1645
- candidateConfirmationCount: this.candidateConfirmationCount,
1646
- required: this.tuning.confirmationSamples[nextCandidate]
1647
- });
1648
- return;
1649
- }
1650
- const elapsedMs = Math.max(0, now - this.statusCommittedAtMs);
1651
- const remainingMs = Math.max(0, this.tuning.minimumDisplayMs[this.status] - elapsedMs);
1652
- const canPreempt = CAR_RUNNING_STATUS_PRIORITY[nextCandidate] > CAR_RUNNING_STATUS_PRIORITY[this.status];
1653
- if (remainingMs > 0 && !canPreempt) {
1654
- if (this.heldCandidate !== nextCandidate) {
1655
- this.heldCandidate = nextCandidate;
1656
- this.emitDiagnostic({
1657
- type: "status_held_by_min_duration",
1658
- atMs: now,
1659
- status: this.status,
1660
- candidate: nextCandidate,
1661
- remainingMs,
1662
- tuningVersion: this.tuning.version
1663
- });
1664
- }
1665
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processCandidate", "held by minimum duration", {
1666
- nextCandidate,
1667
- remainingMs
1668
- });
1669
- return;
1670
- }
1671
- this.commit(nextCandidate, now, "candidate_confirmed");
1672
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processCandidate", "returned after commit", { snapshot: this.getSnapshot() });
1673
- }
1674
- commit(nextStatus, now, reason, force = false) {
1675
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.commit", "called", {
1676
- nextStatus,
1677
- now,
1678
- reason,
1679
- force,
1680
- currentStatus: this.status
1681
- });
1682
- if (!force && nextStatus === this.status) {
1683
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.commit", "ignored unchanged status");
1684
- return;
1685
- }
1809
+ commit(nextStatus, now, reason) {
1686
1810
  if (nextStatus === this.status) {
1687
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.commit", "ignored forced unchanged status");
1811
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.commit", "ignored unchanged status", {
1812
+ status: this.status,
1813
+ reason
1814
+ });
1688
1815
  return;
1689
1816
  }
1690
1817
  const previousStatus = this.status;
1691
1818
  this.status = nextStatus;
1692
- if (isBaseStatus(nextStatus)) this.baseStatus = nextStatus;
1693
1819
  this.statusCommittedAtMs = now;
1694
1820
  this.setCandidate(null, now);
1695
- this.heldCandidate = null;
1696
1821
  const event = {
1697
1822
  type: "status_changed",
1698
1823
  status: nextStatus,
@@ -1709,53 +1834,49 @@ var DefaultDrivingStatusController = class {
1709
1834
  reason,
1710
1835
  tuningVersion: this.tuning.version
1711
1836
  });
1712
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.commit", "returned", {
1713
- event,
1714
- listenerCount: this.listeners.size,
1715
- snapshot: this.getSnapshot()
1716
- });
1837
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.commit", "status changed", { event });
1717
1838
  }
1718
- setCandidate(candidate, now) {
1719
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.setCandidate", "called", {
1720
- candidate,
1721
- now,
1722
- currentCandidate: this.candidate
1723
- });
1724
- if (candidate === this.candidate) {
1725
- if (candidate === null) this.candidateConfirmationCount = 0;
1726
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.setCandidate", "returned unchanged", { candidateConfirmationCount: this.candidateConfirmationCount });
1727
- return;
1728
- }
1839
+ setCandidate(candidate, atMs) {
1840
+ if (candidate === this.candidate) return;
1729
1841
  const previousCandidate = this.candidate;
1730
1842
  this.candidate = candidate;
1731
- this.candidateConfirmationCount = 0;
1732
- this.heldCandidate = null;
1843
+ this.candidateSinceMs = candidate === null ? null : atMs;
1733
1844
  this.emitDiagnostic({
1734
1845
  type: "candidate_changed",
1735
- atMs: now,
1846
+ atMs: this.clock.now(),
1736
1847
  candidate,
1737
1848
  previousCandidate,
1738
1849
  tuningVersion: this.tuning.version
1739
1850
  });
1740
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.setCandidate", "returned", {
1851
+ dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.setCandidate", "candidate changed", {
1741
1852
  candidate,
1742
- previousCandidate
1853
+ previousCandidate,
1854
+ candidateSinceMs: this.candidateSinceMs
1743
1855
  });
1744
1856
  }
1745
- resetTransientRecognition() {
1746
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.resetTransientRecognition", "called", {
1747
- metricWindow: this.metricWindow,
1748
- motionWindow: this.motionWindow,
1749
- candidate: this.candidate
1750
- });
1751
- this.metricWindow.length = 0;
1857
+ clearRecognitionWindows() {
1858
+ this.longitudinalWindow.length = 0;
1859
+ this.yawWindow.length = 0;
1752
1860
  this.motionWindow.length = 0;
1861
+ this.stoppedConditionSinceMs = null;
1862
+ this.drivingConditionSinceMs = null;
1863
+ this.stationaryConfirmed = false;
1864
+ this.urgentStatus = null;
1865
+ this.urgentStartedAtMs = null;
1866
+ this.normalLongitudinalStatus = null;
1867
+ this.longitudinalClearSinceMs = null;
1868
+ this.turnStatus = null;
1869
+ this.turnQuietSinceMs = null;
1870
+ this.oppositeTurnSinceMs = null;
1871
+ }
1872
+ resetTransientRecognition() {
1873
+ this.clearRecognitionWindows();
1753
1874
  this.candidate = null;
1754
- this.candidateConfirmationCount = 0;
1875
+ this.candidateSinceMs = null;
1755
1876
  this.unavailableSinceMs = null;
1756
1877
  this.fallbackApplied = false;
1757
- this.heldCandidate = null;
1758
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.resetTransientRecognition", "returned");
1878
+ this.filteredGravity = null;
1879
+ this.lastGravitySampledAtMs = null;
1759
1880
  }
1760
1881
  emitDiagnostic(event) {
1761
1882
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.emitDiagnostic", "called", {
@@ -1763,315 +1884,197 @@ var DefaultDrivingStatusController = class {
1763
1884
  hasListener: this.onDiagnostic !== void 0
1764
1885
  });
1765
1886
  if (this.onDiagnostic) safeCall(this.onDiagnostic, event);
1766
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.emitDiagnostic", "returned", { event });
1767
1887
  }
1768
1888
  attachLifecycleListeners() {
1769
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.attachLifecycleListeners", "called", {
1770
- managePageLifecycle: this.managePageLifecycle,
1771
- attached: this.unsubscribePageLifecycle !== null
1772
- });
1773
- if (!this.managePageLifecycle || this.unsubscribePageLifecycle !== null) {
1774
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.attachLifecycleListeners", "ignored");
1775
- return;
1776
- }
1889
+ if (!this.managePageLifecycle || this.unsubscribePageLifecycle !== null) return;
1777
1890
  this.unsubscribePageLifecycle = subscribeDrivingPageLifecycle(this.handleLifecyclePause, this.handleLifecycleResume);
1778
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.attachLifecycleListeners", "returned");
1779
1891
  }
1780
1892
  detachLifecycleListeners() {
1781
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.detachLifecycleListeners", "called", { attached: this.unsubscribePageLifecycle !== null });
1782
1893
  this.unsubscribePageLifecycle?.();
1783
1894
  this.unsubscribePageLifecycle = null;
1784
- dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.detachLifecycleListeners", "returned");
1785
1895
  }
1786
1896
  };
1787
- const classifyControllerCandidate = (raw, smoothed, motionIntensity, current, baseStatus, tuning) => {
1788
- dialSdkDebugWarn(DEBUG_MODULE$1, "classifyControllerCandidate", "called", {
1789
- raw,
1790
- smoothed,
1791
- motionIntensity,
1792
- current,
1793
- baseStatus,
1794
- tuning
1795
- });
1796
- const entry = classifyEntryCandidate(raw, smoothed, motionIntensity, baseStatus, tuning);
1797
- const retained = retainsCurrentStatus(raw, smoothed, motionIntensity, current, tuning);
1798
- const result = !retained || CAR_RUNNING_STATUS_PRIORITY[entry] > CAR_RUNNING_STATUS_PRIORITY[current] || isOppositeTurn(entry, current) || (entry === "STOPPED" || entry === "STEADY_DRIVING") && (current === "STOPPED" || current === "STEADY_DRIVING") ? entry : current;
1799
- dialSdkDebugWarn(DEBUG_MODULE$1, "classifyControllerCandidate", "returned", {
1800
- entry,
1801
- retained,
1802
- result
1803
- });
1804
- return result;
1897
+ const detectLongitudinalEvidence = (window, tuning) => {
1898
+ const rapidWindow = recentMetrics(window, tuning.rapidLongitudinalWindowMs, "linearAccelerationSampledAtMs");
1899
+ const normalWindow = recentMetrics(window, tuning.longitudinalWindowMs, "linearAccelerationSampledAtMs");
1900
+ let rapidCandidate = null;
1901
+ let rejectionReason = null;
1902
+ if (windowCovers(rapidWindow, tuning.rapidLongitudinalMinimumSamples, tuning.rapidLongitudinalWindowMs, "linearAccelerationSampledAtMs")) {
1903
+ const values = rapidWindow.map((sample) => sample.longitudinal);
1904
+ const direction = Math.sign(trimmedMean(values));
1905
+ const coherence = signCoherence(values, direction, tuning.longitudinalExitThreshold);
1906
+ const peak = Math.max(...values.map(Math.abs));
1907
+ const mean = Math.abs(trimmedMean(values));
1908
+ const longitudinalRms = rootMeanSquare(values);
1909
+ const verticalRms = rootMeanSquare(rapidWindow.map((sample) => sample.vertical));
1910
+ const reversalWindow = recentMetrics(window, tuning.longitudinalReversalWindowMs, "linearAccelerationSampledAtMs");
1911
+ const hasReversal = containsBothSigns(reversalWindow.map((sample) => sample.longitudinal), tuning.longitudinalThreshold);
1912
+ if (peak >= tuning.rapidLongitudinalPeakThreshold && mean >= tuning.rapidLongitudinalMeanThreshold) {
1913
+ if (verticalRms > longitudinalRms * tuning.verticalBumpRatio) rejectionReason = "vertical_bump";
1914
+ else if (hasReversal) rejectionReason = "longitudinal_reversal";
1915
+ else if (direction !== 0 && coherence >= tuning.rapidLongitudinalSignCoherence) rapidCandidate = direction > 0 ? "RAPID_ACCELERATION" : "SUDDEN_BRAKING";
1916
+ }
1917
+ }
1918
+ let normalCandidate = null;
1919
+ let retainedNormal = false;
1920
+ if (normalWindow.length > 0) {
1921
+ const values = normalWindow.map((sample) => sample.longitudinal);
1922
+ const mean = trimmedMean(values);
1923
+ const direction = Math.sign(mean);
1924
+ retainedNormal = Math.abs(mean) >= tuning.longitudinalExitThreshold;
1925
+ if (windowCovers(normalWindow, tuning.longitudinalMinimumSamples, tuning.longitudinalWindowMs, "linearAccelerationSampledAtMs") && Math.abs(mean) >= tuning.longitudinalThreshold && signCoherence(values, direction, tuning.longitudinalExitThreshold) >= tuning.longitudinalSignCoherence) normalCandidate = direction > 0 ? "ACCELERATION" : "BRAKING";
1926
+ }
1927
+ return {
1928
+ rapidCandidate,
1929
+ normalCandidate,
1930
+ retainedNormal,
1931
+ rejectionReason
1932
+ };
1805
1933
  };
1806
- const classifyEntryCandidate = (raw, smoothed, motionIntensity, baseStatus, tuning) => {
1807
- dialSdkDebugWarn(DEBUG_MODULE$1, "classifyEntryCandidate", "called", {
1808
- raw,
1809
- smoothed,
1810
- motionIntensity,
1811
- baseStatus,
1812
- tuning
1813
- });
1814
- if (raw.longitudinal <= -tuning.rapidLongitudinalThreshold) {
1815
- dialSdkDebugWarn(DEBUG_MODULE$1, "classifyEntryCandidate", "returned", { result: "SUDDEN_BRAKING" });
1816
- return "SUDDEN_BRAKING";
1817
- }
1818
- if (raw.longitudinal >= tuning.rapidLongitudinalThreshold) {
1819
- dialSdkDebugWarn(DEBUG_MODULE$1, "classifyEntryCandidate", "returned", { result: "RAPID_ACCELERATION" });
1820
- return "RAPID_ACCELERATION";
1821
- }
1822
- if (Math.abs(smoothed.yaw) >= tuning.turnYawThreshold) {
1823
- const result = smoothed.yaw > 0 ? "LEFT_TURN" : "RIGHT_TURN";
1824
- dialSdkDebugWarn(DEBUG_MODULE$1, "classifyEntryCandidate", "returned", { result });
1825
- return result;
1826
- }
1827
- if (Math.abs(smoothed.lateral) >= tuning.turnLateralThreshold) {
1828
- const result = smoothed.lateral < 0 ? "LEFT_TURN" : "RIGHT_TURN";
1829
- dialSdkDebugWarn(DEBUG_MODULE$1, "classifyEntryCandidate", "returned", { result });
1830
- return result;
1831
- }
1832
- if (smoothed.longitudinal <= -tuning.longitudinalThreshold) {
1833
- dialSdkDebugWarn(DEBUG_MODULE$1, "classifyEntryCandidate", "returned", { result: "BRAKING" });
1834
- return "BRAKING";
1835
- }
1836
- if (smoothed.longitudinal >= tuning.longitudinalThreshold) {
1837
- dialSdkDebugWarn(DEBUG_MODULE$1, "classifyEntryCandidate", "returned", { result: "ACCELERATION" });
1838
- return "ACCELERATION";
1839
- }
1840
- if (motionIntensity === null) {
1841
- dialSdkDebugWarn(DEBUG_MODULE$1, "classifyEntryCandidate", "returned base status", { result: baseStatus });
1842
- return baseStatus;
1843
- }
1844
- if (motionIntensity > tuning.drivingMotionThreshold || smoothed.gyroMagnitude > tuning.stoppedGyroThreshold) {
1845
- dialSdkDebugWarn(DEBUG_MODULE$1, "classifyEntryCandidate", "returned", { result: "STEADY_DRIVING" });
1846
- return "STEADY_DRIVING";
1847
- }
1848
- if (motionIntensity < tuning.stoppedMotionThreshold) {
1849
- dialSdkDebugWarn(DEBUG_MODULE$1, "classifyEntryCandidate", "returned", { result: "STOPPED" });
1850
- return "STOPPED";
1851
- }
1852
- dialSdkDebugWarn(DEBUG_MODULE$1, "classifyEntryCandidate", "returned base status", { result: baseStatus });
1853
- return baseStatus;
1934
+ const detectTurnEvidence = (window, tuning) => {
1935
+ const latest = window[window.length - 1];
1936
+ if (!latest) return {
1937
+ candidate: null,
1938
+ latestYaw: 0,
1939
+ sampledAtMs: 0
1940
+ };
1941
+ const strongWindow = recentMetrics(window, tuning.turnConfirmationMs, "gyroscopeSampledAtMs");
1942
+ const strongMean = trimmedMean(strongWindow.map((sample) => sample.yaw));
1943
+ const strongDirection = Math.sign(strongMean);
1944
+ const strong = windowCovers(strongWindow, tuning.turnMinimumSamples, tuning.turnConfirmationMs, "gyroscopeSampledAtMs") && Math.abs(strongMean) >= tuning.turnYawThreshold && signCoherence(strongWindow.map((sample) => sample.yaw), strongDirection, tuning.turnExitYawThreshold) >= tuning.turnSignCoherence;
1945
+ const signedAngle = integrateYaw(window);
1946
+ const gentleDirection = Math.sign(signedAngle);
1947
+ const gentle = window.length >= tuning.turnMinimumSamples && Math.abs(signedAngle) >= tuning.turnIntegratedAngleThreshold && signCoherence(window.map((sample) => sample.yaw), gentleDirection, tuning.turnExitYawThreshold) >= tuning.turnSignCoherence;
1948
+ const direction = strong ? strongDirection : gentle ? gentleDirection : 0;
1949
+ return {
1950
+ candidate: direction === 0 ? null : direction > 0 ? "LEFT_TURN" : "RIGHT_TURN",
1951
+ latestYaw: latest.yaw,
1952
+ sampledAtMs: latest.gyroscopeSampledAtMs
1953
+ };
1854
1954
  };
1855
- const retainsCurrentStatus = (raw, smoothed, motionIntensity, current, tuning) => {
1856
- dialSdkDebugWarn(DEBUG_MODULE$1, "retainsCurrentStatus", "called", {
1857
- raw,
1858
- smoothed,
1859
- motionIntensity,
1860
- current,
1861
- tuning
1862
- });
1863
- const ratio = tuning.hysteresisRatio;
1864
- let result;
1865
- switch (current) {
1866
- case "SUDDEN_BRAKING":
1867
- result = raw.longitudinal <= -tuning.rapidLongitudinalThreshold * ratio;
1868
- break;
1869
- case "RAPID_ACCELERATION":
1870
- result = raw.longitudinal >= tuning.rapidLongitudinalThreshold * ratio;
1871
- break;
1872
- case "LEFT_TURN":
1873
- result = smoothed.yaw >= tuning.turnYawThreshold * ratio || smoothed.lateral <= -tuning.turnLateralThreshold * ratio;
1874
- break;
1875
- case "RIGHT_TURN":
1876
- result = smoothed.yaw <= -tuning.turnYawThreshold * ratio || smoothed.lateral >= tuning.turnLateralThreshold * ratio;
1877
- break;
1878
- case "BRAKING":
1879
- result = smoothed.longitudinal <= -tuning.longitudinalThreshold * ratio;
1880
- break;
1881
- case "ACCELERATION":
1882
- result = smoothed.longitudinal >= tuning.longitudinalThreshold * ratio;
1883
- break;
1884
- case "STOPPED":
1885
- result = (motionIntensity === null || motionIntensity <= tuning.drivingMotionThreshold) && smoothed.gyroMagnitude <= tuning.stoppedGyroThreshold;
1886
- break;
1887
- case "STEADY_DRIVING":
1888
- result = motionIntensity === null || motionIntensity >= tuning.stoppedMotionThreshold || smoothed.gyroMagnitude > tuning.stoppedGyroThreshold;
1889
- break;
1890
- }
1891
- dialSdkDebugWarn(DEBUG_MODULE$1, "retainsCurrentStatus", "returned", { result });
1892
- return result;
1955
+ const instantaneousCandidate = (metrics, tuning) => {
1956
+ const longitudinalMagnitude = Math.abs(metrics.longitudinal);
1957
+ if (longitudinalMagnitude >= tuning.rapidLongitudinalPeakThreshold && Math.abs(metrics.vertical) <= longitudinalMagnitude * tuning.verticalBumpRatio) return metrics.longitudinal > 0 ? "RAPID_ACCELERATION" : "SUDDEN_BRAKING";
1958
+ if (Math.abs(metrics.yaw) >= tuning.turnYawThreshold) return metrics.yaw > 0 ? "LEFT_TURN" : "RIGHT_TURN";
1959
+ if (longitudinalMagnitude >= tuning.longitudinalThreshold) return metrics.longitudinal > 0 ? "ACCELERATION" : "BRAKING";
1960
+ return null;
1961
+ };
1962
+ const recentMetrics = (values, windowMs, timestampKey) => {
1963
+ const latestAtMs = values[values.length - 1]?.[timestampKey] ?? 0;
1964
+ return values.filter((value) => value[timestampKey] >= latestAtMs - windowMs);
1965
+ };
1966
+ const windowCovers = (values, minimumSamples, durationMs, timestampKey) => {
1967
+ if (values.length < minimumSamples) return false;
1968
+ const first = values[0];
1969
+ return values[values.length - 1][timestampKey] - first[timestampKey] >= durationMs;
1970
+ };
1971
+ const integrateYaw = (values) => {
1972
+ let angle = 0;
1973
+ for (let index = 1; index < values.length; index += 1) {
1974
+ const previous = values[index - 1];
1975
+ const current = values[index];
1976
+ const elapsedSeconds = Math.max(0, current.gyroscopeSampledAtMs - previous.gyroscopeSampledAtMs) / 1e3;
1977
+ angle += (previous.yaw + current.yaw) / 2 * elapsedSeconds;
1978
+ }
1979
+ return angle;
1980
+ };
1981
+ const trimmedMean = (values) => {
1982
+ if (values.length === 0) return 0;
1983
+ if (values.length < 4) return values.reduce((sum, value) => sum + value, 0) / values.length;
1984
+ const sorted = [...values].sort((left, right) => left - right).slice(1, -1);
1985
+ return sorted.reduce((sum, value) => sum + value, 0) / sorted.length;
1986
+ };
1987
+ const signCoherence = (values, direction, deadband) => {
1988
+ if (values.length === 0 || direction === 0) return 0;
1989
+ return values.filter((value) => Math.abs(value) >= deadband && Math.sign(value) === direction).length / values.length;
1990
+ };
1991
+ const containsBothSigns = (values, threshold) => values.some((value) => value >= threshold) && values.some((value) => value <= -threshold);
1992
+ const rootMeanSquare = (values) => values.length === 0 ? 0 : Math.sqrt(values.reduce((sum, value) => sum + value ** 2, 0) / values.length);
1993
+ const calculateCombinedAxisMad = (samples, minimumSamples) => {
1994
+ if (samples.length < minimumSamples) return null;
1995
+ const medianX = median(samples.map((sample) => sample.value.x));
1996
+ const medianY = median(samples.map((sample) => sample.value.y));
1997
+ const medianZ = median(samples.map((sample) => sample.value.z));
1998
+ const madX = median(samples.map((sample) => Math.abs(sample.value.x - medianX)));
1999
+ const madY = median(samples.map((sample) => Math.abs(sample.value.y - medianY)));
2000
+ const madZ = median(samples.map((sample) => Math.abs(sample.value.z - medianZ)));
2001
+ return Math.hypot(madX, madY, madZ);
2002
+ };
2003
+ const median = (values) => {
2004
+ if (values.length === 0) return 0;
2005
+ const sorted = [...values].sort((left, right) => left - right);
2006
+ const middle = Math.floor(sorted.length / 2);
2007
+ return sorted.length % 2 === 0 ? (sorted[middle - 1] + sorted[middle]) / 2 : sorted[middle];
2008
+ };
2009
+ const pushUniqueMetric = (window, metrics, timestampKey, windowMs) => {
2010
+ const latest = window[window.length - 1];
2011
+ if (latest && metrics[timestampKey] <= latest[timestampKey]) return false;
2012
+ if (window.some((value) => value[timestampKey] === metrics[timestampKey])) return false;
2013
+ window.push(metrics);
2014
+ const cutoffMs = metrics[timestampKey] - windowMs;
2015
+ while (window[0] && window[0][timestampKey] < cutoffMs) window.shift();
2016
+ return true;
1893
2017
  };
1894
- const averageMetrics = (metrics) => {
1895
- dialSdkDebugWarn(DEBUG_MODULE$1, "averageMetrics", "called", { metrics });
1896
- const result = {
1897
- capturedAtMs: metrics[metrics.length - 1]?.capturedAtMs ?? 0,
1898
- longitudinal: 0,
1899
- lateral: 0,
1900
- yaw: 0,
1901
- motionIntensity: 0,
1902
- gyroMagnitude: 0
1903
- };
1904
- for (const value of metrics) {
1905
- result.longitudinal += value.longitudinal;
1906
- result.lateral += value.lateral;
1907
- result.yaw += value.yaw;
1908
- result.motionIntensity += value.motionIntensity;
1909
- result.gyroMagnitude += value.gyroMagnitude;
1910
- }
1911
- const count = Math.max(1, metrics.length);
1912
- result.longitudinal /= count;
1913
- result.lateral /= count;
1914
- result.yaw /= count;
1915
- result.motionIntensity /= count;
1916
- result.gyroMagnitude /= count;
1917
- dialSdkDebugWarn(DEBUG_MODULE$1, "averageMetrics", "returned", { result });
1918
- return result;
2018
+ const pushUniqueVector = (window, sample, windowMs) => {
2019
+ const latest = window[window.length - 1];
2020
+ if (latest && sample.sampledAtMs <= latest.sampledAtMs) return false;
2021
+ if (window.some((value) => value.sampledAtMs === sample.sampledAtMs)) return false;
2022
+ window.push(sample);
2023
+ const cutoffMs = sample.sampledAtMs - windowMs;
2024
+ while (window[0] && window[0].sampledAtMs < cutoffMs) window.shift();
2025
+ return true;
1919
2026
  };
1920
- const extractFreshAccelerometerSample = (info, maxSampleAgeMs) => {
1921
- dialSdkDebugWarn(DEBUG_MODULE$1, "extractFreshAccelerometerSample", "called", {
1922
- info,
1923
- maxSampleAgeMs
1924
- });
1925
- const metric = info?.accelerometer;
1926
- if (!metric?.available || !Number.isFinite(info.capturedAtMs) || !Number.isFinite(metric.sampledAtMs)) {
1927
- dialSdkDebugWarn(DEBUG_MODULE$1, "extractFreshAccelerometerSample", "returned", { result: null });
1928
- return null;
1929
- }
1930
- const ageMs = info.capturedAtMs - metric.sampledAtMs;
1931
- if (ageMs < 0 || ageMs > maxSampleAgeMs) {
1932
- dialSdkDebugWarn(DEBUG_MODULE$1, "extractFreshAccelerometerSample", "returned stale sample", {
1933
- result: null,
1934
- ageMs
1935
- });
1936
- return null;
1937
- }
2027
+ const extractFreshVectorMetric = (metric, capturedAtMs, maxSampleAgeMs) => {
2028
+ if (!metric?.available || !Number.isFinite(capturedAtMs) || !Number.isFinite(metric.sampledAtMs)) return null;
2029
+ const ageMs = capturedAtMs - metric.sampledAtMs;
2030
+ if (ageMs < 0 || ageMs > maxSampleAgeMs) return null;
1938
2031
  const { x, y, z } = metric.value;
1939
- if (![
2032
+ return [
1940
2033
  x,
1941
2034
  y,
1942
2035
  z
1943
- ].every(Number.isFinite)) {
1944
- dialSdkDebugWarn(DEBUG_MODULE$1, "extractFreshAccelerometerSample", "returned invalid vector", { result: null });
1945
- return null;
1946
- }
1947
- const result = {
2036
+ ].every(Number.isFinite) ? {
1948
2037
  sampledAtMs: metric.sampledAtMs,
1949
2038
  value: metric.value
1950
- };
1951
- dialSdkDebugWarn(DEBUG_MODULE$1, "extractFreshAccelerometerSample", "returned", { result });
1952
- return result;
1953
- };
1954
- const combinedAxisStandardDeviation = (samples) => {
1955
- dialSdkDebugWarn(DEBUG_MODULE$1, "combinedAxisStandardDeviation", "called", { samples });
1956
- const count = samples.length;
1957
- let meanX = 0;
1958
- let meanY = 0;
1959
- let meanZ = 0;
1960
- for (const sample of samples) {
1961
- meanX += sample.value.x;
1962
- meanY += sample.value.y;
1963
- meanZ += sample.value.z;
1964
- }
1965
- meanX /= count;
1966
- meanY /= count;
1967
- meanZ /= count;
1968
- let varianceX = 0;
1969
- let varianceY = 0;
1970
- let varianceZ = 0;
1971
- for (const sample of samples) {
1972
- varianceX += (sample.value.x - meanX) ** 2;
1973
- varianceY += (sample.value.y - meanY) ** 2;
1974
- varianceZ += (sample.value.z - meanZ) ** 2;
1975
- }
1976
- const result = Math.sqrt((varianceX + varianceY + varianceZ) / count);
1977
- dialSdkDebugWarn(DEBUG_MODULE$1, "combinedAxisStandardDeviation", "returned", {
1978
- meanX,
1979
- meanY,
1980
- meanZ,
1981
- varianceX,
1982
- varianceY,
1983
- varianceZ,
1984
- result
1985
- });
1986
- return result;
2039
+ } : null;
1987
2040
  };
2041
+ const observationTime = (metrics) => Math.max(metrics.linearAccelerationSampledAtMs, metrics.gyroscopeSampledAtMs);
2042
+ const lerpVector = (from, to, alpha) => ({
2043
+ x: from.x + (to.x - from.x) * alpha,
2044
+ y: from.y + (to.y - from.y) * alpha,
2045
+ z: from.z + (to.z - from.z) * alpha
2046
+ });
1988
2047
  const defaultSensorProvider = () => {
1989
- dialSdkDebugWarn(DEBUG_MODULE$1, "defaultSensorProvider", "called");
1990
2048
  const runtimeGlobal = globalThis;
1991
- if (typeof runtimeGlobal.unifiedSensorInfo !== "function") {
1992
- const error = /* @__PURE__ */ new Error("window.unifiedSensorInfo is unavailable");
1993
- dialSdkDebugWarn(DEBUG_MODULE$1, "defaultSensorProvider", "rejected", error);
1994
- return Promise.reject(error);
1995
- }
1996
- try {
1997
- const result = runtimeGlobal.unifiedSensorInfo();
1998
- result.then((payload) => dialSdkDebugWarn(DEBUG_MODULE$1, "defaultSensorProvider", "resolved", { payload }), (error) => dialSdkDebugWarn(DEBUG_MODULE$1, "defaultSensorProvider", "rejected", error));
1999
- return result;
2000
- } catch (error) {
2001
- dialSdkDebugWarn(DEBUG_MODULE$1, "defaultSensorProvider", "threw", error);
2002
- throw error;
2003
- }
2049
+ if (typeof runtimeGlobal.unifiedSensorInfo !== "function") return Promise.reject(/* @__PURE__ */ new Error("window.unifiedSensorInfo is unavailable"));
2050
+ return runtimeGlobal.unifiedSensorInfo();
2004
2051
  };
2005
2052
  const systemClock = {
2006
- now: () => {
2007
- const result = Date.now();
2008
- dialSdkDebugWarn(DEBUG_MODULE$1, "systemClock.now", "returned", { result });
2009
- return result;
2010
- },
2011
- setTimeout: (callback, delayMs) => {
2012
- const result = globalThis.setTimeout(callback, delayMs);
2013
- dialSdkDebugWarn(DEBUG_MODULE$1, "systemClock.setTimeout", "returned", {
2014
- delayMs,
2015
- result
2016
- });
2017
- return result;
2018
- },
2019
- clearTimeout: (handle) => {
2020
- dialSdkDebugWarn(DEBUG_MODULE$1, "systemClock.clearTimeout", "called", { handle });
2021
- globalThis.clearTimeout(handle);
2022
- dialSdkDebugWarn(DEBUG_MODULE$1, "systemClock.clearTimeout", "returned");
2023
- }
2024
- };
2025
- const isOppositeTurn = (candidate, current) => {
2026
- const result = candidate === "LEFT_TURN" && current === "RIGHT_TURN" || candidate === "RIGHT_TURN" && current === "LEFT_TURN";
2027
- dialSdkDebugWarn(DEBUG_MODULE$1, "isOppositeTurn", "returned", {
2028
- candidate,
2029
- current,
2030
- result
2031
- });
2032
- return result;
2033
- };
2034
- const isBaseStatus = (status) => {
2035
- const result = status === "STOPPED" || status === "STEADY_DRIVING";
2036
- dialSdkDebugWarn(DEBUG_MODULE$1, "isBaseStatus", "returned", {
2037
- status,
2038
- result
2039
- });
2040
- return result;
2041
- };
2042
- const isAbortError$1 = (error) => {
2043
- const result = error instanceof Error && error.name === "AbortError";
2044
- dialSdkDebugWarn(DEBUG_MODULE$1, "isAbortError", "returned", {
2045
- error,
2046
- result
2047
- });
2048
- return result;
2053
+ now: () => Date.now(),
2054
+ setTimeout: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
2055
+ clearTimeout: (handle) => globalThis.clearTimeout(handle)
2049
2056
  };
2057
+ const isBaseStatus = (status) => status === "STOPPED" || status === "STEADY_DRIVING";
2058
+ const isAbortError$1 = (error) => error instanceof Error && error.name === "AbortError";
2050
2059
  const requirePositiveFinite = (name, value) => {
2051
- dialSdkDebugWarn(DEBUG_MODULE$1, "requirePositiveFinite", "called", {
2052
- name,
2053
- value
2054
- });
2055
2060
  if (!Number.isFinite(value) || value <= 0) {
2056
2061
  const error = /* @__PURE__ */ new RangeError(`${name} must be a positive finite number`);
2057
2062
  dialSdkDebugWarn(DEBUG_MODULE$1, "requirePositiveFinite", "threw", error);
2058
2063
  throw error;
2059
2064
  }
2060
- dialSdkDebugWarn(DEBUG_MODULE$1, "requirePositiveFinite", "returned", { result: value });
2061
2065
  return value;
2062
2066
  };
2063
2067
  const safeCall = (callback, value) => {
2064
- dialSdkDebugWarn(DEBUG_MODULE$1, "safeCall", "called", { value });
2065
2068
  try {
2066
2069
  callback(value);
2067
- dialSdkDebugWarn(DEBUG_MODULE$1, "safeCall", "returned");
2068
2070
  } catch (error) {
2069
- dialSdkDebugWarn(DEBUG_MODULE$1, "safeCall", "callback threw", error);
2071
+ dialSdkDebugWarn(DEBUG_MODULE$1, "safeCall", "callback threw", {
2072
+ error,
2073
+ value
2074
+ });
2070
2075
  }
2071
2076
  };
2072
- const noop = () => {
2073
- dialSdkDebugWarn(DEBUG_MODULE$1, "noop", "called and returned");
2074
- };
2077
+ const noop = () => void 0;
2075
2078
  //#endregion
2076
2079
  //#region src/sensor/driving_expression_player.ts
2077
2080
  const DEBUG_MODULE = "driving_expression_player.ts";
@@ -3351,4 +3354,4 @@ const isRecord = (value) => {
3351
3354
  return result;
3352
3355
  };
3353
3356
  //#endregion
3354
- export { BATTERY_HEALTH_LABELS, CAR_RUNNING_LABELS, CAR_RUNNING_STATUSES, CAR_RUNNING_STATUS_PRIORITY, DEFAULT_DRIVING_STATUS_TUNING, DrivingExpressionResolutionError, DrivingExpressionsConfigError, OFFICIAL_DRIVING_EXPRESSIONS_CONFIG, OFFICIAL_DRIVING_EXPRESSION_FALLBACKS, checkRunningStatus, classifyDrivingMetrics, classifyDrivingSnapshot, createDrivingExpressionPlayer, createDrivingStatusController, extractDrivingSnapshotMetrics, isPublicDrivingMediaUrl, parseDrivingExpressionsConfig, readDrivingExpressionsConfig, resolveDrivingExpression, resolveDrivingExpressionStrict, resolveDrivingStatusTuning, tryParseDrivingExpressionsConfig, unifiedSensorInfo, waitForDrivingExpressionsConfig };
3357
+ export { BATTERY_HEALTH_LABELS, CAR_RUNNING_LABELS, CAR_RUNNING_STATUSES, CAR_RUNNING_STATUS_PRIORITY, DEFAULT_DRIVING_STATUS_TUNING, DrivingExpressionResolutionError, DrivingExpressionsConfigError, OFFICIAL_DRIVING_EXPRESSIONS_CONFIG, OFFICIAL_DRIVING_EXPRESSION_FALLBACKS, buildVehicleFrame, classifyDrivingSnapshot, createDrivingExpressionPlayer, createDrivingStatusController, extractDrivingSnapshotMetrics, isPublicDrivingMediaUrl, parseDrivingExpressionsConfig, readDrivingExpressionsConfig, resolveDrivingExpression, resolveDrivingExpressionStrict, resolveDrivingStatusTuning, tryParseDrivingExpressionsConfig, unifiedSensorInfo, waitForDrivingExpressionsConfig };