@ziztechnology/dial-library 0.0.12 → 0.0.14

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
@@ -43,8 +43,108 @@ const dialSdkDebugWarn = (moduleName, scope, phase, data) => {
43
43
  } catch {}
44
44
  };
45
45
  //#endregion
46
- //#region src/sensor/check_status.ts
47
- const DEBUG_MODULE$5 = "check_status.ts";
46
+ //#region src/sensor/unified_info.ts
47
+ const DEBUG_MODULE$5 = "unified_info.ts";
48
+ const unifiedSensorInfo = () => {
49
+ dialSdkDebugWarn(DEBUG_MODULE$5, "unifiedSensorInfo", "called");
50
+ try {
51
+ return window.unifiedSensorInfo().then((payload) => {
52
+ dialSdkDebugWarn(DEBUG_MODULE$5, "unifiedSensorInfo", "resolved", { payload });
53
+ return payload;
54
+ }, (error) => {
55
+ dialSdkDebugWarn(DEBUG_MODULE$5, "unifiedSensorInfo", "rejected", error);
56
+ throw error;
57
+ });
58
+ } catch (error) {
59
+ dialSdkDebugWarn(DEBUG_MODULE$5, "unifiedSensorInfo", "threw", error);
60
+ throw error;
61
+ }
62
+ };
63
+ //#endregion
64
+ //#region src/sensor/constants.ts
65
+ const BATTERY_HEALTH_LABELS = {
66
+ GOOD: "良好",
67
+ OVERHEAT: "过热",
68
+ DEAD: "失效",
69
+ OVER_VOLTAGE: "电压过高",
70
+ UNSPECIFIED_FAILURE: "异常",
71
+ COLD: "温度过低",
72
+ UNKNOWN: "未知"
73
+ };
74
+ //#endregion
75
+ //#region src/driving_expression/constants.ts
76
+ const CAR_RUNNING_LABELS = {
77
+ STOPPED: "静止",
78
+ STEADY_DRIVING: "匀速行驶",
79
+ SUDDEN_BRAKING: "急刹车",
80
+ BRAKING: "刹车",
81
+ LEFT_TURN: "左转",
82
+ RIGHT_TURN: "右转",
83
+ ACCELERATION: "加速",
84
+ RAPID_ACCELERATION: "急加速"
85
+ };
86
+ const CAR_RUNNING_STATUSES = [
87
+ "STOPPED",
88
+ "STEADY_DRIVING",
89
+ "ACCELERATION",
90
+ "RAPID_ACCELERATION",
91
+ "BRAKING",
92
+ "SUDDEN_BRAKING",
93
+ "LEFT_TURN",
94
+ "RIGHT_TURN"
95
+ ];
96
+ const CAR_RUNNING_STATUS_PRIORITY = {
97
+ STOPPED: 0,
98
+ STEADY_DRIVING: 0,
99
+ ACCELERATION: 1,
100
+ BRAKING: 2,
101
+ LEFT_TURN: 3,
102
+ RIGHT_TURN: 3,
103
+ RAPID_ACCELERATION: 4,
104
+ SUDDEN_BRAKING: 5
105
+ };
106
+ //#endregion
107
+ //#region src/helper/sensor.ts
108
+ const extractFreshVector = (metric, capturedAtMs, maxSampleAgeMs) => {
109
+ if (!metric?.available || !Number.isFinite(capturedAtMs) || !Number.isFinite(metric.sampledAtMs)) return null;
110
+ const ageMs = capturedAtMs - metric.sampledAtMs;
111
+ if (ageMs < 0 || ageMs > maxSampleAgeMs) return null;
112
+ const { x, y, z } = metric.value;
113
+ return [
114
+ x,
115
+ y,
116
+ z
117
+ ].every(Number.isFinite) ? {
118
+ sampledAtMs: metric.sampledAtMs,
119
+ value: metric.value
120
+ } : null;
121
+ };
122
+ //#endregion
123
+ //#region src/helper/validation.ts
124
+ const requirePositiveFinite = (name, value, debug) => {
125
+ dialSdkDebugWarn(debug.moduleName, debug.scope, "called", {
126
+ name,
127
+ value
128
+ });
129
+ if (!Number.isFinite(value) || value <= 0) {
130
+ const error = /* @__PURE__ */ new RangeError(`${name} must be a positive finite number`);
131
+ dialSdkDebugWarn(debug.moduleName, debug.scope, "threw", error);
132
+ throw error;
133
+ }
134
+ dialSdkDebugWarn(debug.moduleName, debug.scope, "returned", { result: value });
135
+ return value;
136
+ };
137
+ const isRecord = (value, debug) => {
138
+ const result = typeof value === "object" && value !== null && !Array.isArray(value);
139
+ if (debug) dialSdkDebugWarn(debug.moduleName, debug.scope, "returned", {
140
+ value,
141
+ result
142
+ });
143
+ return result;
144
+ };
145
+ //#endregion
146
+ //#region src/driving_expression/check_status.ts
147
+ const DEBUG_MODULE$4 = "check_status.ts";
48
148
  const DEVICE_RIGHT = {
49
149
  x: 1,
50
150
  y: 0,
@@ -56,7 +156,7 @@ const ZERO_VECTOR$1 = {
56
156
  z: 0
57
157
  };
58
158
  const DEFAULT_DRIVING_STATUS_TUNING = Object.freeze({
59
- version: "driving-status-calibrated-2026-07",
159
+ version: "driving-status-calibrated-2026-07-17-r2",
60
160
  maxSampleAgeMs: 1e3,
61
161
  gravityFilterTimeConstantMs: 1e3,
62
162
  biasFilterTimeConstantMs: 1e4,
@@ -69,7 +169,7 @@ const DEFAULT_DRIVING_STATUS_TUNING = Object.freeze({
69
169
  stoppedRawGyroThreshold: .065,
70
170
  drivingGyroThreshold: .04,
71
171
  drivingRawGyroThreshold: .08,
72
- stoppedConfirmationMs: 5e3,
172
+ stoppedConfirmationMs: 1500,
73
173
  drivingConfirmationMs: 600,
74
174
  longitudinalWindowMs: 800,
75
175
  longitudinalMinimumSamples: 5,
@@ -86,9 +186,9 @@ const DEFAULT_DRIVING_STATUS_TUNING = Object.freeze({
86
186
  verticalBumpRatio: 1.25,
87
187
  longitudinalReversalWindowMs: 400,
88
188
  turnWindowMs: 1500,
89
- turnMinimumSamples: 4,
90
- turnYawThreshold: .12,
91
- turnConfirmationMs: 600,
189
+ turnMinimumSamples: 3,
190
+ turnYawThreshold: .08,
191
+ turnConfirmationMs: 400,
92
192
  turnIntegratedAngleThreshold: .12,
93
193
  turnSignCoherence: .7,
94
194
  turnYawDominanceThreshold: .6,
@@ -100,14 +200,14 @@ const DEFAULT_DRIVING_STATUS_TUNING = Object.freeze({
100
200
  turnClosureWindowMs: 1500
101
201
  });
102
202
  const classifyDrivingSnapshot = (info, tuningOverrides) => {
103
- dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingSnapshot", "called", {
203
+ dialSdkDebugWarn(DEBUG_MODULE$4, "classifyDrivingSnapshot", "called", {
104
204
  info,
105
205
  tuningOverrides
106
206
  });
107
207
  const tuning = resolveDrivingStatusTuning(tuningOverrides);
108
208
  const metrics = extractDrivingSnapshotMetrics(info, tuning);
109
209
  if (metrics === null) {
110
- dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingSnapshot", "returned", { result: null });
210
+ dialSdkDebugWarn(DEBUG_MODULE$4, "classifyDrivingSnapshot", "returned", { result: null });
111
211
  return null;
112
212
  }
113
213
  const { candidateStatus, rejectionReason } = classifyInstantaneousEvidence(metrics, tuning);
@@ -121,11 +221,11 @@ const classifyDrivingSnapshot = (info, tuningOverrides) => {
121
221
  maximumSampleAgeMs: Math.max(info.capturedAtMs - metrics.linearAccelerationSampledAtMs, info.capturedAtMs - metrics.gyroscopeSampledAtMs, info.capturedAtMs - metrics.gravitySampledAtMs)
122
222
  }
123
223
  };
124
- dialSdkDebugWarn(DEBUG_MODULE$5, "classifyDrivingSnapshot", "returned", { result });
224
+ dialSdkDebugWarn(DEBUG_MODULE$4, "classifyDrivingSnapshot", "returned", { result });
125
225
  return result;
126
226
  };
127
227
  const resolveDrivingStatusTuning = (overrides = {}) => {
128
- dialSdkDebugWarn(DEBUG_MODULE$5, "resolveDrivingStatusTuning", "called", { overrides });
228
+ dialSdkDebugWarn(DEBUG_MODULE$4, "resolveDrivingStatusTuning", "called", { overrides });
129
229
  const tuning = {
130
230
  ...DEFAULT_DRIVING_STATUS_TUNING,
131
231
  ...overrides
@@ -164,7 +264,10 @@ const resolveDrivingStatusTuning = (overrides = {}) => {
164
264
  "turnExitYawThreshold",
165
265
  "turnExitConfirmationMs",
166
266
  "turnClosureWindowMs"
167
- ]) assertPositiveFinite(field, tuning[field]);
267
+ ]) requirePositiveFinite(field, tuning[field], {
268
+ moduleName: DEBUG_MODULE$4,
269
+ scope: "assertPositiveFinite"
270
+ });
168
271
  for (const field of [
169
272
  "motionMinimumSamples",
170
273
  "longitudinalMinimumSamples",
@@ -187,28 +290,28 @@ const resolveDrivingStatusTuning = (overrides = {}) => {
187
290
  if (tuning.rapidLongitudinalMeanThreshold <= tuning.longitudinalThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "rapidLongitudinalMeanThreshold must be greater than longitudinalThreshold");
188
291
  if (tuning.rapidLongitudinalPeakThreshold < tuning.rapidLongitudinalMeanThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "rapidLongitudinalPeakThreshold must be greater than or equal to rapidLongitudinalMeanThreshold");
189
292
  if (tuning.turnYawThreshold <= tuning.turnExitYawThreshold) throwLoggedRangeError("resolveDrivingStatusTuning", "turnYawThreshold must be greater than turnExitYawThreshold");
190
- dialSdkDebugWarn(DEBUG_MODULE$5, "resolveDrivingStatusTuning", "returned", { tuning });
293
+ dialSdkDebugWarn(DEBUG_MODULE$4, "resolveDrivingStatusTuning", "returned", { tuning });
191
294
  return tuning;
192
295
  };
193
296
  const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUNING, correction = {}) => {
194
- dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "called", {
297
+ dialSdkDebugWarn(DEBUG_MODULE$4, "extractDrivingSnapshotMetrics", "called", {
195
298
  info,
196
299
  tuning,
197
300
  correction
198
301
  });
199
302
  if (!info || !Number.isFinite(info.capturedAtMs)) {
200
- dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "returned invalid capture time", { info });
303
+ dialSdkDebugWarn(DEBUG_MODULE$4, "extractDrivingSnapshotMetrics", "returned invalid capture time", { info });
201
304
  return null;
202
305
  }
203
- const linearAcceleration = availableFreshVector(info.linearAcceleration, info.capturedAtMs, tuning.maxSampleAgeMs);
204
- const gyroscope = availableFreshVector(info.gyroscope, info.capturedAtMs, tuning.maxSampleAgeMs);
205
- const gravityMetric = availableFreshVector(info.gravity, info.capturedAtMs, tuning.maxSampleAgeMs);
306
+ const linearAcceleration = extractFreshVector(info.linearAcceleration, info.capturedAtMs, tuning.maxSampleAgeMs);
307
+ const gyroscope = extractFreshVector(info.gyroscope, info.capturedAtMs, tuning.maxSampleAgeMs);
308
+ const gravityMetric = extractFreshVector(info.gravity, info.capturedAtMs, tuning.maxSampleAgeMs);
206
309
  const gravity = availableCorrectedGravity(correction, info.capturedAtMs, tuning.maxSampleAgeMs) ?? (correction.filteredGravity !== void 0 && gravityMetric !== null ? {
207
310
  value: correction.filteredGravity,
208
311
  sampledAtMs: gravityMetric.sampledAtMs
209
312
  } : gravityMetric);
210
313
  if (linearAcceleration === null || gyroscope === null || gravity === null) {
211
- dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "returned unavailable vectors", {
314
+ dialSdkDebugWarn(DEBUG_MODULE$4, "extractDrivingSnapshotMetrics", "returned unavailable vectors", {
212
315
  linearAcceleration,
213
316
  gyroscope,
214
317
  gravity
@@ -217,7 +320,7 @@ const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUN
217
320
  }
218
321
  const frame = buildVehicleFrame(gravity.value, tuning.vehicleRightMinimumNorm);
219
322
  if (frame === null) {
220
- dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "returned invalid vehicle frame", {
323
+ dialSdkDebugWarn(DEBUG_MODULE$4, "extractDrivingSnapshotMetrics", "returned invalid vehicle frame", {
221
324
  gravity: gravity.value,
222
325
  vehicleRightMinimumNorm: tuning.vehicleRightMinimumNorm
223
326
  });
@@ -240,28 +343,28 @@ const extractDrivingSnapshotMetrics = (info, tuning = DEFAULT_DRIVING_STATUS_TUN
240
343
  rawGyroscope: gyroscope.value,
241
344
  frame
242
345
  };
243
- dialSdkDebugWarn(DEBUG_MODULE$5, "extractDrivingSnapshotMetrics", "returned", { result });
346
+ dialSdkDebugWarn(DEBUG_MODULE$4, "extractDrivingSnapshotMetrics", "returned", { result });
244
347
  return result;
245
348
  };
246
349
  const buildVehicleFrame = (gravity, rightMinimumNorm = DEFAULT_DRIVING_STATUS_TUNING.vehicleRightMinimumNorm) => {
247
- dialSdkDebugWarn(DEBUG_MODULE$5, "buildVehicleFrame", "called", {
350
+ dialSdkDebugWarn(DEBUG_MODULE$4, "buildVehicleFrame", "called", {
248
351
  gravity,
249
352
  rightMinimumNorm
250
353
  });
251
354
  const gravityDirection = normalize(gravity);
252
355
  if (gravityDirection === null) {
253
- dialSdkDebugWarn(DEBUG_MODULE$5, "buildVehicleFrame", "returned invalid gravity", { result: null });
356
+ dialSdkDebugWarn(DEBUG_MODULE$4, "buildVehicleFrame", "returned invalid gravity", { result: null });
254
357
  return null;
255
358
  }
256
359
  const up = gravityDirection;
257
360
  const projectedRight = subtract(DEVICE_RIGHT, scale(up, dot(DEVICE_RIGHT, up)));
258
361
  if (magnitude(projectedRight) < rightMinimumNorm) {
259
- dialSdkDebugWarn(DEBUG_MODULE$5, "buildVehicleFrame", "returned degenerate right axis", { result: null });
362
+ dialSdkDebugWarn(DEBUG_MODULE$4, "buildVehicleFrame", "returned degenerate right axis", { result: null });
260
363
  return null;
261
364
  }
262
365
  const right = normalize(projectedRight);
263
366
  if (right === null) {
264
- dialSdkDebugWarn(DEBUG_MODULE$5, "buildVehicleFrame", "returned invalid right axis", { result: null });
367
+ dialSdkDebugWarn(DEBUG_MODULE$4, "buildVehicleFrame", "returned invalid right axis", { result: null });
265
368
  return null;
266
369
  }
267
370
  const forward = normalize(cross(right, up));
@@ -270,7 +373,7 @@ const buildVehicleFrame = (gravity, rightMinimumNorm = DEFAULT_DRIVING_STATUS_TU
270
373
  right,
271
374
  forward
272
375
  };
273
- dialSdkDebugWarn(DEBUG_MODULE$5, "buildVehicleFrame", "returned", { result });
376
+ dialSdkDebugWarn(DEBUG_MODULE$4, "buildVehicleFrame", "returned", { result });
274
377
  return result;
275
378
  };
276
379
  const classifyInstantaneousEvidence = (metrics, tuning) => {
@@ -298,18 +401,6 @@ const classifyInstantaneousEvidence = (metrics, tuning) => {
298
401
  rejectionReason: null
299
402
  };
300
403
  };
301
- const availableFreshVector = (metric, capturedAtMs, maxSampleAgeMs) => {
302
- if (!metric?.available || !isFresh(metric.sampledAtMs, capturedAtMs, maxSampleAgeMs)) return null;
303
- const { x, y, z } = metric.value;
304
- return [
305
- x,
306
- y,
307
- z
308
- ].every(Number.isFinite) ? {
309
- value: metric.value,
310
- sampledAtMs: metric.sampledAtMs
311
- } : null;
312
- };
313
404
  const availableCorrectedGravity = (correction, capturedAtMs, maxSampleAgeMs) => {
314
405
  const { filteredGravity, filteredGravitySampledAtMs } = correction;
315
406
  if (filteredGravity === void 0 || filteredGravitySampledAtMs === void 0 || !isFresh(filteredGravitySampledAtMs, capturedAtMs, maxSampleAgeMs)) return null;
@@ -349,80 +440,57 @@ const normalize = (value) => {
349
440
  const length = magnitude(value);
350
441
  return Number.isFinite(length) && length > Number.EPSILON ? scale(value, 1 / length) : null;
351
442
  };
352
- const assertPositiveFinite = (name, value) => {
353
- if (!Number.isFinite(value) || value <= 0) {
354
- const error = /* @__PURE__ */ new RangeError(`${name} must be a positive finite number`);
355
- dialSdkDebugWarn(DEBUG_MODULE$5, "assertPositiveFinite", "threw", error);
356
- throw error;
357
- }
358
- };
359
443
  const throwLoggedRangeError = (scope, message) => {
360
444
  const error = new RangeError(message);
361
- dialSdkDebugWarn(DEBUG_MODULE$5, scope, "threw", error);
445
+ dialSdkDebugWarn(DEBUG_MODULE$4, scope, "threw", error);
362
446
  throw error;
363
447
  };
364
448
  //#endregion
365
- //#region src/sensor/unified_info.ts
366
- const DEBUG_MODULE$4 = "unified_info.ts";
367
- const unifiedSensorInfo = () => {
368
- dialSdkDebugWarn(DEBUG_MODULE$4, "unifiedSensorInfo", "called");
449
+ //#region src/helper/async.ts
450
+ const createAbortError = (message, debug) => {
451
+ dialSdkDebugWarn(debug.moduleName, debug.scope, "called");
452
+ if (typeof DOMException === "function") {
453
+ const result = new DOMException(message, "AbortError");
454
+ dialSdkDebugWarn(debug.moduleName, debug.scope, "returned DOMException", { result });
455
+ return result;
456
+ }
457
+ const error = new Error(message);
458
+ error.name = "AbortError";
459
+ dialSdkDebugWarn(debug.moduleName, debug.scope, "returned Error", { result: error });
460
+ return error;
461
+ };
462
+ const isAbortError = (error, debug) => {
463
+ const result = error instanceof Error && error.name === "AbortError";
464
+ if (debug) dialSdkDebugWarn(debug.moduleName, debug.scope, "returned", {
465
+ error,
466
+ result
467
+ });
468
+ return result;
469
+ };
470
+ const invokeSafely = (callback, args, onError) => {
369
471
  try {
370
- return window.unifiedSensorInfo().then((payload) => {
371
- dialSdkDebugWarn(DEBUG_MODULE$4, "unifiedSensorInfo", "resolved", { payload });
372
- return payload;
373
- }, (error) => {
374
- dialSdkDebugWarn(DEBUG_MODULE$4, "unifiedSensorInfo", "rejected", error);
375
- throw error;
376
- });
472
+ callback(...args);
473
+ return true;
377
474
  } catch (error) {
378
- dialSdkDebugWarn(DEBUG_MODULE$4, "unifiedSensorInfo", "threw", error);
379
- throw error;
475
+ onError(error);
476
+ return false;
380
477
  }
381
478
  };
382
479
  //#endregion
383
- //#region src/sensor/constants.ts
384
- const BATTERY_HEALTH_LABELS = {
385
- GOOD: "良好",
386
- OVERHEAT: "过热",
387
- DEAD: "失效",
388
- OVER_VOLTAGE: "电压过高",
389
- UNSPECIFIED_FAILURE: "异常",
390
- COLD: "温度过低",
391
- UNKNOWN: "未知"
480
+ //#region src/driving_expression/driving_expressions.ts
481
+ const DEBUG_MODULE$3 = "driving_expressions.ts";
482
+ const IS_RECORD_DEBUG$1 = {
483
+ moduleName: DEBUG_MODULE$3,
484
+ scope: "isRecord"
392
485
  };
393
- const CAR_RUNNING_LABELS = {
394
- STOPPED: "静止",
395
- STEADY_DRIVING: "匀速行驶",
396
- SUDDEN_BRAKING: "急刹车",
397
- BRAKING: "刹车",
398
- LEFT_TURN: "左转",
399
- RIGHT_TURN: "右转",
400
- ACCELERATION: "加速",
401
- RAPID_ACCELERATION: "急加速"
486
+ const REQUIRE_POSITIVE_FINITE_DEBUG$1 = {
487
+ moduleName: DEBUG_MODULE$3,
488
+ scope: "requirePositiveFinite"
402
489
  };
403
- const CAR_RUNNING_STATUSES = [
404
- "STOPPED",
405
- "STEADY_DRIVING",
406
- "ACCELERATION",
407
- "RAPID_ACCELERATION",
408
- "BRAKING",
409
- "SUDDEN_BRAKING",
410
- "LEFT_TURN",
411
- "RIGHT_TURN"
412
- ];
413
- const CAR_RUNNING_STATUS_PRIORITY = {
414
- STOPPED: 0,
415
- STEADY_DRIVING: 0,
416
- ACCELERATION: 1,
417
- BRAKING: 2,
418
- LEFT_TURN: 3,
419
- RIGHT_TURN: 3,
420
- RAPID_ACCELERATION: 4,
421
- SUDDEN_BRAKING: 5
490
+ const CREATE_ABORT_ERROR_DEBUG$1 = {
491
+ moduleName: DEBUG_MODULE$3,
492
+ scope: "createAbortError"
422
493
  };
423
- //#endregion
424
- //#region src/sensor/driving_expressions.ts
425
- const DEBUG_MODULE$3 = "driving_expressions.ts";
426
494
  const OFFICIAL_DRIVING_EXPRESSION_FALLBACKS = Object.freeze({
427
495
  STOPPED: Object.freeze({
428
496
  kind: "emoji",
@@ -490,7 +558,7 @@ const parseDrivingExpressionsConfig = (raw, options = {}) => {
490
558
  options
491
559
  });
492
560
  const parsed = parseJsonObject(raw);
493
- if (parsed.schemaVersion !== 1 || !isRecord$1(parsed.states)) throw new DrivingExpressionsConfigError("DRIVING_EXPRESSIONS_INVALID_SCHEMA", "drivingExpressions must use schemaVersion 1 and contain a states object");
561
+ if (parsed.schemaVersion !== 1 || !isRecord(parsed.states, IS_RECORD_DEBUG$1)) throw new DrivingExpressionsConfigError("DRIVING_EXPRESSIONS_INVALID_SCHEMA", "drivingExpressions must use schemaVersion 1 and contain a states object");
494
562
  const knownStatuses = new Set(CAR_RUNNING_STATUSES);
495
563
  const stateKeys = Object.keys(parsed.states);
496
564
  if (stateKeys.length !== CAR_RUNNING_STATUSES.length || stateKeys.some((key) => !knownStatuses.has(key))) throw new DrivingExpressionsConfigError("DRIVING_EXPRESSIONS_INCOMPLETE", "drivingExpressions.states must contain exactly the eight supported statuses");
@@ -516,12 +584,16 @@ const tryParseDrivingExpressionsConfig = (raw, options) => {
516
584
  dialSdkDebugWarn(DEBUG_MODULE$3, "tryParseDrivingExpressionsConfig", "returned", { result });
517
585
  return result;
518
586
  } catch (error) {
587
+ if (!(error instanceof DrivingExpressionsConfigError)) {
588
+ dialSdkDebugWarn(DEBUG_MODULE$3, "tryParseDrivingExpressionsConfig", "threw unexpected error", error);
589
+ throw error;
590
+ }
519
591
  dialSdkDebugWarn(DEBUG_MODULE$3, "tryParseDrivingExpressionsConfig", "returned null after parse error", error);
520
592
  return null;
521
593
  }
522
594
  };
523
595
  /**
524
- * Reads Runtime's typed injection first, then the legacy face-config value.
596
+ * Reads Runtime's typed driving-expressions injection.
525
597
  * It never installs official fallbacks implicitly.
526
598
  */
527
599
  const readDrivingExpressionsConfig = (raw, options) => {
@@ -544,7 +616,7 @@ const readDrivingExpressionsConfig = (raw, options) => {
544
616
  const waitForDrivingExpressionsConfig = (options = {}) => {
545
617
  dialSdkDebugWarn(DEBUG_MODULE$3, "waitForDrivingExpressionsConfig", "called", { options });
546
618
  const timeoutMs = requireNonNegativeFinite("timeoutMs", options.timeoutMs ?? 3e3);
547
- const pollIntervalMs = requirePositiveFinite$1("pollIntervalMs", options.pollIntervalMs ?? 100);
619
+ const pollIntervalMs = requirePositiveFinite("pollIntervalMs", options.pollIntervalMs ?? 100, REQUIRE_POSITIVE_FINITE_DEBUG$1);
548
620
  const { signal } = options;
549
621
  return new Promise((resolve, reject) => {
550
622
  const deadlineMs = Date.now() + timeoutMs;
@@ -592,7 +664,7 @@ const waitForDrivingExpressionsConfig = (options = {}) => {
592
664
  };
593
665
  const handleAbort = () => {
594
666
  dialSdkDebugWarn(DEBUG_MODULE$3, "waitForDrivingExpressionsConfig.handleAbort", "called");
595
- fail(createAbortError$1());
667
+ fail(createAbortError("The operation was aborted", CREATE_ABORT_ERROR_DEBUG$1));
596
668
  };
597
669
  const read = () => {
598
670
  dialSdkDebugWarn(DEBUG_MODULE$3, "waitForDrivingExpressionsConfig.read", "called");
@@ -610,7 +682,13 @@ const waitForDrivingExpressionsConfig = (options = {}) => {
610
682
  handleAbort();
611
683
  return;
612
684
  }
613
- const config = read();
685
+ let config;
686
+ try {
687
+ config = read();
688
+ } catch (error) {
689
+ fail(error);
690
+ return;
691
+ }
614
692
  if (config !== null) {
615
693
  finish(config);
616
694
  return;
@@ -905,7 +983,7 @@ const parseDrivingExpressionMedia = (raw, status, options) => {
905
983
  dialSdkDebugWarn(DEBUG_MODULE$3, "parseDrivingExpressionMedia", "returned legacy image", { result });
906
984
  return result;
907
985
  }
908
- if (!isRecord$1(raw) || typeof raw.kind !== "string") throw invalidMedia(status, "media must be a structured object with a supported kind");
986
+ if (!isRecord(raw, IS_RECORD_DEBUG$1) || typeof raw.kind !== "string") throw invalidMedia(status, "media must be a structured object with a supported kind");
909
987
  const mediaAssetId = parseMediaAssetId(raw.mediaAssetId, status);
910
988
  assertFieldsAbsent(raw, status, [
911
989
  "items",
@@ -1011,7 +1089,7 @@ const parseJsonObject = (raw) => {
1011
1089
  });
1012
1090
  throw error;
1013
1091
  }
1014
- if (!isRecord$1(value)) {
1092
+ if (!isRecord(value, IS_RECORD_DEBUG$1)) {
1015
1093
  const error = new DrivingExpressionsConfigError("DRIVING_EXPRESSIONS_INVALID_SCHEMA", "drivingExpressions must be a JSON object or JSON object string");
1016
1094
  dialSdkDebugWarn(DEBUG_MODULE$3, "parseJsonObject", "threw", error);
1017
1095
  throw error;
@@ -1021,24 +1099,8 @@ const parseJsonObject = (raw) => {
1021
1099
  };
1022
1100
  const readRuntimeDrivingExpressionsValue = () => {
1023
1101
  dialSdkDebugWarn(DEBUG_MODULE$3, "readRuntimeDrivingExpressionsValue", "called");
1024
- const runtimeGlobal = globalThis;
1025
- if (runtimeGlobal.__TOOOONY_DRIVING_EXPRESSIONS__ !== void 0) {
1026
- const result = runtimeGlobal.__TOOOONY_DRIVING_EXPRESSIONS__;
1027
- dialSdkDebugWarn(DEBUG_MODULE$3, "readRuntimeDrivingExpressionsValue", "returned typed injection", { result });
1028
- return result;
1029
- }
1030
- const faceConfig = runtimeGlobal.__TOOOONY_FACE_CONFIG__;
1031
- if (!isRecord$1(faceConfig)) {
1032
- dialSdkDebugWarn(DEBUG_MODULE$3, "readRuntimeDrivingExpressionsValue", "returned", { result: void 0 });
1033
- return;
1034
- }
1035
- if (faceConfig.drivingExpressions !== void 0) {
1036
- const result = faceConfig.drivingExpressions;
1037
- dialSdkDebugWarn(DEBUG_MODULE$3, "readRuntimeDrivingExpressionsValue", "returned face config", { result });
1038
- return result;
1039
- }
1040
- const result = isRecord$1(faceConfig.values) ? faceConfig.values.drivingExpressions : void 0;
1041
- dialSdkDebugWarn(DEBUG_MODULE$3, "readRuntimeDrivingExpressionsValue", "returned legacy face config", { result });
1102
+ const result = globalThis.__TOOOONY_DRIVING_EXPRESSIONS__;
1103
+ dialSdkDebugWarn(DEBUG_MODULE$3, "readRuntimeDrivingExpressionsValue", "returned typed injection", { result });
1042
1104
  return result;
1043
1105
  };
1044
1106
  const parseMediaAssetId = (raw, status) => {
@@ -1159,14 +1221,6 @@ const compact = (value) => {
1159
1221
  dialSdkDebugWarn(DEBUG_MODULE$3, "compact", "returned", { result: value });
1160
1222
  return value;
1161
1223
  };
1162
- const isRecord$1 = (value) => {
1163
- const result = typeof value === "object" && value !== null && !Array.isArray(value);
1164
- dialSdkDebugWarn(DEBUG_MODULE$3, "isRecord", "returned", {
1165
- value,
1166
- result
1167
- });
1168
- return result;
1169
- };
1170
1224
  const requireNonNegativeFinite = (name, value) => {
1171
1225
  dialSdkDebugWarn(DEBUG_MODULE$3, "requireNonNegativeFinite", "called", {
1172
1226
  name,
@@ -1180,33 +1234,8 @@ const requireNonNegativeFinite = (name, value) => {
1180
1234
  dialSdkDebugWarn(DEBUG_MODULE$3, "requireNonNegativeFinite", "returned", { result: value });
1181
1235
  return value;
1182
1236
  };
1183
- const requirePositiveFinite$1 = (name, value) => {
1184
- dialSdkDebugWarn(DEBUG_MODULE$3, "requirePositiveFinite", "called", {
1185
- name,
1186
- value
1187
- });
1188
- if (!Number.isFinite(value) || value <= 0) {
1189
- const error = /* @__PURE__ */ new RangeError(`${name} must be a positive finite number`);
1190
- dialSdkDebugWarn(DEBUG_MODULE$3, "requirePositiveFinite", "threw", error);
1191
- throw error;
1192
- }
1193
- dialSdkDebugWarn(DEBUG_MODULE$3, "requirePositiveFinite", "returned", { result: value });
1194
- return value;
1195
- };
1196
- const createAbortError$1 = () => {
1197
- dialSdkDebugWarn(DEBUG_MODULE$3, "createAbortError", "called");
1198
- if (typeof DOMException === "function") {
1199
- const result = new DOMException("The operation was aborted", "AbortError");
1200
- dialSdkDebugWarn(DEBUG_MODULE$3, "createAbortError", "returned DOMException", { result });
1201
- return result;
1202
- }
1203
- const error = /* @__PURE__ */ new Error("The operation was aborted");
1204
- error.name = "AbortError";
1205
- dialSdkDebugWarn(DEBUG_MODULE$3, "createAbortError", "returned Error", { result: error });
1206
- return error;
1207
- };
1208
1237
  //#endregion
1209
- //#region src/sensor/page_lifecycle.ts
1238
+ //#region src/driving_expression/page_lifecycle.ts
1210
1239
  const DEBUG_MODULE$2 = "page_lifecycle.ts";
1211
1240
  const subscribers = /* @__PURE__ */ new Set();
1212
1241
  let installedWindow = null;
@@ -1339,21 +1368,22 @@ const dispatchResume = () => {
1339
1368
  };
1340
1369
  const safeInvoke = (callback) => {
1341
1370
  dialSdkDebugWarn(DEBUG_MODULE$2, "safeInvoke", "called");
1342
- try {
1343
- callback();
1344
- dialSdkDebugWarn(DEBUG_MODULE$2, "safeInvoke", "returned");
1345
- } catch (error) {
1371
+ if (invokeSafely(callback, [], (error) => {
1346
1372
  dialSdkDebugWarn(DEBUG_MODULE$2, "safeInvoke", "callback threw", error);
1347
- }
1373
+ })) dialSdkDebugWarn(DEBUG_MODULE$2, "safeInvoke", "returned");
1348
1374
  };
1349
1375
  //#endregion
1350
- //#region src/sensor/driving_status_controller.ts
1376
+ //#region src/driving_expression/driving_status_controller.ts
1351
1377
  const DEBUG_MODULE$1 = "driving_status_controller.ts";
1352
1378
  const ZERO_VECTOR = {
1353
1379
  x: 0,
1354
1380
  y: 0,
1355
1381
  z: 0
1356
1382
  };
1383
+ const REQUIRE_POSITIVE_FINITE_DEBUG = {
1384
+ moduleName: DEBUG_MODULE$1,
1385
+ scope: "requirePositiveFinite"
1386
+ };
1357
1387
  const createDrivingStatusController = (options = {}) => {
1358
1388
  dialSdkDebugWarn(DEBUG_MODULE$1, "createDrivingStatusController", "called", { options });
1359
1389
  const result = new DefaultDrivingStatusController(options);
@@ -1437,9 +1467,9 @@ var DefaultDrivingStatusController = class {
1437
1467
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.handleLifecycleResume", "returned");
1438
1468
  };
1439
1469
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.constructor", "called", { options });
1440
- this.sensorProvider = options.sensorProvider ?? defaultSensorProvider;
1441
- this.pollIntervalMs = requirePositiveFinite("pollIntervalMs", options.pollIntervalMs ?? 200);
1442
- this.unavailableFallbackMs = requirePositiveFinite("unavailableFallbackMs", options.unavailableFallbackMs ?? 3e3);
1470
+ this.sensorProvider = options.sensorProvider ?? unifiedSensorInfo;
1471
+ this.pollIntervalMs = requirePositiveFinite("pollIntervalMs", options.pollIntervalMs ?? 200, REQUIRE_POSITIVE_FINITE_DEBUG);
1472
+ this.unavailableFallbackMs = requirePositiveFinite("unavailableFallbackMs", options.unavailableFallbackMs ?? 3e3, REQUIRE_POSITIVE_FINITE_DEBUG);
1443
1473
  this.tuning = resolveDrivingStatusTuning(options.tuning);
1444
1474
  this.clock = options.clock ?? systemClock;
1445
1475
  this.onDiagnostic = options.onDiagnostic;
@@ -1580,6 +1610,7 @@ var DefaultDrivingStatusController = class {
1580
1610
  }, delayMs);
1581
1611
  }
1582
1612
  async poll(generation) {
1613
+ const pollStartedAtMs = this.clock.now();
1583
1614
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "called", { generation });
1584
1615
  if (!this.polling || generation !== this.generation || this.pendingRead !== null) {
1585
1616
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "ignored", {
@@ -1610,7 +1641,7 @@ var DefaultDrivingStatusController = class {
1610
1641
  linearAccelerationBias: this.linearAccelerationBias,
1611
1642
  gyroscopeBias: this.gyroscopeBias
1612
1643
  });
1613
- const accelerometerSample = extractFreshVectorMetric(payload.accelerometer, payload.capturedAtMs, this.tuning.maxSampleAgeMs);
1644
+ const accelerometerSample = extractFreshVector(payload.accelerometer, payload.capturedAtMs, this.tuning.maxSampleAgeMs);
1614
1645
  if (gravity === null || metrics === null || accelerometerSample === null) {
1615
1646
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "invalid or stale snapshot", {
1616
1647
  metrics,
@@ -1622,7 +1653,7 @@ var DefaultDrivingStatusController = class {
1622
1653
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "processed sample", { capturedAtMs: metrics.capturedAtMs });
1623
1654
  }
1624
1655
  } catch (error) {
1625
- if (!this.polling || generation !== this.generation || pendingRead.signal.aborted || isAbortError$1(error)) {
1656
+ if (!this.polling || generation !== this.generation || pendingRead.signal.aborted || isAbortError(error)) {
1626
1657
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "ignored provider error", { error });
1627
1658
  return;
1628
1659
  }
@@ -1630,7 +1661,10 @@ var DefaultDrivingStatusController = class {
1630
1661
  this.processUnavailable("provider_error");
1631
1662
  } finally {
1632
1663
  if (this.pendingRead === pendingRead) this.pendingRead = null;
1633
- if (this.polling && generation === this.generation) this.schedulePoll(this.pollIntervalMs, generation);
1664
+ if (this.polling && generation === this.generation) {
1665
+ const elapsedMs = Math.max(0, this.clock.now() - pollStartedAtMs);
1666
+ this.schedulePoll(Math.max(0, this.pollIntervalMs - elapsedMs), generation);
1667
+ }
1634
1668
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.poll", "completed", {
1635
1669
  generation,
1636
1670
  polling: this.polling
@@ -1638,7 +1672,7 @@ var DefaultDrivingStatusController = class {
1638
1672
  }
1639
1673
  }
1640
1674
  resolveGravityFrame(info) {
1641
- const sample = extractFreshVectorMetric(info.gravity, info.capturedAtMs, this.tuning.maxSampleAgeMs);
1675
+ const sample = extractFreshVector(info.gravity, info.capturedAtMs, this.tuning.maxSampleAgeMs);
1642
1676
  if (sample === null) {
1643
1677
  if (this.filteredGravity === null || this.lastGravitySampledAtMs === null) return null;
1644
1678
  const ageMs = info.capturedAtMs - this.lastGravitySampledAtMs;
@@ -1691,19 +1725,23 @@ var DefaultDrivingStatusController = class {
1691
1725
  frameAgeMs: metrics.capturedAtMs - gravity.sampledAtMs,
1692
1726
  tuningVersion: this.tuning.version
1693
1727
  });
1694
- const addedLongitudinal = pushUniqueMetric(this.longitudinalWindow, metrics, "linearAccelerationSampledAtMs", Math.max(this.tuning.longitudinalWindowMs, this.tuning.rapidLongitudinalWindowMs));
1695
- const addedYaw = pushUniqueMetric(this.yawWindow, metrics, "gyroscopeSampledAtMs", Math.max(this.tuning.turnWindowMs, this.tuning.turnClosureWindowMs));
1696
- const addedMotion = pushUniqueVector(this.motionWindow, accelerometerSample, this.tuning.motionWindowMs);
1728
+ const longitudinalRetentionMs = Math.max(recognitionRetentionMs(this.tuning.longitudinalWindowMs, this.tuning.longitudinalMinimumSamples), recognitionRetentionMs(this.tuning.rapidLongitudinalWindowMs, this.tuning.rapidLongitudinalMinimumSamples));
1729
+ const addedLongitudinal = pushUniqueMetric(this.longitudinalWindow, metrics, "linearAccelerationSampledAtMs", longitudinalRetentionMs);
1730
+ const yawRetentionMs = Math.max(this.tuning.turnWindowMs, this.tuning.turnClosureWindowMs, recognitionRetentionMs(this.tuning.turnConfirmationMs, this.tuning.turnMinimumSamples));
1731
+ const addedYaw = pushUniqueMetric(this.yawWindow, metrics, "gyroscopeSampledAtMs", yawRetentionMs);
1732
+ const addedMotion = pushUniqueVector(this.motionWindow, accelerometerSample, recognitionRetentionMs(this.tuning.motionWindowMs, this.tuning.motionMinimumSamples));
1697
1733
  if (!addedLongitudinal && !addedYaw && !addedMotion) {
1698
1734
  dialSdkDebugWarn(DEBUG_MODULE$1, "DefaultDrivingStatusController.processAvailable", "ignored duplicate sample");
1699
1735
  return;
1700
1736
  }
1701
- const motionMad = calculateCombinedAxisMad(this.motionWindow, this.tuning.motionMinimumSamples);
1737
+ const motionCoverageWindow = selectCoverageWindow(this.motionWindow, this.tuning.motionMinimumSamples, this.tuning.motionWindowMs, (sample) => sample.sampledAtMs);
1738
+ const motionStatisticsWindow = motionCoverageWindow ?? recentVectors(this.motionWindow, this.tuning.motionWindowMs);
1739
+ const motionMad = motionCoverageWindow === null ? null : calculateCombinedAxisMad(motionCoverageWindow, minimumTemporalSamples(this.tuning.motionMinimumSamples));
1702
1740
  const previousBaseStatus = this.baseStatus;
1703
1741
  const previousUrgentStatus = this.urgentStatus;
1704
1742
  const previousLongitudinalStatus = this.normalLongitudinalStatus;
1705
1743
  const previousTurnStatus = this.turnStatus;
1706
- const baseCondition = this.updateBaseDetector(motionMad, metrics, accelerometerSample.sampledAtMs, addedMotion);
1744
+ const baseCondition = this.updateBaseDetector(motionMad, metrics, accelerometerSample.sampledAtMs, addedMotion, motionCoverageWindow?.[0]?.sampledAtMs ?? accelerometerSample.sampledAtMs);
1707
1745
  const longitudinalEvidence = detectLongitudinalEvidence(this.longitudinalWindow, this.tuning);
1708
1746
  if (addedLongitudinal) this.updateLongitudinalDetector(longitudinalEvidence, metrics.linearAccelerationSampledAtMs);
1709
1747
  const turnEvidence = detectTurnEvidence(this.yawWindow, this.tuning);
@@ -1716,8 +1754,8 @@ var DefaultDrivingStatusController = class {
1716
1754
  if (this.stationaryConfirmed) this.updateBias(metrics);
1717
1755
  const statistics = {
1718
1756
  base: {
1719
- sampleCount: this.motionWindow.length,
1720
- durationMs: vectorWindowDuration(this.motionWindow),
1757
+ sampleCount: motionStatisticsWindow.length,
1758
+ durationMs: vectorWindowDuration(motionStatisticsWindow),
1721
1759
  motionMad,
1722
1760
  gyroMagnitude: metrics.gyroMagnitude,
1723
1761
  rawGyroMagnitude: metrics.rawGyroMagnitude
@@ -1763,7 +1801,7 @@ var DefaultDrivingStatusController = class {
1763
1801
  normalLongitudinalStatus: this.normalLongitudinalStatus
1764
1802
  });
1765
1803
  }
1766
- updateBaseDetector(motionMad, metrics, sampledAtMs, isNewMotionSample) {
1804
+ updateBaseDetector(motionMad, metrics, sampledAtMs, isNewMotionSample, conditionStartedAtMs) {
1767
1805
  if (motionMad === null || !isNewMotionSample) return "warming_up";
1768
1806
  const gyroForStop = this.biasCalibrated ? metrics.gyroMagnitude : metrics.rawGyroMagnitude;
1769
1807
  const stoppedGyroThreshold = this.biasCalibrated ? this.tuning.stoppedGyroThreshold : this.tuning.stoppedRawGyroThreshold;
@@ -1772,7 +1810,7 @@ var DefaultDrivingStatusController = class {
1772
1810
  const moving = motionMad >= this.tuning.drivingMotionThreshold || gyroForStop >= drivingGyroThreshold;
1773
1811
  if (stationary) {
1774
1812
  this.drivingConditionSinceMs = null;
1775
- if (this.stoppedConditionSinceMs === null) this.stoppedConditionSinceMs = this.motionWindow[0]?.sampledAtMs ?? sampledAtMs;
1813
+ if (this.stoppedConditionSinceMs === null) this.stoppedConditionSinceMs = conditionStartedAtMs;
1776
1814
  if (sampledAtMs - this.stoppedConditionSinceMs >= this.tuning.stoppedConfirmationMs) {
1777
1815
  this.baseStatus = "STOPPED";
1778
1816
  this.stationaryConfirmed = true;
@@ -1782,7 +1820,7 @@ var DefaultDrivingStatusController = class {
1782
1820
  this.stoppedConditionSinceMs = null;
1783
1821
  this.stationaryConfirmed = false;
1784
1822
  if (moving) {
1785
- if (this.drivingConditionSinceMs === null) this.drivingConditionSinceMs = this.motionWindow[0]?.sampledAtMs ?? sampledAtMs;
1823
+ if (this.drivingConditionSinceMs === null) this.drivingConditionSinceMs = sampledAtMs;
1786
1824
  if (sampledAtMs - this.drivingConditionSinceMs >= this.tuning.drivingConfirmationMs) this.baseStatus = "STEADY_DRIVING";
1787
1825
  return "moving";
1788
1826
  }
@@ -1882,7 +1920,15 @@ var DefaultDrivingStatusController = class {
1882
1920
  });
1883
1921
  }
1884
1922
  commitSelectedStatus(now) {
1885
- const selected = this.urgentStatus ?? this.turnStatus ?? this.normalLongitudinalStatus ?? this.baseStatus;
1923
+ const selected = [
1924
+ this.baseStatus,
1925
+ this.normalLongitudinalStatus,
1926
+ this.turnStatus,
1927
+ this.urgentStatus
1928
+ ].reduce((highest, candidate) => {
1929
+ if (candidate === null) return highest;
1930
+ return CAR_RUNNING_STATUS_PRIORITY[candidate] > CAR_RUNNING_STATUS_PRIORITY[highest] ? candidate : highest;
1931
+ }, this.baseStatus);
1886
1932
  this.commit(selected, now, "candidate_confirmed");
1887
1933
  }
1888
1934
  processUnavailable(category) {
@@ -1890,7 +1936,6 @@ var DefaultDrivingStatusController = class {
1890
1936
  const now = this.clock.now();
1891
1937
  if (this.unavailableSinceMs === null) this.unavailableSinceMs = now;
1892
1938
  const unavailableForMs = Math.max(0, now - this.unavailableSinceMs);
1893
- this.clearRecognitionWindows();
1894
1939
  this.setCandidate(null, now);
1895
1940
  this.emitDiagnostic({
1896
1941
  type: "sample_unavailable",
@@ -1908,6 +1953,7 @@ var DefaultDrivingStatusController = class {
1908
1953
  return;
1909
1954
  }
1910
1955
  this.fallbackApplied = true;
1956
+ this.clearRecognitionWindows();
1911
1957
  this.baseStatus = "STOPPED";
1912
1958
  this.emitDiagnostic({
1913
1959
  type: "fallback_to_stopped",
@@ -1940,7 +1986,12 @@ var DefaultDrivingStatusController = class {
1940
1986
  reason,
1941
1987
  atMs: now
1942
1988
  };
1943
- for (const listener of [...this.listeners]) safeCall(listener, event);
1989
+ for (const listener of [...this.listeners]) invokeSafely(listener, [event], (error) => {
1990
+ dialSdkDebugWarn(DEBUG_MODULE$1, "safeCall", "callback threw", {
1991
+ error,
1992
+ value: event
1993
+ });
1994
+ });
1944
1995
  this.emitDiagnostic({
1945
1996
  type: "status_committed",
1946
1997
  atMs: now,
@@ -2002,7 +2053,12 @@ var DefaultDrivingStatusController = class {
2002
2053
  event,
2003
2054
  hasListener: this.onDiagnostic !== void 0
2004
2055
  });
2005
- if (this.onDiagnostic) safeCall(this.onDiagnostic, event);
2056
+ if (this.onDiagnostic) invokeSafely(this.onDiagnostic, [event], (error) => {
2057
+ dialSdkDebugWarn(DEBUG_MODULE$1, "safeCall", "callback threw", {
2058
+ error,
2059
+ value: event
2060
+ });
2061
+ });
2006
2062
  }
2007
2063
  attachLifecycleListeners() {
2008
2064
  if (!this.managePageLifecycle || this.unsubscribePageLifecycle !== null) return;
@@ -2014,8 +2070,12 @@ var DefaultDrivingStatusController = class {
2014
2070
  }
2015
2071
  };
2016
2072
  const detectLongitudinalEvidence = (window, tuning) => {
2017
- const rapidWindow = recentMetrics(window, tuning.rapidLongitudinalWindowMs, "linearAccelerationSampledAtMs");
2018
- const normalWindow = recentMetrics(window, tuning.longitudinalWindowMs, "linearAccelerationSampledAtMs");
2073
+ const recentRapidWindow = recentMetrics(window, tuning.rapidLongitudinalWindowMs, "linearAccelerationSampledAtMs");
2074
+ const rapidCoverageWindow = selectCoverageWindow(window, tuning.rapidLongitudinalMinimumSamples, tuning.rapidLongitudinalWindowMs, (sample) => sample.linearAccelerationSampledAtMs);
2075
+ const rapidWindow = rapidCoverageWindow ?? recentRapidWindow;
2076
+ const recentNormalWindow = recentMetrics(window, tuning.longitudinalWindowMs, "linearAccelerationSampledAtMs");
2077
+ const normalCoverageWindow = selectCoverageWindow(window, tuning.longitudinalMinimumSamples, tuning.longitudinalWindowMs, (sample) => sample.linearAccelerationSampledAtMs);
2078
+ const normalWindow = normalCoverageWindow ?? recentNormalWindow;
2019
2079
  let rapidCandidate = null;
2020
2080
  let rejectionReason = null;
2021
2081
  const rapidValues = rapidWindow.map((sample) => sample.longitudinal);
@@ -2037,7 +2097,7 @@ const detectLongitudinalEvidence = (window, tuning) => {
2037
2097
  verticalRms,
2038
2098
  hasReversal
2039
2099
  };
2040
- if (windowCovers(rapidWindow, tuning.rapidLongitudinalMinimumSamples, tuning.rapidLongitudinalWindowMs, "linearAccelerationSampledAtMs")) {
2100
+ if (rapidCoverageWindow !== null) {
2041
2101
  if (rapidPeak >= tuning.rapidLongitudinalPeakThreshold && Math.abs(rapidMean) >= tuning.rapidLongitudinalMeanThreshold) {
2042
2102
  if (verticalRms > longitudinalRms * tuning.verticalBumpRatio) rejectionReason = "vertical_bump";
2043
2103
  else if (hasReversal) rejectionReason = "longitudinal_reversal";
@@ -2059,8 +2119,9 @@ const detectLongitudinalEvidence = (window, tuning) => {
2059
2119
  peak: maximumAbsolute(values),
2060
2120
  coherence
2061
2121
  };
2062
- retainedNormal = Math.abs(mean) >= tuning.longitudinalExitThreshold;
2063
- if (windowCovers(normalWindow, tuning.longitudinalMinimumSamples, tuning.longitudinalWindowMs, "linearAccelerationSampledAtMs") && Math.abs(mean) >= tuning.longitudinalThreshold && coherence >= tuning.longitudinalSignCoherence) normalCandidate = direction > 0 ? "ACCELERATION" : "BRAKING";
2122
+ const retentionValues = recentNormalWindow.map((sample) => sample.longitudinal);
2123
+ retainedNormal = Math.abs(trimmedMean(retentionValues)) >= tuning.longitudinalExitThreshold;
2124
+ if (normalCoverageWindow !== null && Math.abs(mean) >= tuning.longitudinalThreshold && coherence >= tuning.longitudinalSignCoherence) normalCandidate = direction > 0 ? "ACCELERATION" : "BRAKING";
2064
2125
  }
2065
2126
  return {
2066
2127
  rapidCandidate,
@@ -2072,7 +2133,8 @@ const detectLongitudinalEvidence = (window, tuning) => {
2072
2133
  };
2073
2134
  };
2074
2135
  const detectTurnEvidence = (window, tuning) => {
2075
- const latest = window[window.length - 1];
2136
+ const turnWindow = recentMetrics(window, tuning.turnWindowMs, "gyroscopeSampledAtMs");
2137
+ const latest = turnWindow[turnWindow.length - 1];
2076
2138
  if (!latest) return {
2077
2139
  candidate: null,
2078
2140
  latestYaw: 0,
@@ -2080,20 +2142,22 @@ const detectTurnEvidence = (window, tuning) => {
2080
2142
  rejectionReason: null,
2081
2143
  statistics: null
2082
2144
  };
2083
- const strongWindow = recentMetrics(window, tuning.turnConfirmationMs, "gyroscopeSampledAtMs");
2145
+ const recentStrongWindow = recentMetrics(turnWindow, tuning.turnConfirmationMs, "gyroscopeSampledAtMs");
2146
+ const strongCoverageWindow = selectCoverageWindow(window, tuning.turnMinimumSamples, tuning.turnConfirmationMs, (sample) => sample.gyroscopeSampledAtMs);
2147
+ const strongWindow = strongCoverageWindow ?? recentStrongWindow;
2084
2148
  const strongMean = trimmedMean(strongWindow.map((sample) => sample.yaw));
2085
2149
  const strongDirection = Math.sign(strongMean);
2086
2150
  const strongCoherence = signCoherence(strongWindow.map((sample) => sample.yaw), strongDirection, tuning.turnExitYawThreshold);
2087
- const strong = windowCovers(strongWindow, tuning.turnMinimumSamples, tuning.turnConfirmationMs, "gyroscopeSampledAtMs") && Math.abs(strongMean) >= tuning.turnYawThreshold && strongCoherence >= tuning.turnSignCoherence;
2088
- const signedAngle = integrateYaw(window);
2151
+ const strong = strongCoverageWindow !== null && Math.abs(strongMean) >= tuning.turnYawThreshold && strongCoherence >= tuning.turnSignCoherence;
2152
+ const signedAngle = integrateYaw(turnWindow);
2089
2153
  const gentleDirection = Math.sign(signedAngle);
2090
- const gentleCoherence = signCoherence(window.map((sample) => sample.yaw), gentleDirection, tuning.turnExitYawThreshold);
2091
- const yawRms = rootMeanSquare(window.map((sample) => sample.yaw));
2092
- const rawGyroRms = rootMeanSquare(window.map((sample) => sample.rawGyroMagnitude));
2154
+ const gentleCoherence = signCoherence(turnWindow.map((sample) => sample.yaw), gentleDirection, tuning.turnExitYawThreshold);
2155
+ const yawRms = rootMeanSquare(turnWindow.map((sample) => sample.yaw));
2156
+ const rawGyroRms = rootMeanSquare(turnWindow.map((sample) => sample.rawGyroMagnitude));
2093
2157
  const yawDominance = rawGyroRms <= Number.EPSILON ? 0 : Math.min(1, yawRms / rawGyroRms);
2094
- const lateralRms = rootMeanSquare(window.map((sample) => sample.lateral));
2095
- const verticalRms = rootMeanSquare(window.map((sample) => sample.vertical));
2096
- const gentleShape = window.length >= tuning.turnMinimumSamples && Math.abs(signedAngle) >= tuning.turnIntegratedAngleThreshold && gentleCoherence >= tuning.turnSignCoherence;
2158
+ const lateralRms = rootMeanSquare(turnWindow.map((sample) => sample.lateral));
2159
+ const verticalRms = rootMeanSquare(turnWindow.map((sample) => sample.vertical));
2160
+ const gentleShape = turnWindow.length >= tuning.turnMinimumSamples && Math.abs(signedAngle) >= tuning.turnIntegratedAngleThreshold && gentleCoherence >= tuning.turnSignCoherence;
2097
2161
  const verticalDominant = verticalRms > Math.max(lateralRms, tuning.turnVerticalNoiseFloor) * tuning.turnVerticalBumpRatio;
2098
2162
  const yawDominant = yawDominance >= tuning.turnYawDominanceThreshold;
2099
2163
  const laterallySupported = lateralRms >= tuning.turnLateralEvidenceThreshold;
@@ -2105,8 +2169,8 @@ const detectTurnEvidence = (window, tuning) => {
2105
2169
  sampledAtMs: latest.gyroscopeSampledAtMs,
2106
2170
  rejectionReason,
2107
2171
  statistics: {
2108
- sampleCount: window.length,
2109
- durationMs: metricWindowDuration(window, "gyroscopeSampledAtMs"),
2172
+ sampleCount: turnWindow.length,
2173
+ durationMs: metricWindowDuration(turnWindow, "gyroscopeSampledAtMs"),
2110
2174
  strongMean,
2111
2175
  strongCoherence,
2112
2176
  signedAngle,
@@ -2130,10 +2194,29 @@ const recentMetrics = (values, windowMs, timestampKey) => {
2130
2194
  const latestAtMs = values[values.length - 1]?.[timestampKey] ?? 0;
2131
2195
  return values.filter((value) => value[timestampKey] >= latestAtMs - windowMs);
2132
2196
  };
2133
- const windowCovers = (values, minimumSamples, durationMs, timestampKey) => {
2134
- if (values.length < minimumSamples) return false;
2135
- const first = values[0];
2136
- return values[values.length - 1][timestampKey] - first[timestampKey] >= durationMs;
2197
+ const MINIMUM_TEMPORAL_SAMPLES = 3;
2198
+ const MAXIMUM_RECOGNITION_GAP_MS = 1e3;
2199
+ const minimumTemporalSamples = (minimumSamples) => Math.min(minimumSamples, MINIMUM_TEMPORAL_SAMPLES);
2200
+ const maximumRecognitionGapMs = (minimumSamples, durationMs) => {
2201
+ const nominalSampleIntervalMs = durationMs / Math.max(1, minimumSamples - 1);
2202
+ return Math.max(MAXIMUM_RECOGNITION_GAP_MS, nominalSampleIntervalMs * 2.5);
2203
+ };
2204
+ const recognitionRetentionMs = (durationMs, minimumSamples) => Math.max(durationMs, maximumRecognitionGapMs(minimumSamples, durationMs) * Math.max(1, minimumTemporalSamples(minimumSamples) - 1));
2205
+ const selectCoverageWindow = (values, minimumSamples, durationMs, timestampOf) => {
2206
+ const requiredSamples = minimumTemporalSamples(minimumSamples);
2207
+ if (values.length < requiredSamples) return null;
2208
+ const lastAtMs = timestampOf(values[values.length - 1]);
2209
+ const maximumGapMs = maximumRecognitionGapMs(minimumSamples, durationMs);
2210
+ for (let startIndex = values.length - requiredSamples; startIndex >= 0; startIndex -= 1) {
2211
+ const coveredValues = values.slice(startIndex);
2212
+ if (lastAtMs - timestampOf(coveredValues[0]) < durationMs) continue;
2213
+ if (coveredValues.slice(1).every((value, index) => timestampOf(value) - timestampOf(coveredValues[index]) < maximumGapMs)) return coveredValues;
2214
+ }
2215
+ return null;
2216
+ };
2217
+ const recentVectors = (values, windowMs) => {
2218
+ const latestAtMs = values[values.length - 1]?.sampledAtMs ?? 0;
2219
+ return values.filter((value) => value.sampledAtMs >= latestAtMs - windowMs);
2137
2220
  };
2138
2221
  const integrateYaw = (values) => {
2139
2222
  let angle = 0;
@@ -2187,7 +2270,6 @@ const median = (values) => {
2187
2270
  const pushUniqueMetric = (window, metrics, timestampKey, windowMs) => {
2188
2271
  const latest = window[window.length - 1];
2189
2272
  if (latest && metrics[timestampKey] <= latest[timestampKey]) return false;
2190
- if (window.some((value) => value[timestampKey] === metrics[timestampKey])) return false;
2191
2273
  window.push(metrics);
2192
2274
  const cutoffMs = metrics[timestampKey] - windowMs;
2193
2275
  while (window[0] && window[0][timestampKey] < cutoffMs) window.shift();
@@ -2196,67 +2278,44 @@ const pushUniqueMetric = (window, metrics, timestampKey, windowMs) => {
2196
2278
  const pushUniqueVector = (window, sample, windowMs) => {
2197
2279
  const latest = window[window.length - 1];
2198
2280
  if (latest && sample.sampledAtMs <= latest.sampledAtMs) return false;
2199
- if (window.some((value) => value.sampledAtMs === sample.sampledAtMs)) return false;
2200
2281
  window.push(sample);
2201
2282
  const cutoffMs = sample.sampledAtMs - windowMs;
2202
2283
  while (window[0] && window[0].sampledAtMs < cutoffMs) window.shift();
2203
2284
  return true;
2204
2285
  };
2205
- const extractFreshVectorMetric = (metric, capturedAtMs, maxSampleAgeMs) => {
2206
- if (!metric?.available || !Number.isFinite(capturedAtMs) || !Number.isFinite(metric.sampledAtMs)) return null;
2207
- const ageMs = capturedAtMs - metric.sampledAtMs;
2208
- if (ageMs < 0 || ageMs > maxSampleAgeMs) return null;
2209
- const { x, y, z } = metric.value;
2210
- return [
2211
- x,
2212
- y,
2213
- z
2214
- ].every(Number.isFinite) ? {
2215
- sampledAtMs: metric.sampledAtMs,
2216
- value: metric.value
2217
- } : null;
2218
- };
2219
2286
  const observationTime = (metrics) => Math.max(metrics.linearAccelerationSampledAtMs, metrics.gyroscopeSampledAtMs);
2220
2287
  const lerpVector = (from, to, alpha) => ({
2221
2288
  x: from.x + (to.x - from.x) * alpha,
2222
2289
  y: from.y + (to.y - from.y) * alpha,
2223
2290
  z: from.z + (to.z - from.z) * alpha
2224
2291
  });
2225
- const defaultSensorProvider = () => {
2226
- const runtimeGlobal = globalThis;
2227
- if (typeof runtimeGlobal.unifiedSensorInfo !== "function") return Promise.reject(/* @__PURE__ */ new Error("window.unifiedSensorInfo is unavailable"));
2228
- return runtimeGlobal.unifiedSensorInfo();
2229
- };
2230
2292
  const systemClock = {
2231
2293
  now: () => Date.now(),
2232
2294
  setTimeout: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
2233
2295
  clearTimeout: (handle) => globalThis.clearTimeout(handle)
2234
2296
  };
2235
2297
  const isBaseStatus = (status) => status === "STOPPED" || status === "STEADY_DRIVING";
2236
- const isAbortError$1 = (error) => error instanceof Error && error.name === "AbortError";
2237
- const requirePositiveFinite = (name, value) => {
2238
- if (!Number.isFinite(value) || value <= 0) {
2239
- const error = /* @__PURE__ */ new RangeError(`${name} must be a positive finite number`);
2240
- dialSdkDebugWarn(DEBUG_MODULE$1, "requirePositiveFinite", "threw", error);
2241
- throw error;
2242
- }
2243
- return value;
2244
- };
2245
- const safeCall = (callback, value) => {
2246
- try {
2247
- callback(value);
2248
- } catch (error) {
2249
- dialSdkDebugWarn(DEBUG_MODULE$1, "safeCall", "callback threw", {
2250
- error,
2251
- value
2252
- });
2253
- }
2254
- };
2255
2298
  const noop = () => void 0;
2256
2299
  //#endregion
2257
- //#region src/sensor/driving_expression_player.ts
2300
+ //#region src/driving_expression/driving_expression_player.ts
2258
2301
  const DEBUG_MODULE = "driving_expression_player.ts";
2259
2302
  const MAX_RETAINED_IMAGES = 3;
2303
+ const POSITIVE_FINITE_DEBUG = {
2304
+ moduleName: DEBUG_MODULE,
2305
+ scope: "positiveFinite"
2306
+ };
2307
+ const CREATE_ABORT_ERROR_DEBUG = {
2308
+ moduleName: DEBUG_MODULE,
2309
+ scope: "createAbortError"
2310
+ };
2311
+ const IS_ABORT_ERROR_DEBUG = {
2312
+ moduleName: DEBUG_MODULE,
2313
+ scope: "isAbortError"
2314
+ };
2315
+ const IS_RECORD_DEBUG = {
2316
+ moduleName: DEBUG_MODULE,
2317
+ scope: "isRecord"
2318
+ };
2260
2319
  const createDrivingExpressionPlayer = (container, options) => {
2261
2320
  dialSdkDebugWarn(DEBUG_MODULE, "createDrivingExpressionPlayer", "called", {
2262
2321
  container,
@@ -2273,6 +2332,7 @@ var DefaultDrivingExpressionPlayer = class {
2273
2332
  this.mediaKind = null;
2274
2333
  this.errorCategory = null;
2275
2334
  this.desiredMedia = null;
2335
+ this.desiredMediaRole = null;
2276
2336
  this.userPaused = false;
2277
2337
  this.lifecycleSuspended = false;
2278
2338
  this.paused = false;
@@ -2321,15 +2381,15 @@ var DefaultDrivingExpressionPlayer = class {
2321
2381
  this.document = container.ownerDocument;
2322
2382
  this.config = options.config;
2323
2383
  this.fallbacks = options.fallbacks;
2324
- this.loadTimeoutMs = positiveFinite("loadTimeoutMs", options.loadTimeoutMs ?? 5e3);
2325
- this.retryBackoffMs = positiveFinite("retryBackoffMs", options.retryBackoffMs ?? 15e3);
2384
+ this.loadTimeoutMs = requirePositiveFinite("loadTimeoutMs", options.loadTimeoutMs ?? 5e3, POSITIVE_FINITE_DEBUG);
2385
+ this.retryBackoffMs = requirePositiveFinite("retryBackoffMs", options.retryBackoffMs ?? 15e3, POSITIVE_FINITE_DEBUG);
2326
2386
  this.fit = options.fit ?? "contain";
2327
2387
  this.tgsRenderer = defaultTgsRenderer;
2328
2388
  this.fetchImplementation = options.fetch ?? globalThis.fetch?.bind(globalThis);
2329
2389
  this.onDiagnostic = options.onDiagnostic;
2330
- this.maxTgsCompressedBytes = positiveFinite("maxTgsCompressedBytes", options.maxTgsCompressedBytes ?? 2 * 1024 * 1024);
2331
- this.maxTgsJsonBytes = positiveFinite("maxTgsJsonBytes", options.maxTgsJsonBytes ?? 8 * 1024 * 1024);
2332
- this.maxTgsLayers = positiveFinite("maxTgsLayers", options.maxTgsLayers ?? 500);
2390
+ this.maxTgsCompressedBytes = requirePositiveFinite("maxTgsCompressedBytes", options.maxTgsCompressedBytes ?? 2 * 1024 * 1024, POSITIVE_FINITE_DEBUG);
2391
+ this.maxTgsJsonBytes = requirePositiveFinite("maxTgsJsonBytes", options.maxTgsJsonBytes ?? 8 * 1024 * 1024, POSITIVE_FINITE_DEBUG);
2392
+ this.maxTgsLayers = requirePositiveFinite("maxTgsLayers", options.maxTgsLayers ?? 500, POSITIVE_FINITE_DEBUG);
2333
2393
  if (options.managePageLifecycle ?? true) this.unsubscribePageLifecycle = subscribeDrivingPageLifecycle(this.handleLifecyclePause, this.handleLifecycleResume);
2334
2394
  dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.constructor", "created", {
2335
2395
  loadTimeoutMs: this.loadTimeoutMs,
@@ -2354,24 +2414,33 @@ var DefaultDrivingExpressionPlayer = class {
2354
2414
  const operation = this.beginOperation();
2355
2415
  const configured = this.config.states[status];
2356
2416
  if (configured === void 0) {
2357
- const fallback = this.fallbacks?.[status];
2358
- if (fallback === void 0) {
2417
+ const fallback = this.resolveFallback(status);
2418
+ if (fallback.type === "missing") {
2419
+ this.desiredMedia = null;
2420
+ this.desiredMediaRole = null;
2359
2421
  this.handleFailure(status, null, new DrivingExpressionResolutionError(status), "invalid_config", operation, false);
2360
2422
  dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.show", "returned after missing config");
2361
2423
  return;
2362
2424
  }
2363
- this.desiredMedia = fallback;
2425
+ this.desiredMedia = fallback.media;
2426
+ this.desiredMediaRole = "fallback";
2364
2427
  if (this.paused) {
2365
2428
  this.phase = "idle";
2366
2429
  dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.show", "deferred fallback while paused");
2367
2430
  return;
2368
2431
  }
2369
- this.renderFallback(status, fallback);
2432
+ if (fallback.type === "unsupported") {
2433
+ this.handleFailure(status, fallback.media, unsupportedFallbackError(fallback.media), "invalid_config", operation, false, false);
2434
+ dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.show", "returned unsupported fallback");
2435
+ return;
2436
+ }
2437
+ this.renderFallback(status, fallback.media);
2370
2438
  dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.show", "returned fallback", { snapshot: this.getSnapshot() });
2371
2439
  return;
2372
2440
  }
2373
2441
  const media = normalizeMedia(configured);
2374
2442
  this.desiredMedia = media;
2443
+ this.desiredMediaRole = "primary";
2375
2444
  if (this.paused) {
2376
2445
  this.phase = "idle";
2377
2446
  dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.show", "deferred media while paused", { media });
@@ -2422,6 +2491,7 @@ var DefaultDrivingExpressionPlayer = class {
2422
2491
  this.container.replaceChildren();
2423
2492
  this.status = null;
2424
2493
  this.desiredMedia = null;
2494
+ this.desiredMediaRole = null;
2425
2495
  this.phase = "idle";
2426
2496
  this.mediaKind = null;
2427
2497
  this.errorCategory = null;
@@ -2490,7 +2560,12 @@ var DefaultDrivingExpressionPlayer = class {
2490
2560
  }
2491
2561
  if (this.status !== null && this.desiredMedia !== null) {
2492
2562
  const operation = this.beginOperation();
2493
- this.load(this.status, this.desiredMedia, operation);
2563
+ if (this.desiredMediaRole === "fallback") {
2564
+ const status = this.status;
2565
+ const fallback = this.desiredMedia;
2566
+ if (isStaticFallback(fallback)) this.renderFallback(status, fallback);
2567
+ else this.handleFailure(status, fallback, unsupportedFallbackError(fallback), "invalid_config", operation, false, false);
2568
+ } else this.load(this.status, this.desiredMedia, operation);
2494
2569
  }
2495
2570
  dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.updatePauseState", "returned", this.getSnapshot());
2496
2571
  }
@@ -2553,7 +2628,7 @@ var DefaultDrivingExpressionPlayer = class {
2553
2628
  error,
2554
2629
  operation
2555
2630
  });
2556
- if (!this.isCurrent(operation) || isAbortError(error)) {
2631
+ if (!this.isCurrent(operation) || isAbortError(error, IS_ABORT_ERROR_DEBUG)) {
2557
2632
  dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.load", "ignored error");
2558
2633
  return;
2559
2634
  }
@@ -2774,19 +2849,37 @@ var DefaultDrivingExpressionPlayer = class {
2774
2849
  status,
2775
2850
  report
2776
2851
  });
2777
- const fallback = this.fallbacks?.[status];
2778
- if (!fallback) {
2852
+ const fallback = this.resolveFallback(status);
2853
+ if (fallback.type !== "static") {
2779
2854
  this.container.replaceChildren();
2780
- dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.renderConfiguredFallback", "returned", { result: false });
2855
+ dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.renderConfiguredFallback", "returned", {
2856
+ result: false,
2857
+ fallbackType: fallback.type
2858
+ });
2781
2859
  return false;
2782
2860
  }
2783
- this.renderFallback(status, fallback, report);
2861
+ this.renderFallback(status, fallback.media, report);
2784
2862
  dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.renderConfiguredFallback", "returned", {
2785
2863
  result: true,
2786
- fallback
2864
+ fallback: fallback.media
2787
2865
  });
2788
2866
  return true;
2789
2867
  }
2868
+ resolveFallback(status) {
2869
+ const fallback = this.fallbacks?.[status];
2870
+ const result = fallback === void 0 ? { type: "missing" } : isStaticFallback(fallback) ? {
2871
+ type: "static",
2872
+ media: fallback
2873
+ } : {
2874
+ type: "unsupported",
2875
+ media: fallback
2876
+ };
2877
+ dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.resolveFallback", "returned", {
2878
+ status,
2879
+ result
2880
+ });
2881
+ return result;
2882
+ }
2790
2883
  renderFallback(status, fallback, report = true) {
2791
2884
  dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.renderFallback", "called", {
2792
2885
  status,
@@ -2799,11 +2892,11 @@ var DefaultDrivingExpressionPlayer = class {
2799
2892
  this.errorCategory = null;
2800
2893
  }
2801
2894
  if (fallback.kind === "emoji") this.renderEmoji(fallback.text);
2802
- else if (fallback.kind === "image") {
2895
+ else {
2803
2896
  const image = this.createImage();
2804
2897
  image.src = fallback.url;
2805
2898
  this.container.replaceChildren(image);
2806
- } else this.container.replaceChildren();
2899
+ }
2807
2900
  if (report) this.emitDiagnostic({
2808
2901
  type: "media_fallback_applied",
2809
2902
  status,
@@ -2852,14 +2945,15 @@ var DefaultDrivingExpressionPlayer = class {
2852
2945
  });
2853
2946
  dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.markReady", "returned", this.getSnapshot());
2854
2947
  }
2855
- handleFailure(status, media, error, category, operation, scheduleRetry = true) {
2948
+ handleFailure(status, media, error, category, operation, scheduleRetry = true, applyFallback = true) {
2856
2949
  dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.handleFailure", "called", {
2857
2950
  status,
2858
2951
  media,
2859
2952
  error,
2860
2953
  category,
2861
2954
  operation,
2862
- scheduleRetry
2955
+ scheduleRetry,
2956
+ applyFallback
2863
2957
  });
2864
2958
  if (!this.isCurrent(operation) || this.failedOperation === operation) {
2865
2959
  dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.handleFailure", "ignored");
@@ -2868,6 +2962,7 @@ var DefaultDrivingExpressionPlayer = class {
2868
2962
  this.failedOperation = operation;
2869
2963
  this.stopActiveVideo();
2870
2964
  this.phase = "error";
2965
+ if (!applyFallback && media !== null) this.mediaKind = media.kind;
2871
2966
  this.errorCategory = category;
2872
2967
  this.emitDiagnostic({
2873
2968
  type: "media_load_failed",
@@ -2876,8 +2971,24 @@ var DefaultDrivingExpressionPlayer = class {
2876
2971
  category,
2877
2972
  message: safeErrorMessage(error)
2878
2973
  });
2879
- const fallbackApplied = this.renderConfiguredFallback(status);
2880
- if (!fallbackApplied) {
2974
+ let fallbackApplied = false;
2975
+ const fallback = applyFallback ? this.resolveFallback(status) : { type: "missing" };
2976
+ if (fallback.type === "static") {
2977
+ this.renderFallback(status, fallback.media);
2978
+ fallbackApplied = true;
2979
+ } else if (fallback.type === "unsupported") {
2980
+ this.container.replaceChildren();
2981
+ this.phase = "error";
2982
+ this.mediaKind = fallback.media.kind;
2983
+ this.errorCategory = "invalid_config";
2984
+ this.emitDiagnostic({
2985
+ type: "media_load_failed",
2986
+ status,
2987
+ kind: fallback.media.kind,
2988
+ category: "invalid_config",
2989
+ message: safeErrorMessage(unsupportedFallbackError(fallback.media))
2990
+ });
2991
+ } else {
2881
2992
  this.phase = "error";
2882
2993
  this.errorCategory = category;
2883
2994
  }
@@ -3123,12 +3234,9 @@ var DefaultDrivingExpressionPlayer = class {
3123
3234
  dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.emitDiagnostic", "returned no listener");
3124
3235
  return;
3125
3236
  }
3126
- try {
3127
- this.onDiagnostic(event);
3128
- dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.emitDiagnostic", "returned", { event });
3129
- } catch (error) {
3237
+ if (invokeSafely(this.onDiagnostic, [event], (error) => {
3130
3238
  dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.emitDiagnostic", "listener threw", error);
3131
- }
3239
+ })) dialSdkDebugWarn(DEBUG_MODULE, "DefaultDrivingExpressionPlayer.emitDiagnostic", "returned", { event });
3132
3240
  }
3133
3241
  };
3134
3242
  var MediaPlayerError = class extends Error {
@@ -3161,6 +3269,8 @@ const normalizeMedia = (media) => {
3161
3269
  });
3162
3270
  return result;
3163
3271
  };
3272
+ const isStaticFallback = (media) => media.kind === "emoji" || media.kind === "image";
3273
+ const unsupportedFallbackError = (media) => new MediaPlayerError("invalid_config", `Fallback media kind ${media.kind} is unsupported; use emoji or image`);
3164
3274
  const isRetainableImage = (media) => {
3165
3275
  if (media.mimeType?.split(";", 1)[0]?.trim().toLowerCase() === "image/gif") return false;
3166
3276
  try {
@@ -3246,7 +3356,7 @@ const waitForElementEvent = (element, successEvent, failureEvent, signal, timeou
3246
3356
  const handleAbort = () => {
3247
3357
  dialSdkDebugWarn(DEBUG_MODULE, "waitForElementEvent.handleAbort", "called");
3248
3358
  cleanup();
3249
- reject(createAbortError());
3359
+ reject(createAbortError("Aborted", CREATE_ABORT_ERROR_DEBUG));
3250
3360
  };
3251
3361
  element.addEventListener(successEvent, handleSuccess, { once: true });
3252
3362
  element.addEventListener(failureEvent, handleFailure, { once: true });
@@ -3283,7 +3393,7 @@ const withTimeout = (promise, timeoutMs, signal) => {
3283
3393
  const handleAbort = () => {
3284
3394
  dialSdkDebugWarn(DEBUG_MODULE, "withTimeout.handleAbort", "called");
3285
3395
  cleanup();
3286
- reject(createAbortError());
3396
+ reject(createAbortError("Aborted", CREATE_ABORT_ERROR_DEBUG));
3287
3397
  };
3288
3398
  signal.addEventListener("abort", handleAbort, { once: true });
3289
3399
  timer = globalThis.setTimeout(() => {
@@ -3310,9 +3420,9 @@ const defaultTgsRenderer = async (container, animationData, signal) => {
3310
3420
  animationData,
3311
3421
  signal
3312
3422
  });
3313
- if (signal.aborted) throw createAbortError();
3423
+ if (signal.aborted) throw createAbortError("Aborted", CREATE_ABORT_ERROR_DEBUG);
3314
3424
  const module = await import("lottie-web/build/player/lottie_light.js");
3315
- if (signal.aborted) throw createAbortError();
3425
+ if (signal.aborted) throw createAbortError("Aborted", CREATE_ABORT_ERROR_DEBUG);
3316
3426
  const animation = module.default.loadAnimation({
3317
3427
  animationData,
3318
3428
  autoplay: true,
@@ -3336,7 +3446,7 @@ const defaultTgsRenderer = async (container, animationData, signal) => {
3336
3446
  dialSdkDebugWarn(DEBUG_MODULE, "defaultTgsRenderer.aborted", "called");
3337
3447
  cleanup();
3338
3448
  animation.destroy();
3339
- reject(createAbortError());
3449
+ reject(createAbortError("Aborted", CREATE_ABORT_ERROR_DEBUG));
3340
3450
  };
3341
3451
  const cleanup = () => {
3342
3452
  dialSdkDebugWarn(DEBUG_MODULE, "defaultTgsRenderer.cleanup", "called");
@@ -3375,7 +3485,7 @@ function validateTgsAnimation(value, maxLayers) {
3375
3485
  value,
3376
3486
  maxLayers
3377
3487
  });
3378
- if (!isRecord(value) || !Array.isArray(value.layers)) throw new MediaPlayerError("decode", "TGS animation does not contain a layers array");
3488
+ if (!isRecord(value, IS_RECORD_DEBUG) || !Array.isArray(value.layers)) throw new MediaPlayerError("decode", "TGS animation does not contain a layers array");
3379
3489
  if (value.layers.length > maxLayers) throw new MediaPlayerError("decode", "TGS animation has too many layers");
3380
3490
  if (typeof value.fr === "number" && (!Number.isFinite(value.fr) || value.fr <= 0 || value.fr > 120)) throw new MediaPlayerError("decode", "TGS animation frame rate is outside the supported range");
3381
3491
  if (typeof value.ip === "number" && typeof value.op === "number" && (value.op <= value.ip || value.op - value.ip > 3e4)) throw new MediaPlayerError("decode", "TGS animation duration is outside the supported range");
@@ -3430,11 +3540,7 @@ const resetVideo = (video) => {
3430
3540
  video.pause();
3431
3541
  video.removeAttribute("src");
3432
3542
  video.removeAttribute("type");
3433
- try {
3434
- video.load();
3435
- } catch (error) {
3436
- dialSdkDebugWarn(DEBUG_MODULE, "resetVideo", "load threw", error);
3437
- }
3543
+ video.load();
3438
3544
  dialSdkDebugWarn(DEBUG_MODULE, "resetVideo", "returned", { video });
3439
3545
  };
3440
3546
  const styleMediaElement = (element, fit) => {
@@ -3481,39 +3587,6 @@ const redactUrlQueries = (message) => {
3481
3587
  dialSdkDebugWarn(DEBUG_MODULE, "redactUrlQueries", "returned", { result });
3482
3588
  return result;
3483
3589
  };
3484
- const createAbortError = () => {
3485
- dialSdkDebugWarn(DEBUG_MODULE, "createAbortError", "called");
3486
- if (typeof DOMException !== "undefined") {
3487
- const result = new DOMException("Aborted", "AbortError");
3488
- dialSdkDebugWarn(DEBUG_MODULE, "createAbortError", "returned DOMException", { result });
3489
- return result;
3490
- }
3491
- const error = /* @__PURE__ */ new Error("Aborted");
3492
- error.name = "AbortError";
3493
- dialSdkDebugWarn(DEBUG_MODULE, "createAbortError", "returned Error", { result: error });
3494
- return error;
3495
- };
3496
- const isAbortError = (error) => {
3497
- const result = error instanceof Error && error.name === "AbortError";
3498
- dialSdkDebugWarn(DEBUG_MODULE, "isAbortError", "returned", {
3499
- error,
3500
- result
3501
- });
3502
- return result;
3503
- };
3504
- const positiveFinite = (name, value) => {
3505
- dialSdkDebugWarn(DEBUG_MODULE, "positiveFinite", "called", {
3506
- name,
3507
- value
3508
- });
3509
- if (!Number.isFinite(value) || value <= 0) {
3510
- const error = /* @__PURE__ */ new RangeError(`${name} must be a positive finite number`);
3511
- dialSdkDebugWarn(DEBUG_MODULE, "positiveFinite", "threw", error);
3512
- throw error;
3513
- }
3514
- dialSdkDebugWarn(DEBUG_MODULE, "positiveFinite", "returned", { result: value });
3515
- return value;
3516
- };
3517
3590
  const safeDestroy = (handle) => {
3518
3591
  dialSdkDebugWarn(DEBUG_MODULE, "safeDestroy", "called", { handle });
3519
3592
  try {
@@ -3523,13 +3596,5 @@ const safeDestroy = (handle) => {
3523
3596
  dialSdkDebugWarn(DEBUG_MODULE, "safeDestroy", "destroy threw", error);
3524
3597
  }
3525
3598
  };
3526
- const isRecord = (value) => {
3527
- const result = typeof value === "object" && value !== null && !Array.isArray(value);
3528
- dialSdkDebugWarn(DEBUG_MODULE, "isRecord", "returned", {
3529
- value,
3530
- result
3531
- });
3532
- return result;
3533
- };
3534
3599
  //#endregion
3535
3600
  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 };