ultimatedarktower 2.3.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +100 -46
  2. package/README.md +85 -56
  3. package/dist/esm/index.mjs +274 -246
  4. package/dist/src/UltimateDarkTower.d.ts +21 -6
  5. package/dist/src/UltimateDarkTower.js +39 -41
  6. package/dist/src/UltimateDarkTower.js.map +1 -1
  7. package/dist/src/adapters/NodeBluetoothAdapter.js.map +1 -1
  8. package/dist/src/adapters/WebBluetoothAdapter.js.map +1 -1
  9. package/dist/src/udtBleConnection.d.ts +4 -4
  10. package/dist/src/udtBleConnection.js +13 -12
  11. package/dist/src/udtBleConnection.js.map +1 -1
  12. package/dist/src/udtBluetoothAdapter.d.ts +2 -2
  13. package/dist/src/udtBluetoothAdapterFactory.js.map +1 -1
  14. package/dist/src/udtCommandFactory.d.ts +6 -0
  15. package/dist/src/udtCommandFactory.js +26 -22
  16. package/dist/src/udtCommandFactory.js.map +1 -1
  17. package/dist/src/udtCommandQueue.d.ts +2 -2
  18. package/dist/src/udtCommandQueue.js +2 -3
  19. package/dist/src/udtCommandQueue.js.map +1 -1
  20. package/dist/src/udtConstants.d.ts +1 -8
  21. package/dist/src/udtConstants.js +6 -6
  22. package/dist/src/udtConstants.js.map +1 -1
  23. package/dist/src/udtHelpers.js +8 -9
  24. package/dist/src/udtHelpers.js.map +1 -1
  25. package/dist/src/udtLogger.d.ts +1 -0
  26. package/dist/src/udtLogger.js +24 -2
  27. package/dist/src/udtLogger.js.map +1 -1
  28. package/dist/src/udtTowerCommands.d.ts +2 -2
  29. package/dist/src/udtTowerCommands.js +17 -14
  30. package/dist/src/udtTowerCommands.js.map +1 -1
  31. package/dist/src/udtTowerResponse.d.ts +9 -5
  32. package/dist/src/udtTowerResponse.js +5 -6
  33. package/dist/src/udtTowerResponse.js.map +1 -1
  34. package/dist/src/udtTowerState.js +4 -4
  35. package/dist/src/udtTowerState.js.map +1 -1
  36. package/package.json +18 -15
@@ -6,8 +6,7 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
6
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
7
7
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
8
8
  }) : x)(function(x) {
9
- if (typeof require !== "undefined")
10
- return require.apply(this, arguments);
9
+ if (typeof require !== "undefined") return require.apply(this, arguments);
11
10
  throw Error('Dynamic require of "' + x + '" is not supported');
12
11
  });
