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