13
12
  var __esm = (fn, res) => function __init() {
@@ -31,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
30
  var UART_SERVICE_UUID, UART_TX_CHARACTERISTIC_UUID, UART_RX_CHARACTERISTIC_UUID, TOWER_DEVICE_NAME, DIS_SERVICE_UUID, DIS_MANUFACTURER_NAME_UUID, DIS_MODEL_NUMBER_UUID, DIS_SERIAL_NUMBER_UUID, DIS_HARDWARE_REVISION_UUID, DIS_FIRMWARE_REVISION_UUID, DIS_SOFTWARE_REVISION_UUID, DIS_SYSTEM_ID_UUID, DIS_IEEE_REGULATORY_UUID, DIS_PNP_ID_UUID, TOWER_COMMAND_PACKET_SIZE, TOWER_STATE_DATA_SIZE, TOWER_COMMAND_HEADER_SIZE, TOWER_STATE_RESPONSE_MIN_LENGTH, TOWER_STATE_DATA_OFFSET, TOWER_COMMAND_TYPE_TOWER_STATE, DEFAULT_CONNECTION_MONITORING_FREQUENCY, DEFAULT_CONNECTION_MONITORING_TIMEOUT, DEFAULT_BATTERY_HEARTBEAT_TIMEOUT, BATTERY_STATUS_FREQUENCY, DEFAULT_RETRY_SEND_COMMAND_MAX, TOWER_SIDES_COUNT, TOWER_COMMANDS, TC, DRUM_PACKETS, GLYPHS, AUDIO_COMMAND_POS, SKULL_DROP_COUNT_POS, drumPositionCmds, LIGHT_EFFECTS, TOWER_LIGHT_SEQUENCES, TOWER_MESSAGES, VOLTAGE_LEVELS, TOWER_LAYERS, RING_LIGHT_POSITIONS, LEDGE_BASE_LIGHT_POSITIONS, LED_CHANNEL_LOOKUP, LAYER_TO_POSITION, LIGHT_INDEX_TO_DIRECTION, STATE_DATA_LENGTH, TOWER_AUDIO_LIBRARY, VOLUME_DESCRIPTIONS, VOLUME_ICONS;
32
31
  var init_udtConstants = __esm({
33
32
  "src/udtConstants.ts"() {
33
+ "use strict";
34
34
  UART_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
35
35
  UART_TX_CHARACTERISTIC_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
36
36
  UART_RX_CHARACTERISTIC_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
@@ -362,160 +362,11 @@ var init_udtConstants = __esm({
362
362
  }
363
363
  });
364
364
 
365
- // src/udtTowerState.ts
366
- var udtTowerState_exports = {};
367
- __export(udtTowerState_exports, {
368
- LAYER_TO_POSITION: () => LAYER_TO_POSITION,
369
- LEDGE_BASE_LIGHT_POSITIONS: () => LEDGE_BASE_LIGHT_POSITIONS,
370
- LED_CHANNEL_LOOKUP: () => LED_CHANNEL_LOOKUP,
371
- LIGHT_INDEX_TO_DIRECTION: () => LIGHT_INDEX_TO_DIRECTION,
372
- RING_LIGHT_POSITIONS: () => RING_LIGHT_POSITIONS,
373
- STATE_DATA_LENGTH: () => STATE_DATA_LENGTH,
374
- TOWER_LAYERS: () => TOWER_LAYERS,
375
- isCalibrated: () => isCalibrated,
376
- rtdt_pack_state: () => rtdt_pack_state,
377
- rtdt_unpack_state: () => rtdt_unpack_state
378
- });
379
- function rtdt_unpack_state(data) {
380
- const state = {
381
- drum: [
382
- { jammed: false, calibrated: false, position: 0, playSound: false, reverse: false },
383
- { jammed: false, calibrated: false, position: 0, playSound: false, reverse: false },
384
- { jammed: false, calibrated: false, position: 0, playSound: false, reverse: false }
385
- ],
386
- layer: [
387
- { light: [{ effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }] },
388
- { light: [{ effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }] },
389
- { light: [{ effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }] },
390
- { light: [{ effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }] },
391
- { light: [{ effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }] },
392
- { light: [{ effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }] }
393
- ],
394
- audio: { sample: 0, loop: false, volume: 0 },
395
- beam: { count: 0, fault: false },
396
- led_sequence: 0
397
- };
398
- state.drum[0].jammed = !!(data[0] & 8);
399
- state.drum[0].calibrated = !!(data[0] & 16);
400
- state.drum[1].jammed = !!(data[1] & 1);
401
- state.drum[1].calibrated = !!(data[1] & 2);
402
- state.drum[2].jammed = !!(data[1] & 32);
403
- state.drum[2].calibrated = !!(data[1] & 64);
404
- state.drum[0].position = (data[0] & 6) >> 1;
405
- state.drum[1].position = (data[0] & 192) >> 6;
406
- state.drum[2].position = (data[1] & 24) >> 3;
407
- state.drum[0].playSound = !!(data[0] & 1);
408
- state.drum[1].playSound = !!(data[0] & 32);
409
- state.drum[2].playSound = !!(data[1] & 4);
410
- state.layer[0].light[0].effect = (data[2] & 224) >> 5;
411
- state.layer[0].light[0].loop = !!(data[2] & 16);
412
- state.layer[0].light[1].effect = (data[2] & 14) >> 1;
413
- state.layer[0].light[1].loop = !!(data[2] & 1);
414
- state.layer[0].light[2].effect = (data[3] & 224) >> 5;
415
- state.layer[0].light[2].loop = !!(data[3] & 16);
416
- state.layer[0].light[3].effect = (data[3] & 14) >> 1;
417
- state.layer[0].light[3].loop = !!(data[3] & 1);
418
- state.layer[1].light[0].effect = (data[4] & 224) >> 5;
419
- state.layer[1].light[0].loop = !!(data[4] & 16);
420
- state.layer[1].light[1].effect = (data[4] & 14) >> 1;
421
- state.layer[1].light[1].loop = !!(data[4] & 1);
422
- state.layer[1].light[2].effect = (data[5] & 224) >> 5;
423
- state.layer[1].light[2].loop = !!(data[5] & 16);
424
- state.layer[1].light[3].effect = (data[5] & 14) >> 1;
425
- state.layer[1].light[3].loop = !!(data[5] & 1);
426
- state.layer[2].light[0].effect = (data[6] & 224) >> 5;
427
- state.layer[2].light[0].loop = !!(data[6] & 16);
428
- state.layer[2].light[1].effect = (data[6] & 14) >> 1;
429
- state.layer[2].light[1].loop = !!(data[6] & 1);
430
- state.layer[2].light[2].effect = (data[7] & 224) >> 5;
431
- state.layer[2].light[2].loop = !!(data[7] & 16);
432
- state.layer[2].light[3].effect = (data[7] & 14) >> 1;
433
- state.layer[2].light[3].loop = !!(data[7] & 1);
434
- state.layer[3].light[0].effect = (data[8] & 224) >> 5;
435
- state.layer[3].light[0].loop = !!(data[8] & 16);
436
- state.layer[3].light[1].effect = (data[8] & 14) >> 1;
437
- state.layer[3].light[1].loop = !!(data[8] & 1);
438
- state.layer[3].light[2].effect = (data[9] & 224) >> 5;
439
- state.layer[3].light[2].loop = !!(data[9] & 16);
440
- state.layer[3].light[3].effect = (data[9] & 14) >> 1;
441
- state.layer[3].light[3].loop = !!(data[9] & 1);
442
- state.layer[4].light[0].effect = (data[10] & 224) >> 5;
443
- state.layer[4].light[0].loop = !!(data[10] & 16);
444
- state.layer[4].light[1].effect = (data[10] & 14) >> 1;
445
- state.layer[4].light[1].loop = !!(data[10] & 1);
446
- state.layer[4].light[2].effect = (data[11] & 224) >> 5;
447
- state.layer[4].light[2].loop = !!(data[11] & 16);
448
- state.layer[4].light[3].effect = (data[11] & 14) >> 1;
449
- state.layer[4].light[3].loop = !!(data[11] & 1);
450
- state.layer[5].light[0].effect = (data[12] & 224) >> 5;
451
- state.layer[5].light[0].loop = !!(data[12] & 16);
452
- state.layer[5].light[1].effect = (data[12] & 14) >> 1;
453
- state.layer[5].light[1].loop = !!(data[12] & 1);
454
- state.layer[5].light[2].effect = (data[13] & 224) >> 5;
455
- state.layer[5].light[2].loop = !!(data[13] & 16);
456
- state.layer[5].light[3].effect = (data[13] & 14) >> 1;
457
- state.layer[5].light[3].loop = !!(data[13] & 1);
458
- state.audio.sample = data[14] & 127;
459
- state.audio.loop = !!(data[14] & 128);
460
- state.beam.count = data[15] << 8 | data[16];
461
- state.beam.fault = !!(data[17] & 1);
462
- state.drum[0].reverse = !!(data[17] & 2);
463
- state.drum[1].reverse = !!(data[17] & 4);
464
- state.drum[2].reverse = !!(data[17] & 8);
465
- state.audio.volume = (data[17] & 240) >> 4;
466
- state.led_sequence = data[18];
467
- return state;
468
- }
469
- function rtdt_pack_state(data, len, state) {
470
- if (!data || len < STATE_DATA_LENGTH)
471
- return false;
472
- data.fill(0, 0, STATE_DATA_LENGTH);
473
- data[0] |= (state.drum[0].playSound ? 1 : 0) | (state.drum[0].position & 3) << 1 | (state.drum[0].jammed ? 1 : 0) << 3 | (state.drum[0].calibrated ? 1 : 0) << 4 | (state.drum[1].playSound ? 1 : 0) << 5 | (state.drum[1].position & 3) << 6;
474
- data[1] |= (state.drum[1].jammed ? 1 : 0) | (state.drum[1].calibrated ? 1 : 0) << 1 | (state.drum[2].playSound ? 1 : 0) << 2 | (state.drum[2].position & 3) << 3 | (state.drum[2].jammed ? 1 : 0) << 5 | (state.drum[2].calibrated ? 1 : 0) << 6;
475
- data[2] |= state.layer[0].light[0].effect << 5 | (state.layer[0].light[0].loop ? 1 : 0) << 4;
476
- data[2] |= state.layer[0].light[1].effect << 1 | (state.layer[0].light[1].loop ? 1 : 0);
477
- data[3] |= state.layer[0].light[2].effect << 5 | (state.layer[0].light[2].loop ? 1 : 0) << 4;
478
- data[3] |= state.layer[0].light[3].effect << 1 | (state.layer[0].light[3].loop ? 1 : 0);
479
- data[4] |= state.layer[1].light[0].effect << 5 | (state.layer[1].light[0].loop ? 1 : 0) << 4;
480
- data[4] |= state.layer[1].light[1].effect << 1 | (state.layer[1].light[1].loop ? 1 : 0);
481
- data[5] |= state.layer[1].light[2].effect << 5 | (state.layer[1].light[2].loop ? 1 : 0) << 4;
482
- data[5] |= state.layer[1].light[3].effect << 1 | (state.layer[1].light[3].loop ? 1 : 0);
483
- data[6] |= state.layer[2].light[0].effect << 5 | (state.layer[2].light[0].loop ? 1 : 0) << 4;
484
- data[6] |= state.layer[2].light[1].effect << 1 | (state.layer[2].light[1].loop ? 1 : 0);
485
- data[7] |= state.layer[2].light[2].effect << 5 | (state.layer[2].light[2].loop ? 1 : 0) << 4;
486
- data[7] |= state.layer[2].light[3].effect << 1 | (state.layer[2].light[3].loop ? 1 : 0);
487
- data[8] |= state.layer[3].light[0].effect << 5 | (state.layer[3].light[0].loop ? 1 : 0) << 4;
488
- data[8] |= state.layer[3].light[1].effect << 1 | (state.layer[3].light[1].loop ? 1 : 0);
489
- data[9] |= state.layer[3].light[2].effect << 5 | (state.layer[3].light[2].loop ? 1 : 0) << 4;
490
- data[9] |= state.layer[3].light[3].effect << 1 | (state.layer[3].light[3].loop ? 1 : 0);
491
- data[10] |= state.layer[4].light[0].effect << 5 | (state.layer[4].light[0].loop ? 1 : 0) << 4;
492
- data[10] |= state.layer[4].light[1].effect << 1 | (state.layer[4].light[1].loop ? 1 : 0);
493
- data[11] |= state.layer[4].light[2].effect << 5 | (state.layer[4].light[2].loop ? 1 : 0) << 4;
494
- data[11] |= state.layer[4].light[3].effect << 1 | (state.layer[4].light[3].loop ? 1 : 0);
495
- data[12] |= state.layer[5].light[0].effect << 5 | (state.layer[5].light[0].loop ? 1 : 0) << 4;
496
- data[12] |= state.layer[5].light[1].effect << 1 | (state.layer[5].light[1].loop ? 1 : 0);
497
- data[13] |= state.layer[5].light[2].effect << 5 | (state.layer[5].light[2].loop ? 1 : 0) << 4;
498
- data[13] |= state.layer[5].light[3].effect << 1 | (state.layer[5].light[3].loop ? 1 : 0);
499
- data[14] = state.audio.sample | (state.audio.loop ? 1 : 0) << 7;
500
- data[15] = state.beam.count >> 8;
501
- data[16] = state.beam.count & 255;
502
- data[17] = state.audio.volume << 4 | (state.beam.fault ? 1 : 0) | (state.drum[0].reverse ? 1 : 0) << 1 | (state.drum[1].reverse ? 1 : 0) << 2 | (state.drum[2].reverse ? 1 : 0) << 3;
503
- data[18] = state.led_sequence;
504
- return true;
505
- }
506
- function isCalibrated(state) {
507
- return state.drum.every((drum) => drum.calibrated);
508
- }
509
- var init_udtTowerState = __esm({
510
- "src/udtTowerState.ts"() {
511
- init_udtConstants();
512
- }
513
- });
514
-
515
365
  // src/udtBluetoothAdapter.ts
516
366
  var BluetoothError, BluetoothConnectionError, BluetoothDeviceNotFoundError, BluetoothUserCancelledError, BluetoothTimeoutError;
517
367
  var init_udtBluetoothAdapter = __esm({
518
368
  "src/udtBluetoothAdapter.ts"() {
369
+ "use strict";
519
370
  BluetoothError = class extends Error {
520
371
  constructor(message, originalError) {
521
372
  super(message);
@@ -558,6 +409,7 @@ __export(WebBluetoothAdapter_exports, {
558
409
  var WebBluetoothAdapter;
559
410
  var init_WebBluetoothAdapter = __esm({
560
411
  "src/adapters/WebBluetoothAdapter.ts"() {
412
+ "use strict";
561
413
  init_udtConstants();
562
414
  init_udtBluetoothAdapter();
563
415
  WebBluetoothAdapter = class {
@@ -720,6 +572,7 @@ __export(NodeBluetoothAdapter_exports, {
720
572
  var noble, NodeBluetoothAdapter;
721
573
  var init_NodeBluetoothAdapter = __esm({
722
574
  "src/adapters/NodeBluetoothAdapter.ts"() {
575
+ "use strict";
723
576
  init_udtBluetoothAdapter();
724
577
  init_udtConstants();
725
578
  try {
@@ -813,8 +666,7 @@ var init_NodeBluetoothAdapter = __esm({
813
666
  }
814
667
  }
815
668
  async disconnect() {
816
- if (!this.peripheral)
817
- return;
669
+ if (!this.peripheral) return;
818
670
  try {
819
671
  if (this.rxCharacteristic) {
820
672
  if (this.boundDataHandler) {
@@ -916,8 +768,7 @@ var init_NodeBluetoothAdapter = __esm({
916
768
  return cUuid === normalizedUuid || cUuid === shortUuid;
917
769
  }
918
770
  );
919
- if (!char)
920
- continue;
771
+ if (!char) continue;
921
772
  try {
922
773
  const buffer = await char.readAsync();
923
774
  if (binary) {
@@ -1006,7 +857,139 @@ var init_NodeBluetoothAdapter = __esm({
1006
857
 
1007
858
  // src/UltimateDarkTower.ts
1008
859
  init_udtConstants();
1009
- init_udtTowerState();
860
+
861
+ // src/udtTowerState.ts
862
+ init_udtConstants();
863
+ function rtdt_unpack_state(data) {
864
+ const state = {
865
+ drum: [
866
+ { jammed: false, calibrated: false, position: 0, playSound: false, reverse: false },
867
+ { jammed: false, calibrated: false, position: 0, playSound: false, reverse: false },
868
+ { jammed: false, calibrated: false, position: 0, playSound: false, reverse: false }
869
+ ],
870
+ layer: [
871
+ { light: [{ effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }] },
872
+ { light: [{ effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }] },
873
+ { light: [{ effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }] },
874
+ { light: [{ effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }] },
875
+ { light: [{ effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }] },
876
+ { light: [{ effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }, { effect: 0, loop: false }] }
877
+ ],
878
+ audio: { sample: 0, loop: false, volume: 0 },
879
+ beam: { count: 0, fault: false },
880
+ led_sequence: 0
881
+ };
882
+ state.drum[0].jammed = !!(data[0] & 8);
883
+ state.drum[0].calibrated = !!(data[0] & 16);
884
+ state.drum[1].jammed = !!(data[1] & 1);
885
+ state.drum[1].calibrated = !!(data[1] & 2);
886
+ state.drum[2].jammed = !!(data[1] & 32);
887
+ state.drum[2].calibrated = !!(data[1] & 64);
888
+ state.drum[0].position = (data[0] & 6) >> 1;
889
+ state.drum[1].position = (data[0] & 192) >> 6;
890
+ state.drum[2].position = (data[1] & 24) >> 3;
891
+ state.drum[0].playSound = !!(data[0] & 1);
892
+ state.drum[1].playSound = !!(data[0] & 32);
893
+ state.drum[2].playSound = !!(data[1] & 4);
894
+ state.layer[0].light[0].effect = (data[2] & 224) >> 5;
895
+ state.layer[0].light[0].loop = !!(data[2] & 16);
896
+ state.layer[0].light[1].effect = (data[2] & 14) >> 1;
897
+ state.layer[0].light[1].loop = !!(data[2] & 1);
898
+ state.layer[0].light[2].effect = (data[3] & 224) >> 5;
899
+ state.layer[0].light[2].loop = !!(data[3] & 16);
900
+ state.layer[0].light[3].effect = (data[3] & 14) >> 1;
901
+ state.layer[0].light[3].loop = !!(data[3] & 1);
902
+ state.layer[1].light[0].effect = (data[4] & 224) >> 5;
903
+ state.layer[1].light[0].loop = !!(data[4] & 16);
904
+ state.layer[1].light[1].effect = (data[4] & 14) >> 1;
905
+ state.layer[1].light[1].loop = !!(data[4] & 1);
906
+ state.layer[1].light[2].effect = (data[5] & 224) >> 5;
907
+ state.layer[1].light[2].loop = !!(data[5] & 16);
908
+ state.layer[1].light[3].effect = (data[5] & 14) >> 1;
909
+ state.layer[1].light[3].loop = !!(data[5] & 1);
910
+ state.layer[2].light[0].effect = (data[6] & 224) >> 5;
911
+ state.layer[2].light[0].loop = !!(data[6] & 16);
912
+ state.layer[2].light[1].effect = (data[6] & 14) >> 1;
913
+ state.layer[2].light[1].loop = !!(data[6] & 1);
914
+ state.layer[2].light[2].effect = (data[7] & 224) >> 5;
915
+ state.layer[2].light[2].loop = !!(data[7] & 16);
916
+ state.layer[2].light[3].effect = (data[7] & 14) >> 1;
917
+ state.layer[2].light[3].loop = !!(data[7] & 1);
918
+ state.layer[3].light[0].effect = (data[8] & 224) >> 5;
919
+ state.layer[3].light[0].loop = !!(data[8] & 16);
920
+ state.layer[3].light[1].effect = (data[8] & 14) >> 1;
921
+ state.layer[3].light[1].loop = !!(data[8] & 1);
922
+ state.layer[3].light[2].effect = (data[9] & 224) >> 5;
923
+ state.layer[3].light[2].loop = !!(data[9] & 16);
924
+ state.layer[3].light[3].effect = (data[9] & 14) >> 1;
925
+ state.layer[3].light[3].loop = !!(data[9] & 1);
926
+ state.layer[4].light[0].effect = (data[10] & 224) >> 5;
927
+ state.layer[4].light[0].loop = !!(data[10] & 16);
928
+ state.layer[4].light[1].effect = (data[10] & 14) >> 1;
929
+ state.layer[4].light[1].loop = !!(data[10] & 1);
930
+ state.layer[4].light[2].effect = (data[11] & 224) >> 5;
931
+ state.layer[4].light[2].loop = !!(data[11] & 16);
932
+ state.layer[4].light[3].effect = (data[11] & 14) >> 1;
933
+ state.layer[4].light[3].loop = !!(data[11] & 1);
934
+ state.layer[5].light[0].effect = (data[12] & 224) >> 5;
935
+ state.layer[5].light[0].loop = !!(data[12] & 16);
936
+ state.layer[5].light[1].effect = (data[12] & 14) >> 1;
937
+ state.layer[5].light[1].loop = !!(data[12] & 1);
938
+ state.layer[5].light[2].effect = (data[13] & 224) >> 5;
939
+ state.layer[5].light[2].loop = !!(data[13] & 16);
940
+ state.layer[5].light[3].effect = (data[13] & 14) >> 1;
941
+ state.layer[5].light[3].loop = !!(data[13] & 1);
942
+ state.audio.sample = data[14] & 127;
943
+ state.audio.loop = !!(data[14] & 128);
944
+ state.beam.count = data[15] << 8 | data[16];
945
+ state.beam.fault = !!(data[17] & 1);
946
+ state.drum[0].reverse = !!(data[17] & 2);
947
+ state.drum[1].reverse = !!(data[17] & 4);
948
+ state.drum[2].reverse = !!(data[17] & 8);
949
+ state.audio.volume = (data[17] & 240) >> 4;
950
+ state.led_sequence = data[18];
951
+ return state;
952
+ }
953
+ function rtdt_pack_state(data, len, state) {
954
+ if (!data || len < STATE_DATA_LENGTH)
955
+ return false;
956
+ data.fill(0, 0, STATE_DATA_LENGTH);
957
+ data[0] |= (state.drum[0].playSound ? 1 : 0) | (state.drum[0].position & 3) << 1 | (state.drum[0].jammed ? 1 : 0) << 3 | (state.drum[0].calibrated ? 1 : 0) << 4 | (state.drum[1].playSound ? 1 : 0) << 5 | (state.drum[1].position & 3) << 6;
958
+ data[1] |= (state.drum[1].jammed ? 1 : 0) | (state.drum[1].calibrated ? 1 : 0) << 1 | (state.drum[2].playSound ? 1 : 0) << 2 | (state.drum[2].position & 3) << 3 | (state.drum[2].jammed ? 1 : 0) << 5 | (state.drum[2].calibrated ? 1 : 0) << 6;
959
+ data[2] |= state.layer[0].light[0].effect << 5 | (state.layer[0].light[0].loop ? 1 : 0) << 4;
960
+ data[2] |= state.layer[0].light[1].effect << 1 | (state.layer[0].light[1].loop ? 1 : 0);
961
+ data[3] |= state.layer[0].light[2].effect << 5 | (state.layer[0].light[2].loop ? 1 : 0) << 4;
962
+ data[3] |= state.layer[0].light[3].effect << 1 | (state.layer[0].light[3].loop ? 1 : 0);
963
+ data[4] |= state.layer[1].light[0].effect << 5 | (state.layer[1].light[0].loop ? 1 : 0) << 4;
964
+ data[4] |= state.layer[1].light[1].effect << 1 | (state.layer[1].light[1].loop ? 1 : 0);
965
+ data[5] |= state.layer[1].light[2].effect << 5 | (state.layer[1].light[2].loop ? 1 : 0) << 4;
966
+ data[5] |= state.layer[1].light[3].effect << 1 | (state.layer[1].light[3].loop ? 1 : 0);
967
+ data[6] |= state.layer[2].light[0].effect << 5 | (state.layer[2].light[0].loop ? 1 : 0) << 4;
968
+ data[6] |= state.layer[2].light[1].effect << 1 | (state.layer[2].light[1].loop ? 1 : 0);
969
+ data[7] |= state.layer[2].light[2].effect << 5 | (state.layer[2].light[2].loop ? 1 : 0) << 4;
970
+ data[7] |= state.layer[2].light[3].effect << 1 | (state.layer[2].light[3].loop ? 1 : 0);
971
+ data[8] |= state.layer[3].light[0].effect << 5 | (state.layer[3].light[0].loop ? 1 : 0) << 4;
972
+ data[8] |= state.layer[3].light[1].effect << 1 | (state.layer[3].light[1].loop ? 1 : 0);
973
+ data[9] |= state.layer[3].light[2].effect << 5 | (state.layer[3].light[2].loop ? 1 : 0) << 4;
974
+ data[9] |= state.layer[3].light[3].effect << 1 | (state.layer[3].light[3].loop ? 1 : 0);
975
+ data[10] |= state.layer[4].light[0].effect << 5 | (state.layer[4].light[0].loop ? 1 : 0) << 4;
976
+ data[10] |= state.layer[4].light[1].effect << 1 | (state.layer[4].light[1].loop ? 1 : 0);
977
+ data[11] |= state.layer[4].light[2].effect << 5 | (state.layer[4].light[2].loop ? 1 : 0) << 4;
978
+ data[11] |= state.layer[4].light[3].effect << 1 | (state.layer[4].light[3].loop ? 1 : 0);
979
+ data[12] |= state.layer[5].light[0].effect << 5 | (state.layer[5].light[0].loop ? 1 : 0) << 4;
980
+ data[12] |= state.layer[5].light[1].effect << 1 | (state.layer[5].light[1].loop ? 1 : 0);
981
+ data[13] |= state.layer[5].light[2].effect << 5 | (state.layer[5].light[2].loop ? 1 : 0) << 4;
982
+ data[13] |= state.layer[5].light[3].effect << 1 | (state.layer[5].light[3].loop ? 1 : 0);
983
+ data[14] = state.audio.sample | (state.audio.loop ? 1 : 0) << 7;
984
+ data[15] = state.beam.count >> 8;
985
+ data[16] = state.beam.count & 255;
986
+ data[17] = state.audio.volume << 4 | (state.beam.fault ? 1 : 0) | (state.drum[0].reverse ? 1 : 0) << 1 | (state.drum[1].reverse ? 1 : 0) << 2 | (state.drum[2].reverse ? 1 : 0) << 3;
987
+ data[18] = state.led_sequence;
988
+ return true;
989
+ }
990
+ function isCalibrated(state) {
991
+ return state.drum.every((drum) => drum.calibrated);
992
+ }
1010
993
 
1011
994
  // src/udtHelpers.ts
1012
995
  init_udtConstants();
@@ -1135,17 +1118,33 @@ var DOMOutput = class {
1135
1118
  this.maxLines = maxLines;
1136
1119
  }
1137
1120
  write(level, message, timestamp) {
1138
- if (!this.container)
1139
- return;
1121
+ if (!this.container) return;
1140
1122
  this.allEntries.push({ level, message, timestamp });
1123
+ let removedEntries = false;
1141
1124
  while (this.allEntries.length > this.maxLines) {
1142
1125
  this.allEntries.shift();
1126
+ removedEntries = true;
1127
+ }
1128
+ if (removedEntries) {
1129
+ this.refreshDisplay();
1130
+ return;
1131
+ }
1132
+ const enabledLevels = this.getEnabledLevelsFromCheckboxes();
1133
+ if (enabledLevels.has(level)) {
1134
+ const textFilter = this.getTextFilter();
1135
+ if (!textFilter || message.toLowerCase().includes(textFilter.toLowerCase())) {
1136
+ const timeStr = timestamp.toLocaleTimeString();
1137
+ const logLine = document.createElement("div");
1138
+ logLine.className = `log-line log-${level}`;
1139
+ logLine.textContent = `[${timeStr}] ${message}`;
1140
+ this.container.appendChild(logLine);
1141
+ this.container.scrollTop = this.container.scrollHeight;
1142
+ this.updateBufferSizeDisplay();
1143
+ }
1143
1144
  }
1144
- this.refreshDisplay();
1145
1145
  }
1146
1146
  refreshDisplay() {
1147
- if (!this.container)
1148
- return;
1147
+ if (!this.container) return;
1149
1148
  this.container.innerHTML = "";
1150
1149
  const enabledLevels = this.getEnabledLevelsFromCheckboxes();
1151
1150
  const textFilter = this.getTextFilter();
@@ -1242,6 +1241,9 @@ var Logger = class _Logger {
1242
1241
  addOutput(output) {
1243
1242
  this.outputs.push(output);
1244
1243
  }
1244
+ clearOutputs() {
1245
+ this.outputs = [];
1246
+ }
1245
1247
  setMinLevel(level) {
1246
1248
  this.enabledLevels = /* @__PURE__ */ new Set([level]);
1247
1249
  }
@@ -1258,12 +1260,9 @@ var Logger = class _Logger {
1258
1260
  return Array.from(this.enabledLevels);
1259
1261
  }
1260
1262
  shouldLog(level) {
1261
- if (this.enabledLevels.has("all"))
1262
- return true;
1263
- if (level === "all")
1264
- return true;
1265
- if (this.enabledLevels.has(level))
1266
- return true;
1263
+ if (this.enabledLevels.has("all")) return true;
1264
+ if (level === "all") return true;
1265
+ if (this.enabledLevels.has(level)) return true;
1267
1266
  if (this.enabledLevels.size === 1) {
1268
1267
  const singleLevel = Array.from(this.enabledLevels)[0];
1269
1268
  if (singleLevel !== "all") {
@@ -1276,8 +1275,7 @@ var Logger = class _Logger {
1276
1275
  return false;
1277
1276
  }
1278
1277
  log(level, message, context) {
1279
- if (!this.shouldLog(level))
1280
- return;
1278
+ if (!this.shouldLog(level)) return;
1281
1279
  const contextPrefix = context ? `${context} ` : "";
1282
1280
  const finalMessage = `${contextPrefix}${message}`;
1283
1281
  const timestamp = /* @__PURE__ */ new Date();
@@ -1412,7 +1410,7 @@ var TowerResponseProcessor = class {
1412
1410
  const cmdKey = cmdKeys.find((key) => TOWER_MESSAGES[key].value === cmdValue);
1413
1411
  if (!cmdKey) {
1414
1412
  logger.warn(`Unknown command received from tower: ${cmdValue} (0x${cmdValue.toString(16)})`, "TowerResponseProcessor");
1415
- return { cmdKey: void 0, command: { name: "Unknown Command", value: cmdValue } };
1413
+ return { cmdKey: void 0, command: { name: "Unknown Command", value: cmdValue, critical: false } };
1416
1414
  }
1417
1415
  const command = TOWER_MESSAGES[cmdKey];
1418
1416
  return { cmdKey, command };
@@ -1455,12 +1453,11 @@ var TowerResponseProcessor = class {
1455
1453
  * @returns {boolean} Whether this response should be logged
1456
1454
  */
1457
1455
  shouldLogResponse(cmdKey, logConfig) {
1458
- const logAll = logConfig["LOG_ALL"];
1459
- let canLogThisResponse = logConfig[cmdKey] || logAll;
1460
1456
  if (!cmdKey) {
1461
- canLogThisResponse = true;
1457
+ return true;
1462
1458
  }
1463
- return canLogThisResponse;
1459
+ const logAll = logConfig["LOG_ALL"];
1460
+ return logConfig[cmdKey] || logAll;
1464
1461
  }
1465
1462
  /**
1466
1463
  * Checks if a command is a battery response type.
@@ -1480,9 +1477,6 @@ var TowerResponseProcessor = class {
1480
1477
  }
1481
1478
  };
1482
1479
 
1483
- // src/udtBleConnection.ts
1484
- init_udtTowerState();
1485
-
1486
1480
  // src/udtBluetoothAdapterFactory.ts
1487
1481
  var BluetoothPlatform = /* @__PURE__ */ ((BluetoothPlatform3) => {
1488
1482
  BluetoothPlatform3["WEB"] = "web";
@@ -1558,11 +1552,11 @@ var UdtBleConnection = class {
1558
1552
  // When true, verifies connection before triggering disconnection on heartbeat timeout
1559
1553
  // Tower state
1560
1554
  this.towerSkullDropCount = -1;
1561
- this.lastBatteryNotification = 0;
1555
+ this.lastBatteryLog = 0;
1562
1556
  this.lastBatteryPercentage = "";
1563
- this.batteryNotifyFrequency = 15 * 1e3;
1564
- this.batteryNotifyOnValueChangeOnly = false;
1565
- this.batteryNotifyEnabled = true;
1557
+ this.batteryLogFrequency = 15 * 1e3;
1558
+ this.batteryLogOnChangeOnly = false;
1559
+ this.batteryLogEnabled = true;
1566
1560
  // Device information
1567
1561
  this.deviceInformation = {};
1568
1562
  // Logging configuration
@@ -1641,7 +1635,7 @@ var UdtBleConnection = class {
1641
1635
  this.lastSuccessfulCommand = Date.now();
1642
1636
  const { cmdKey } = this.responseProcessor.getTowerCommand(receivedData[0]);
1643
1637
  const isBattery = this.responseProcessor.isBatteryResponse(cmdKey);
1644
- const shouldLogCommand = this.logTowerResponses && this.responseProcessor.shouldLogResponse(cmdKey, this.logTowerResponseConfig) && (!isBattery || this.batteryNotifyEnabled);
1638
+ const shouldLogCommand = this.logTowerResponses && this.responseProcessor.shouldLogResponse(cmdKey, this.logTowerResponseConfig) && !isBattery;
1645
1639
  if (shouldLogCommand) {
1646
1640
  this.logger.info(`${cmdKey}`, "[UDT][BLE][RCVD]");
1647
1641
  }
@@ -1656,14 +1650,14 @@ var UdtBleConnection = class {
1656
1650
  const millivolts = getMilliVoltsFromTowerResponse(receivedData);
1657
1651
  const batteryPercentage = milliVoltsToPercentage(millivolts);
1658
1652
  const didBatteryLevelChange = this.lastBatteryPercentage !== "" && this.lastBatteryPercentage !== batteryPercentage;
1659
- const batteryNotifyFrequencyPassed = Date.now() - this.lastBatteryNotification >= this.batteryNotifyFrequency;
1660
- const shouldNotify = this.batteryNotifyEnabled && (this.batteryNotifyOnValueChangeOnly ? didBatteryLevelChange || this.lastBatteryPercentage === "" : batteryNotifyFrequencyPassed);
1661
- if (shouldNotify) {
1653
+ const batteryLogFrequencyPassed = Date.now() - this.lastBatteryLog >= this.batteryLogFrequency;
1654
+ const shouldLog = this.batteryLogEnabled && (this.batteryLogOnChangeOnly ? didBatteryLevelChange || this.lastBatteryPercentage === "" : batteryLogFrequencyPassed);
1655
+ if (shouldLog) {
1662
1656
  this.logger.info(`${this.responseProcessor.commandToString(receivedData).join(" ")}`, "[UDT][BLE]");
1663
- this.lastBatteryNotification = Date.now();
1657
+ this.lastBatteryLog = Date.now();
1664
1658
  this.lastBatteryPercentage = batteryPercentage;
1665
- this.callbacks.onBatteryLevelNotify(millivolts);
1666
1659
  }
1660
+ this.callbacks.onBatteryLevelNotify(millivolts);
1667
1661
  } else {
1668
1662
  if (this.callbacks.onTowerResponse) {
1669
1663
  this.callbacks.onTowerResponse(receivedData);
@@ -1835,13 +1829,12 @@ var UdtBleConnection = class {
1835
1829
  this.logger.info(`Device ${key}: ${value}`, "[UDT][BLE]");
1836
1830
  }
1837
1831
  }
1838
- } catch (error) {
1832
+ } catch {
1839
1833
  this.logger.debug("Device Information Service not available", "[UDT][BLE]");
1840
1834
  }
1841
1835
  }
1842
1836
  async cleanup() {
1843
- if (this.isDisposed)
1844
- return;
1837
+ if (this.isDisposed) return;
1845
1838
  this.isDisposed = true;
1846
1839
  this.logger.info("Cleaning up UdtBleConnection instance", "[UDT][BLE]");
1847
1840
  this.stopConnectionMonitoring();
@@ -1854,7 +1847,6 @@ var UdtBleConnection = class {
1854
1847
 
1855
1848
  // src/udtCommandFactory.ts
1856
1849
  init_udtConstants();
1857
- init_udtTowerState();
1858
1850
  var UdtCommandFactory = class {
1859
1851
  /**
1860
1852
  * Creates a rotation command packet for positioning tower drums.
@@ -1876,8 +1868,7 @@ var UdtCommandFactory = class {
1876
1868
  */
1877
1869
  createSoundCommand(soundIndex) {
1878
1870
  const soundCommand = new Uint8Array(TOWER_COMMAND_PACKET_SIZE);
1879
- const sound = Number("0x" + Number(soundIndex).toString(16).padStart(2, "0"));
1880
- soundCommand[AUDIO_COMMAND_POS] = sound;
1871
+ soundCommand[AUDIO_COMMAND_POS] = soundIndex & 255;
1881
1872
  return soundCommand;
1882
1873
  }
1883
1874
  /**
@@ -1897,7 +1888,7 @@ var UdtCommandFactory = class {
1897
1888
  * @returns 20-byte command packet (command type + 19-byte state data)
1898
1889
  */
1899
1890
  createStatefulCommand(currentState, modifications) {
1900
- const newState = currentState ? { ...currentState } : this.createEmptyTowerState();
1891
+ const newState = currentState ? this.deepCopyTowerState(currentState) : this.createEmptyTowerState();
1901
1892
  if (modifications.drum) {
1902
1893
  modifications.drum.forEach((drum, index) => {
1903
1894
  if (drum && newState.drum[index]) {
@@ -1939,17 +1930,10 @@ var UdtCommandFactory = class {
1939
1930
  * @returns 20-byte command packet
1940
1931
  */
1941
1932
  createStatefulLEDCommand(currentState, layerIndex, lightIndex, effect, loop = false) {
1942
- const modifications = {};
1943
- if (!modifications.layer) {
1944
- modifications.layer = [];
1945
- }
1946
- if (!modifications.layer[layerIndex]) {
1947
- modifications.layer[layerIndex] = { light: [] };
1948
- }
1949
- if (!modifications.layer[layerIndex].light) {
1950
- modifications.layer[layerIndex].light = [];
1951
- }
1952
- modifications.layer[layerIndex].light[lightIndex] = { effect, loop };
1933
+ const layer = [];
1934
+ layer[layerIndex] = { light: [] };
1935
+ layer[layerIndex].light[lightIndex] = { effect, loop };
1936
+ const modifications = { layer };
1953
1937
  modifications.audio = { sample: 0, loop: false, volume: 0 };
1954
1938
  return this.createStatefulCommand(currentState, modifications);
1955
1939
  }
@@ -2043,17 +2027,15 @@ var UdtCommandFactory = class {
2043
2027
  * @returns 20-byte command packet
2044
2028
  */
2045
2029
  createStatefulDrumCommand(currentState, drumIndex, position, playSound = false) {
2046
- const modifications = {};
2047
- if (!modifications.drum) {
2048
- modifications.drum = [];
2049
- }
2050
- modifications.drum[drumIndex] = {
2030
+ const drum = [];
2031
+ drum[drumIndex] = {
2051
2032
  jammed: false,
2052
2033
  calibrated: true,
2053
2034
  position,
2054
2035
  playSound,
2055
2036
  reverse: false
2056
2037
  };
2038
+ const modifications = { drum };
2057
2039
  modifications.audio = { sample: 0, loop: false, volume: 0 };
2058
2040
  return this.createStatefulCommand(currentState, modifications);
2059
2041
  }
@@ -2097,6 +2079,22 @@ var UdtCommandFactory = class {
2097
2079
  led_sequence: 0
2098
2080
  };
2099
2081
  }
2082
+ /**
2083
+ * Creates a deep copy of a TowerState to avoid mutating the original.
2084
+ * @param state - The tower state to copy
2085
+ * @returns A new TowerState with all nested objects copied
2086
+ */
2087
+ deepCopyTowerState(state) {
2088
+ return {
2089
+ drum: state.drum.map((d) => ({ ...d })),
2090
+ layer: state.layer.map((l) => ({
2091
+ light: l.light.map((lt) => ({ ...lt }))
2092
+ })),
2093
+ audio: { ...state.audio },
2094
+ beam: { ...state.beam },
2095
+ led_sequence: state.led_sequence
2096
+ };
2097
+ }
2100
2098
  //#endregion
2101
2099
  };
2102
2100
 
@@ -2180,9 +2178,10 @@ var CommandQueue = class {
2180
2178
  if (this.currentCommand) {
2181
2179
  const { description, id } = this.currentCommand;
2182
2180
  this.logger.warn(`Command timeout after ${this.timeoutMs}ms: ${description || id}`, "[UDT]");
2183
- this.currentCommand.resolve();
2181
+ const reject = this.currentCommand.reject;
2184
2182
  this.currentCommand = null;
2185
2183
  this.isProcessing = false;
2184
+ reject(new Error(`Command timeout after ${this.timeoutMs}ms: ${description || id}`));
2186
2185
  this.processNext();
2187
2186
  }
2188
2187
  }
@@ -2264,7 +2263,7 @@ var UdtTowerCommands = class {
2264
2263
  this.deps.bleConnection.lastSuccessfulCommand = Date.now();
2265
2264
  } catch (error) {
2266
2265
  this.deps.logger.error(`command send error: ${error}`, "[UDT][CMD]");
2267
- const errorMsg = error?.message ?? new String(error);
2266
+ const errorMsg = error?.message ?? String(error);
2268
2267
  const wasCancelled = errorMsg.includes("User cancelled");
2269
2268
  const maxRetriesReached = this.deps.retrySendCommandCount.value >= this.deps.retrySendCommandMax;
2270
2269
  const isDisconnected = errorMsg.includes("Cannot read properties of null") || errorMsg.includes("GATT Server is disconnected") || errorMsg.includes("Device is not connected") || errorMsg.includes("BluetoothConnectionError") || !this.deps.bleConnection.isConnected;
@@ -2276,9 +2275,9 @@ var UdtTowerCommands = class {
2276
2275
  if (!maxRetriesReached && this.deps.bleConnection.isConnected && !wasCancelled) {
2277
2276
  this.deps.logger.info(`retrying tower command attempt ${this.deps.retrySendCommandCount.value + 1}`, "[UDT][CMD]");
2278
2277
  this.deps.retrySendCommandCount.value++;
2279
- setTimeout(() => {
2280
- this.sendTowerCommandDirect(command);
2281
- }, 250 * this.deps.retrySendCommandCount.value);
2278
+ const delay = 250 * this.deps.retrySendCommandCount.value;
2279
+ await new Promise((resolve) => setTimeout(resolve, delay));
2280
+ return await this.sendTowerCommandDirect(command);
2282
2281
  } else {
2283
2282
  this.deps.retrySendCommandCount.value = 0;
2284
2283
  }
@@ -2326,9 +2325,13 @@ var UdtTowerCommands = class {
2326
2325
  this.deps.logDetail && this.deps.logger.debug(`Light Parameter ${JSON.stringify(lights)}`, "[UDT][CMD]");
2327
2326
  this.deps.logger.info("Sending light commands", "[UDT][CMD]");
2328
2327
  const layerCommands = this.mapLightsToLayerCommands(lights);
2329
- for (const { layerIndex, lightIndex, effect } of layerCommands) {
2330
- await this.setLEDStateful(layerIndex, lightIndex, effect);
2328
+ const currentState = this.deps.getCurrentTowerState();
2329
+ for (const { layerIndex, lightIndex, effect, loop } of layerCommands) {
2330
+ currentState.layer[layerIndex].light[lightIndex] = { effect, loop };
2331
2331
  }
2332
+ const command = this.deps.commandFactory.createStatefulCommand(currentState, {});
2333
+ this.deps.setTowerState(currentState, "lights");
2334
+ await this.sendTowerCommand(command, "lights");
2332
2335
  }
2333
2336
  /**
2334
2337
  * Maps the Lights object to layer/light index commands for setLEDStateful.
@@ -2342,7 +2345,7 @@ var UdtTowerCommands = class {
2342
2345
  const layerIndex = this.getTowerLayerForLevel(doorwayLight.level);
2343
2346
  const lightIndex = this.getLightIndexForSide(doorwayLight.position);
2344
2347
  const effect = LIGHT_EFFECTS[doorwayLight.style] || LIGHT_EFFECTS.off;
2345
- commands.push({ layerIndex, lightIndex, effect, loop: true });
2348
+ commands.push({ layerIndex, lightIndex, effect, loop: effect !== LIGHT_EFFECTS.off });
2346
2349
  }
2347
2350
  }
2348
2351
  if (lights.ledge) {
@@ -2350,7 +2353,7 @@ var UdtTowerCommands = class {
2350
2353
  const layerIndex = TOWER_LAYERS.LEDGE;
2351
2354
  const lightIndex = this.getLedgeLightIndexForSide(ledgeLight.position);
2352
2355
  const effect = LIGHT_EFFECTS[ledgeLight.style] || LIGHT_EFFECTS.off;
2353
- commands.push({ layerIndex, lightIndex, effect, loop: false });
2356
+ commands.push({ layerIndex, lightIndex, effect, loop: effect !== LIGHT_EFFECTS.off });
2354
2357
  }
2355
2358
  }
2356
2359
  if (lights.base) {
@@ -2358,7 +2361,7 @@ var UdtTowerCommands = class {
2358
2361
  const layerIndex = baseLight.position.level === "top" || baseLight.position.level === "b" ? TOWER_LAYERS.BASE2 : TOWER_LAYERS.BASE1;
2359
2362
  const lightIndex = this.getBaseLightIndexForSide(baseLight.position.side);
2360
2363
  const effect = LIGHT_EFFECTS[baseLight.style] || LIGHT_EFFECTS.off;
2361
- commands.push({ layerIndex, lightIndex, effect, loop: false });
2364
+ commands.push({ layerIndex, lightIndex, effect, loop: effect !== LIGHT_EFFECTS.off });
2362
2365
  }
2363
2366
  }
2364
2367
  return commands;
@@ -2564,8 +2567,7 @@ var UdtTowerCommands = class {
2564
2567
  };
2565
2568
  const command = this.deps.commandFactory.createStatefulCommand(currentState, modifications);
2566
2569
  await this.sendTowerCommand(command, "resetTowerSkullCount");
2567
- const updatedState = { ...currentState };
2568
- updatedState.beam.count = 0;
2570
+ const updatedState = { ...currentState, beam: { ...currentState.beam, count: 0 } };
2569
2571
  this.deps.setTowerState(updatedState, "resetTowerSkullCount");
2570
2572
  }
2571
2573
  /**
@@ -2836,10 +2838,15 @@ var UltimateDarkTower = class {
2836
2838
  this.onCalibrationComplete = () => {
2837
2839
  };
2838
2840
  this.onSkullDrop = (towerSkullCount) => {
2841
+ void towerSkullCount;
2839
2842
  };
2840
2843
  this.onBatteryLevelNotify = (millivolts) => {
2844
+ void millivolts;
2841
2845
  };
2842
2846
  this.onTowerStateUpdate = (newState, oldState, source) => {
2847
+ void newState;
2848
+ void oldState;
2849
+ void source;
2843
2850
  };
2844
2851
  // utility
2845
2852
  this._logDetail = false;
@@ -2870,6 +2877,12 @@ var UltimateDarkTower = class {
2870
2877
  this.commandFactory = new UdtCommandFactory();
2871
2878
  const commandDependencies = this.createCommandDependencies();
2872
2879
  this.towerCommands = new UdtTowerCommands(commandDependencies);
2880
+ if (config?.brokenSeals) {
2881
+ for (const seal of config.brokenSeals) {
2882
+ const sealKey = `${seal.level}-${seal.side}`;
2883
+ this.brokenSeals.add(sealKey);
2884
+ }
2885
+ }
2873
2886
  }
2874
2887
  /**
2875
2888
  * Set up the tower response callback after all components are initialized
@@ -2984,23 +2997,23 @@ var UltimateDarkTower = class {
2984
2997
  return this.previousBatteryPercentage;
2985
2998
  }
2986
2999
  // Getter/setter methods for connection configuration
2987
- get batteryNotifyFrequency() {
2988
- return this.bleConnection.batteryNotifyFrequency;
3000
+ get batteryLogFrequency() {
3001
+ return this.bleConnection.batteryLogFrequency;
2989
3002
  }
2990
- set batteryNotifyFrequency(value) {
2991
- this.bleConnection.batteryNotifyFrequency = value;
3003
+ set batteryLogFrequency(value) {
3004
+ this.bleConnection.batteryLogFrequency = value;
2992
3005
  }
2993
- get batteryNotifyOnValueChangeOnly() {
2994
- return this.bleConnection.batteryNotifyOnValueChangeOnly;
3006
+ get batteryLogOnChangeOnly() {
3007
+ return this.bleConnection.batteryLogOnChangeOnly;
2995
3008
  }
2996
- set batteryNotifyOnValueChangeOnly(value) {
2997
- this.bleConnection.batteryNotifyOnValueChangeOnly = value;
3009
+ set batteryLogOnChangeOnly(value) {
3010
+ this.bleConnection.batteryLogOnChangeOnly = value;
2998
3011
  }
2999
- get batteryNotifyEnabled() {
3000
- return this.bleConnection.batteryNotifyEnabled;
3012
+ get batteryLogEnabled() {
3013
+ return this.bleConnection.batteryLogEnabled;
3001
3014
  }
3002
- set batteryNotifyEnabled(value) {
3003
- this.bleConnection.batteryNotifyEnabled = value;
3015
+ set batteryLogEnabled(value) {
3016
+ this.bleConnection.batteryLogEnabled = value;
3004
3017
  }
3005
3018
  get logTowerResponses() {
3006
3019
  return this.bleConnection.logTowerResponses;
@@ -3184,11 +3197,10 @@ var UltimateDarkTower = class {
3184
3197
  * @returns Promise that resolves when the command is sent
3185
3198
  */
3186
3199
  async sendTowerState(towerState) {
3187
- const { rtdt_pack_state: rtdt_pack_state2 } = await Promise.resolve().then(() => (init_udtTowerState(), udtTowerState_exports));
3188
3200
  const stateToSend = { ...towerState };
3189
3201
  stateToSend.audio = { sample: 0, loop: false, volume: 0 };
3190
3202
  const stateData = new Uint8Array(TOWER_STATE_DATA_SIZE);
3191
- const success = rtdt_pack_state2(stateData, TOWER_STATE_DATA_SIZE, stateToSend);
3203
+ const success = rtdt_pack_state(stateData, TOWER_STATE_DATA_SIZE, stateToSend);
3192
3204
  if (!success) {
3193
3205
  throw new Error("Failed to pack tower state data");
3194
3206
  }
@@ -3216,11 +3228,9 @@ var UltimateDarkTower = class {
3216
3228
  * @param stateData - The 19-byte state data from tower response
3217
3229
  */
3218
3230
  updateTowerStateFromResponse(stateData) {
3219
- Promise.resolve().then(() => (init_udtTowerState(), udtTowerState_exports)).then(({ rtdt_unpack_state: rtdt_unpack_state2 }) => {
3220
- const newState = rtdt_unpack_state2(stateData);
3221
- newState.audio = { sample: 0, loop: false, volume: this.currentTowerState.audio.volume };
3222
- this.setTowerState(newState, "tower response");
3223
- });
3231
+ const newState = rtdt_unpack_state(stateData);
3232
+ newState.audio = { sample: 0, loop: false, volume: this.currentTowerState.audio.volume };
3233
+ this.setTowerState(newState, "tower response");
3224
3234
  }
3225
3235
  //#endregion
3226
3236
  /**
@@ -3380,6 +3390,25 @@ var UltimateDarkTower = class {
3380
3390
  return { level, side };
3381
3391
  });
3382
3392
  }
3393
+ /**
3394
+ * Marks a seal as broken in software tracking without sending any commands to the tower.
3395
+ * Use this to restore game state (e.g., resuming a game where seals were already broken).
3396
+ * Unlike breakSeal(), this does NOT trigger sound or light effects on the tower.
3397
+ * @param seal - Seal identifier to mark as broken
3398
+ */
3399
+ markSealBroken(seal) {
3400
+ const sealKey = `${seal.level}-${seal.side}`;
3401
+ this.brokenSeals.add(sealKey);
3402
+ }
3403
+ /**
3404
+ * Marks a seal as unbroken in software tracking without sending any commands to the tower.
3405
+ * Use this to undo a seal break or restore individual seals for game state management.
3406
+ * @param seal - Seal identifier to mark as unbroken
3407
+ */
3408
+ markSealRestored(seal) {
3409
+ const sealKey = `${seal.level}-${seal.side}`;
3410
+ this.brokenSeals.delete(sealKey);
3411
+ }
3383
3412
  /**
3384
3413
  * Resets the broken seals tracking (clears all broken seals).
3385
3414
  */
@@ -3429,7 +3458,7 @@ var UltimateDarkTower = class {
3429
3458
  * @param {LogOutput[]} outputs - Array of log outputs to use (e.g., ConsoleOutput, DOMOutput)
3430
3459
  */
3431
3460
  setLoggerOutputs(outputs) {
3432
- this.logger.outputs = [];
3461
+ this.logger.clearOutputs();
3433
3462
  outputs.forEach((output) => this.logger.addOutput(output));
3434
3463
  }
3435
3464
  /**
@@ -3527,8 +3556,7 @@ var UltimateDarkTower_default = UltimateDarkTower;
3527
3556
  // src/index.ts
3528
3557
  init_udtConstants();
3529
3558
  init_udtBluetoothAdapter();
3530
- init_udtTowerState();
3531
- var src_default = UltimateDarkTower_default;
3559
+ var index_default = UltimateDarkTower_default;
3532
3560
  export {
3533
3561
  AUDIO_COMMAND_POS,
3534
3562
  BATTERY_STATUS_FREQUENCY,
@@ -3589,7 +3617,7 @@ export {
3589
3617
  VOLUME_DESCRIPTIONS,
3590
3618
  VOLUME_ICONS,
3591
3619
  createDefaultTowerState,
3592
- src_default as default,
3620
+ index_default as default,
3593
3621
  drumPositionCmds,
3594
3622
  isCalibrated,
3595
3623
  logger,