ultimatedarktower 4.0.1 → 4.1.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 (39) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +105 -253
  3. package/dist/esm/index.mjs +943 -25
  4. package/dist/src/UltimateDarkTower.d.ts +7 -0
  5. package/dist/src/UltimateDarkTower.js +13 -1
  6. package/dist/src/UltimateDarkTower.js.map +1 -1
  7. package/dist/src/adapters/NoopBluetoothAdapter.d.ts +26 -0
  8. package/dist/src/adapters/NoopBluetoothAdapter.js +50 -0
  9. package/dist/src/adapters/NoopBluetoothAdapter.js.map +1 -0
  10. package/dist/src/index.d.ts +10 -0
  11. package/dist/src/index.js +29 -1
  12. package/dist/src/index.js.map +1 -1
  13. package/dist/src/udtBleConnection.d.ts +13 -0
  14. package/dist/src/udtBleConnection.js +66 -19
  15. package/dist/src/udtBleConnection.js.map +1 -1
  16. package/dist/src/udtBluetoothAdapterFactory.d.ts +3 -1
  17. package/dist/src/udtBluetoothAdapterFactory.js +6 -0
  18. package/dist/src/udtBluetoothAdapterFactory.js.map +1 -1
  19. package/dist/src/udtBoardAdjacency.d.ts +25 -0
  20. package/dist/src/udtBoardAdjacency.js +397 -0
  21. package/dist/src/udtBoardAdjacency.js.map +1 -0
  22. package/dist/src/udtBoardAnchors.d.ts +34 -0
  23. package/dist/src/udtBoardAnchors.js +357 -0
  24. package/dist/src/udtBoardAnchors.js.map +1 -0
  25. package/dist/src/udtDisplayExports.d.ts +2 -0
  26. package/dist/src/udtDisplayExports.js +26 -0
  27. package/dist/src/udtDisplayExports.js.map +1 -0
  28. package/dist/src/udtFoes.d.ts +53 -0
  29. package/dist/src/udtFoes.js +47 -0
  30. package/dist/src/udtFoes.js.map +1 -0
  31. package/dist/src/udtHeroes.d.ts +30 -0
  32. package/dist/src/udtHeroes.js +38 -0
  33. package/dist/src/udtHeroes.js.map +1 -0
  34. package/dist/src/udtMonuments.d.ts +23 -0
  35. package/dist/src/udtMonuments.js +20 -0
  36. package/dist/src/udtMonuments.js.map +1 -0
  37. package/dist/src/udtTowerResponse.js +1 -2
  38. package/dist/src/udtTowerResponse.js.map +1 -1
  39. package/package.json +2 -2
@@ -9,8 +9,13 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
9
9
  if (typeof require !== "undefined") return require.apply(this, arguments);
10
10
  throw Error('Dynamic require of "' + x + '" is not supported');
11
11
  });
12
- var __esm = (fn, res) => function __init() {
13
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
+ var __esm = (fn, res, err) => function __init() {
13
+ if (err) throw err[0];
14
+ try {
15
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
16
+ } catch (e) {
17
+ throw err = [e], e;
18
+ }
14
19
  };
15
20
  var __export = (target, all) => {
16
21
  for (var name in all)
@@ -862,6 +867,52 @@ var init_NodeBluetoothAdapter = __esm({
862
867
  }
863
868
  });
864
869
 
870
+ // src/adapters/NoopBluetoothAdapter.ts
871
+ var NoopBluetoothAdapter_exports = {};
872
+ __export(NoopBluetoothAdapter_exports, {
873
+ NoopBluetoothAdapter: () => NoopBluetoothAdapter
874
+ });
875
+ var NoopBluetoothAdapter;
876
+ var init_NoopBluetoothAdapter = __esm({
877
+ "src/adapters/NoopBluetoothAdapter.ts"() {
878
+ "use strict";
879
+ init_udtBluetoothAdapter();
880
+ NoopBluetoothAdapter = class {
881
+ async connect(_deviceName, _serviceUuids) {
882
+ void _deviceName;
883
+ void _serviceUuids;
884
+ throw new BluetoothError("Bluetooth is disabled (platform: none)");
885
+ }
886
+ async disconnect() {
887
+ }
888
+ isConnected() {
889
+ return false;
890
+ }
891
+ isGattConnected() {
892
+ return false;
893
+ }
894
+ async writeCharacteristic(_data) {
895
+ void _data;
896
+ throw new BluetoothError("Bluetooth is disabled (platform: none)");
897
+ }
898
+ onCharacteristicValueChanged(callback) {
899
+ this.characteristicCallback = callback;
900
+ }
901
+ onDisconnect(callback) {
902
+ this.disconnectCallback = callback;
903
+ }
904
+ onBluetoothAvailabilityChanged(callback) {
905
+ this.availabilityCallback = callback;
906
+ }
907
+ async readDeviceInformation() {
908
+ return {};
909
+ }
910
+ async cleanup() {
911
+ }
912
+ };
913
+ }
914
+ });
915
+
865
916
  // src/UltimateDarkTower.ts
866
917
  init_udtConstants();
867
918
 
@@ -1457,9 +1508,8 @@ var TowerResponseProcessor = class {
1457
1508
  return [towerCommand.name, commandToPacketString(command)];
1458
1509
  case TC.BATTERY: {
1459
1510
  const millivolts = getMilliVoltsFromTowerResponse(command);
1460
- const retval = [towerCommand.name, milliVoltsToPercentage(millivolts)];
1511
+ const retval = [towerCommand.name, `${milliVoltsToPercentage(millivolts)} (${(millivolts / 1e3).toFixed(2)}mv)`];
1461
1512
  if (this.logDetail) {
1462
- retval.push(`${millivolts}mv`);
1463
1513
  retval.push(commandToPacketString(command));
1464
1514
  }
1465
1515
  return retval;
@@ -1500,11 +1550,12 @@ var TowerResponseProcessor = class {
1500
1550
  };
1501
1551
 
1502
1552
  // src/udtBluetoothAdapterFactory.ts
1503
- var BluetoothPlatform = /* @__PURE__ */ ((BluetoothPlatform3) => {
1504
- BluetoothPlatform3["WEB"] = "web";
1505
- BluetoothPlatform3["NODE"] = "node";
1506
- BluetoothPlatform3["AUTO"] = "auto";
1507
- return BluetoothPlatform3;
1553
+ var BluetoothPlatform = /* @__PURE__ */ ((BluetoothPlatform2) => {
1554
+ BluetoothPlatform2["WEB"] = "web";
1555
+ BluetoothPlatform2["NODE"] = "node";
1556
+ BluetoothPlatform2["AUTO"] = "auto";
1557
+ BluetoothPlatform2["NONE"] = "none";
1558
+ return BluetoothPlatform2;
1508
1559
  })(BluetoothPlatform || {});
1509
1560
  var BluetoothAdapterFactory = class {
1510
1561
  /**
@@ -1523,6 +1574,10 @@ var BluetoothAdapterFactory = class {
1523
1574
  const { NodeBluetoothAdapter: NodeBluetoothAdapter2 } = (init_NodeBluetoothAdapter(), __toCommonJS(NodeBluetoothAdapter_exports));
1524
1575
  return new NodeBluetoothAdapter2();
1525
1576
  }
1577
+ case "none" /* NONE */: {
1578
+ const { NoopBluetoothAdapter: NoopBluetoothAdapter2 } = (init_NoopBluetoothAdapter(), __toCommonJS(NoopBluetoothAdapter_exports));
1579
+ return new NoopBluetoothAdapter2();
1580
+ }
1526
1581
  default:
1527
1582
  throw new Error(`Unsupported Bluetooth platform: ${detectedPlatform}`);
1528
1583
  }
@@ -1559,6 +1614,12 @@ var UdtBleConnection = class {
1559
1614
  // higher-level state (command queue, tower state, broken seals) at the
1560
1615
  // moment a disconnect cause fires.
1561
1616
  this.snapshotProviders = null;
1617
+ // Bluetooth adapter (platform-agnostic).
1618
+ // Null until an adapter is provided or lazily created on first connect() —
1619
+ // construction never triggers platform detection, so creating an
1620
+ // UltimateDarkTower in a non-Bluetooth environment (e.g. iOS Safari) does
1621
+ // not throw. The detection error, if any, surfaces at connect() time.
1622
+ this.bluetoothAdapter = null;
1562
1623
  // Connection state
1563
1624
  this.isConnected = false;
1564
1625
  this.isDisposed = false;
@@ -1604,17 +1665,41 @@ var UdtBleConnection = class {
1604
1665
  this.callbacks = callbacks;
1605
1666
  this.responseProcessor = new TowerResponseProcessor();
1606
1667
  this.recorder = recorder ?? null;
1607
- this.bluetoothAdapter = adapter || BluetoothAdapterFactory.create("auto" /* AUTO */);
1608
- this.bluetoothAdapter.onCharacteristicValueChanged((data) => {
1668
+ if (adapter) {
1669
+ this.bluetoothAdapter = adapter;
1670
+ this.wireAdapterCallbacks(adapter);
1671
+ }
1672
+ }
1673
+ /**
1674
+ * Wires this connection's internal handlers onto a Bluetooth adapter.
1675
+ * Called when an adapter is supplied at construction, or when one is
1676
+ * lazily created on first connect().
1677
+ */
1678
+ wireAdapterCallbacks(adapter) {
1679
+ adapter.onCharacteristicValueChanged((data) => {
1609
1680
  this.onRxData(data);
1610
1681
  });
1611
- this.bluetoothAdapter.onDisconnect(() => {
1682
+ adapter.onDisconnect(() => {
1612
1683
  this.onTowerDeviceDisconnected();
1613
1684
  });
1614
- this.bluetoothAdapter.onBluetoothAvailabilityChanged((available) => {
1685
+ adapter.onBluetoothAvailabilityChanged((available) => {
1615
1686
  this.bleAvailabilityChange(available);
1616
1687
  });
1617
1688
  }
1689
+ /**
1690
+ * Returns the Bluetooth adapter, lazily creating one via platform
1691
+ * auto-detection on first use. Platform-detection errors (e.g. no Web
1692
+ * Bluetooth on iOS) surface here, at connect time, rather than at
1693
+ * construction.
1694
+ */
1695
+ ensureAdapter() {
1696
+ if (!this.bluetoothAdapter) {
1697
+ const adapter = BluetoothAdapterFactory.create("auto" /* AUTO */);
1698
+ this.bluetoothAdapter = adapter;
1699
+ this.wireAdapterCallbacks(adapter);
1700
+ }
1701
+ return this.bluetoothAdapter;
1702
+ }
1618
1703
  setDiagnosticsSnapshotProviders(providers) {
1619
1704
  this.snapshotProviders = providers;
1620
1705
  }
@@ -1650,7 +1735,8 @@ var UdtBleConnection = class {
1650
1735
  }
1651
1736
  this.logger.info("Looking for Tower...", "[UDT]");
1652
1737
  try {
1653
- await this.bluetoothAdapter.connect(
1738
+ const adapter = this.ensureAdapter();
1739
+ await adapter.connect(
1654
1740
  TOWER_DEVICE_NAME,
1655
1741
  [UART_SERVICE_UUID, DIS_SERVICE_UUID]
1656
1742
  );
@@ -1675,8 +1761,9 @@ var UdtBleConnection = class {
1675
1761
  if (this.isConnected) {
1676
1762
  this.recordIncident("user_initiated");
1677
1763
  }
1678
- if (this.bluetoothAdapter.isConnected()) {
1679
- await this.bluetoothAdapter.disconnect();
1764
+ const adapter = this.bluetoothAdapter;
1765
+ if (adapter?.isConnected()) {
1766
+ await adapter.disconnect();
1680
1767
  this.logger.info("Tower disconnected", "[UDT]");
1681
1768
  }
1682
1769
  this.handleDisconnection();
@@ -1686,8 +1773,12 @@ var UdtBleConnection = class {
1686
1773
  * Used by UdtTowerCommands instead of direct characteristic access.
1687
1774
  */
1688
1775
  async writeCommand(command) {
1776
+ const adapter = this.bluetoothAdapter;
1777
+ if (!adapter) {
1778
+ throw new Error("Cannot write command: not connected (no Bluetooth adapter)");
1779
+ }
1689
1780
  this.recorder?.recordCommandPayload("cmd_sent", command, { len: command.length });
1690
- return await this.bluetoothAdapter.writeCharacteristic(command);
1781
+ return await adapter.writeCharacteristic(command);
1691
1782
  }
1692
1783
  /**
1693
1784
  * Processes received data from the RX characteristic (platform-agnostic).
@@ -1732,7 +1823,7 @@ var UdtBleConnection = class {
1732
1823
  }
1733
1824
  handleTowerStateResponse(receivedData) {
1734
1825
  const dataSkullDropCount = receivedData[SKULL_DROP_COUNT_POS];
1735
- const state = rtdt_unpack_state(receivedData);
1826
+ const state = rtdt_unpack_state(receivedData.slice(TOWER_STATE_DATA_OFFSET, TOWER_STATE_RESPONSE_MIN_LENGTH));
1736
1827
  this.logger.debug(`Tower State: ${JSON.stringify(state)} `, "[UDT][BLE]");
1737
1828
  this.recorder?.recordEvent("tower_state_response");
1738
1829
  if (this.performingCalibration) {
@@ -1812,7 +1903,7 @@ var UdtBleConnection = class {
1812
1903
  if (!this.isConnected) {
1813
1904
  return;
1814
1905
  }
1815
- if (!this.bluetoothAdapter.isGattConnected()) {
1906
+ if (!(this.bluetoothAdapter?.isGattConnected() ?? false)) {
1816
1907
  this.logger.warn("GATT connection lost detected during health check", "[UDT][BLE]");
1817
1908
  this.recordIncident("gatt_health_check");
1818
1909
  this.handleDisconnection();
@@ -1830,7 +1921,7 @@ var UdtBleConnection = class {
1830
1921
  }
1831
1922
  if (this.batteryHeartbeatVerifyConnection) {
1832
1923
  this.logger.info("Verifying tower connection status before triggering disconnection...", "[UDT][BLE]");
1833
- if (this.bluetoothAdapter.isGattConnected()) {
1924
+ if (this.bluetoothAdapter?.isGattConnected() ?? false) {
1834
1925
  this.logger.info("GATT connection still available - heartbeat timeout may be temporary", "[UDT][BLE]");
1835
1926
  this.recorder?.recordEvent("heartbeat_late", {
1836
1927
  sinceMs: timeSinceLastBatteryHeartbeat,
@@ -1878,7 +1969,7 @@ var UdtBleConnection = class {
1878
1969
  if (!this.isConnected) {
1879
1970
  return false;
1880
1971
  }
1881
- return this.bluetoothAdapter.isGattConnected();
1972
+ return this.bluetoothAdapter?.isGattConnected() ?? false;
1882
1973
  }
1883
1974
  getConnectionStatus() {
1884
1975
  const now = Date.now();
@@ -1886,7 +1977,7 @@ var UdtBleConnection = class {
1886
1977
  const timeSinceLastCommand = this.lastSuccessfulCommand ? now - this.lastSuccessfulCommand : -1;
1887
1978
  return {
1888
1979
  isConnected: this.isConnected,
1889
- isGattConnected: this.bluetoothAdapter.isGattConnected(),
1980
+ isGattConnected: this.bluetoothAdapter?.isGattConnected() ?? false,
1890
1981
  lastBatteryHeartbeatMs: timeSinceLastBattery,
1891
1982
  lastCommandResponseMs: timeSinceLastCommand,
1892
1983
  batteryHeartbeatHealthy: timeSinceLastBattery >= 0 && timeSinceLastBattery < this.batteryHeartbeatTimeout,
@@ -1901,9 +1992,11 @@ var UdtBleConnection = class {
1901
1992
  return { ...this.deviceInformation };
1902
1993
  }
1903
1994
  async readDeviceInformation() {
1995
+ const adapter = this.bluetoothAdapter;
1996
+ if (!adapter) return;
1904
1997
  try {
1905
1998
  this.logger.info("Reading device information service...", "[UDT][BLE]");
1906
- this.deviceInformation = await this.bluetoothAdapter.readDeviceInformation();
1999
+ this.deviceInformation = await adapter.readDeviceInformation();
1907
2000
  for (const [key, value] of Object.entries(this.deviceInformation)) {
1908
2001
  if (key !== "lastUpdated" && value) {
1909
2002
  this.logger.info(`Device ${key}: ${value}`, "[UDT][BLE]");
@@ -1921,7 +2014,7 @@ var UdtBleConnection = class {
1921
2014
  if (this.isConnected) {
1922
2015
  await this.disconnect();
1923
2016
  }
1924
- await this.bluetoothAdapter.cleanup();
2017
+ await this.bluetoothAdapter?.cleanup();
1925
2018
  }
1926
2019
  };
1927
2020
 
@@ -3126,6 +3219,15 @@ var UltimateDarkTower = class {
3126
3219
  void oldState;
3127
3220
  void source;
3128
3221
  };
3222
+ /**
3223
+ * Called with the raw bytes of every non-battery tower notification (e.g.
3224
+ * tower-state responses). Use this when you need the verbatim packet rather
3225
+ * than the decoded `TowerState` from {@link onTowerStateUpdate} — for example
3226
+ * a relay forwarding the tower's exact 20-byte state to other consumers.
3227
+ */
3228
+ this.onTowerResponse = (response) => {
3229
+ void response;
3230
+ };
3129
3231
  // utility
3130
3232
  this._logDetail = false;
3131
3233
  this.initializeLogger();
@@ -3162,7 +3264,7 @@ var UltimateDarkTower = class {
3162
3264
  let adapter;
3163
3265
  if (config?.adapter) {
3164
3266
  adapter = config.adapter;
3165
- } else if (config?.platform) {
3267
+ } else if (config?.platform && config.platform !== "auto" /* AUTO */) {
3166
3268
  adapter = BluetoothAdapterFactory.create(config.platform);
3167
3269
  }
3168
3270
  this.towerEventCallbacks = this.createTowerEventCallbacks();
@@ -3213,6 +3315,7 @@ var UltimateDarkTower = class {
3213
3315
  this.updateTowerStateFromResponse(stateData);
3214
3316
  }
3215
3317
  }
3318
+ this.onTowerResponse(response);
3216
3319
  };
3217
3320
  }
3218
3321
  /**
@@ -4339,6 +4442,95 @@ function dumpSeedChars(seed) {
4339
4442
  return { seed: normalized, chars };
4340
4443
  }
4341
4444
 
4445
+ // src/udtHeroes.ts
4446
+ var HEROES = [
4447
+ // Base (4)
4448
+ { id: "brutal-warlord", name: "Brutal Warlord", source: "base" },
4449
+ { id: "orphaned-scion", name: "Orphaned Scion", source: "base" },
4450
+ { id: "relic-hunter", name: "Relic Hunter", source: "base" },
4451
+ { id: "spymaster", name: "Spymaster", source: "base" },
4452
+ // Alliances (2)
4453
+ { id: "archwright", name: "Archwright", source: "alliances" },
4454
+ { id: "haunted-recluse", name: "Haunted Recluse", source: "alliances" },
4455
+ // Covenant (4)
4456
+ { id: "devious-swindler", name: "Devious Swindler", source: "covenant" },
4457
+ { id: "relentless-warden", name: "Relentless Warden", source: "covenant" },
4458
+ { id: "reverent-astromancer", name: "Reverent Astromancer", source: "covenant" },
4459
+ { id: "undaunted-aegis", name: "Undaunted Aegis", source: "covenant" },
4460
+ // Expeditions (4, unreleased — provisional)
4461
+ { id: "jocular-druid", name: "Jocular Druid", source: "expeditions" },
4462
+ { id: "grizzled-mariner", name: "Grizzled Mariner", source: "expeditions" },
4463
+ { id: "clever-tinkerer", name: "Clever Tinkerer", source: "expeditions" },
4464
+ { id: "enlightened-ascetic", name: "Enlightened Ascetic", source: "expeditions" }
4465
+ ];
4466
+ var HERO_BY_ID = Object.freeze(
4467
+ HEROES.reduce((acc, hero) => {
4468
+ acc[hero.id] = hero;
4469
+ return acc;
4470
+ }, {})
4471
+ );
4472
+
4473
+ // src/udtMonuments.ts
4474
+ var MONUMENTS = [
4475
+ { id: "arch-of-the-golden-sun", name: "Arch of the Golden Sun", source: "covenant" },
4476
+ { id: "argent-oak", name: "Argent Oak", source: "covenant" },
4477
+ { id: "cenotaph-of-the-first-prophet", name: "Cenotaph of the First Prophet", source: "covenant" },
4478
+ { id: "colossus-of-bjorn", name: "Colossus of Bjorn", source: "covenant" },
4479
+ { id: "endless-necropolis", name: "Endless Necropolis", source: "covenant" },
4480
+ { id: "moonstone-temple", name: "Moonstone Temple", source: "covenant" },
4481
+ { id: "nightmare-cage", name: "Nightmare Cage", source: "covenant" },
4482
+ { id: "tower-shard", name: "Tower Shard", source: "covenant" }
4483
+ ];
4484
+ var MONUMENT_BY_ID = Object.freeze(
4485
+ MONUMENTS.reduce((acc, monument) => {
4486
+ acc[monument.id] = monument;
4487
+ return acc;
4488
+ }, {})
4489
+ );
4490
+
4491
+ // src/udtFoes.ts
4492
+ var FOE_STATUSES = ["panicked", "unsteady", "ready", "savage", "lethal"];
4493
+ var FOES = [
4494
+ // Tier 1 — level 2
4495
+ { id: "brigands", name: "Brigands", kind: "foe", level: 2, tier: 1, source: "base" },
4496
+ { id: "oreks", name: "Oreks", kind: "foe", level: 2, tier: 1, source: "base" },
4497
+ { id: "shadow-wolves", name: "Shadow Wolves", kind: "foe", level: 2, tier: 1, source: "base" },
4498
+ { id: "spine-fiends", name: "Spine Fiends", kind: "foe", level: 2, tier: 1, source: "base" },
4499
+ // Tier 2 — level 3
4500
+ { id: "frost-trolls", name: "Frost Trolls", kind: "foe", level: 3, tier: 2, source: "base" },
4501
+ { id: "clan-of-neuri", name: "Clan of Neuri", kind: "foe", level: 3, tier: 2, source: "base" },
4502
+ { id: "lemures", name: "Lemures", kind: "foe", level: 3, tier: 2, source: "base" },
4503
+ { id: "widowmade-spiders", name: "Widowmade Spiders", kind: "foe", level: 3, tier: 2, source: "base" },
4504
+ // Tier 3 — level 4
4505
+ { id: "dragons", name: "Dragons", kind: "foe", level: 4, tier: 3, source: "base" },
4506
+ { id: "mormos", name: "Mormos", kind: "foe", level: 4, tier: 3, source: "base" },
4507
+ { id: "striga", name: "Striga", kind: "foe", level: 4, tier: 3, source: "base" },
4508
+ { id: "titans", name: "Titans", kind: "foe", level: 4, tier: 3, source: "base" }
4509
+ ];
4510
+ var ADVERSARY_ROSTER = [
4511
+ { id: "ashstrider", name: "Ashstrider", kind: "adversary", level: 5, source: "base" },
4512
+ { id: "bane-of-omens", name: "Bane of Omens", kind: "adversary", level: 5, source: "base" },
4513
+ { id: "empress-of-shades", name: "Empress of Shades", kind: "adversary", level: 5, source: "base" },
4514
+ { id: "gaze-eternal", name: "Gaze Eternal", kind: "adversary", level: 5, source: "base" },
4515
+ { id: "gravemaw", name: "Gravemaw", kind: "adversary", level: 5, source: "base" },
4516
+ { id: "isa-the-exile", name: "Isa the Exile", kind: "adversary", level: 5, source: "base" },
4517
+ { id: "lingering-rot", name: "Lingering Rot", kind: "adversary", level: 5, source: "base" },
4518
+ { id: "utuk-ku", name: "Utuk'Ku", kind: "adversary", level: 5, source: "base" }
4519
+ ];
4520
+ var ALL_FOES = [...FOES, ...ADVERSARY_ROSTER];
4521
+ var FOE_BY_ID = Object.freeze(
4522
+ ALL_FOES.reduce((acc, foe) => {
4523
+ acc[foe.id] = foe;
4524
+ return acc;
4525
+ }, {})
4526
+ );
4527
+ var FOE_BY_NAME = Object.freeze(
4528
+ ALL_FOES.reduce((acc, foe) => {
4529
+ acc[foe.name] = foe;
4530
+ return acc;
4531
+ }, {})
4532
+ );
4533
+
4342
4534
  // src/udtSystemRandom.ts
4343
4535
  var INT32_MAX = 2147483647;
4344
4536
  var MSEED = 161803398;
@@ -4529,14 +4721,729 @@ var BOARD_LOCATIONS = [
4529
4721
  ];
4530
4722
  var BOARD_LOCATION_BY_NAME = Object.fromEntries(BOARD_LOCATIONS.map((loc) => [loc.name, loc]));
4531
4723
 
4724
+ // src/udtBoardAnchors.ts
4725
+ var BOARD_IMAGE_INFO = {
4726
+ width: 4096,
4727
+ height: 4096,
4728
+ centerX: 0.5,
4729
+ centerY: 0.5,
4730
+ radius: 0.5,
4731
+ northHeadingDegrees: 135
4732
+ };
4733
+ var BOARD_ANCHORS = {
4734
+ "Broken Lands": {
4735
+ hero: { x: 0.80599, y: 0.51238 },
4736
+ foe: { x: 0.8355, y: 0.50648 },
4737
+ marker: { x: 0.81451, y: 0.53009 }
4738
+ },
4739
+ Dayside: {
4740
+ building: { x: 0.74564, y: 0.7754 },
4741
+ skull: { x: 0.75351, y: 0.77147 },
4742
+ hero: { x: 0.78708, y: 0.84542 },
4743
+ foe: { x: 0.82397, y: 0.81232 },
4744
+ marker: { x: 0.79742, y: 0.81818 }
4745
+ },
4746
+ "Egan's End": {
4747
+ building: { x: 0.57088, y: 0.9152 },
4748
+ skull: { x: 0.57506, y: 0.92844 },
4749
+ hero: { x: 0.65606, y: 0.89196 },
4750
+ foe: { x: 0.66675, y: 0.91679 },
4751
+ marker: { x: 0.63365, y: 0.91644 }
4752
+ },
4753
+ Fivepint: {
4754
+ marker: { x: 0.84691, y: 0.76433 },
4755
+ hero: { x: 0.82, y: 0.75615 },
4756
+ foe: { x: 0.83838, y: 0.73603 }
4757
+ },
4758
+ "Green Bridge": {
4759
+ hero: { x: 0.73574, y: 0.71149 },
4760
+ foe: { x: 0.77097, y: 0.67222 },
4761
+ marker: { x: 0.76259, y: 0.69647 }
4762
+ },
4763
+ "Lodestone Mountains": {
4764
+ hero: { x: 0.90983, y: 0.54979 },
4765
+ foe: { x: 0.93133, y: 0.52482 },
4766
+ marker: { x: 0.92439, y: 0.57164 }
4767
+ },
4768
+ "Lower Ice Fangs": {
4769
+ hero: { x: 0.644, y: 0.75336 },
4770
+ foe: { x: 0.60986, y: 0.76715 },
4771
+ marker: { x: 0.65399, y: 0.77853 }
4772
+ },
4773
+ "Muted Forest": {
4774
+ hero: { x: 0.72254, y: 0.57754 },
4775
+ foe: { x: 0.71006, y: 0.53557 },
4776
+ marker: { x: 0.74474, y: 0.54875 }
4777
+ },
4778
+ "Peaks of the Djinn": {
4779
+ hero: { x: 0.52263, y: 0.73716 },
4780
+ foe: { x: 0.53056, y: 0.76095 },
4781
+ marker: { x: 0.53401, y: 0.78715 }
4782
+ },
4783
+ "Pearl of the North": {
4784
+ marker: { x: 0.90091, y: 0.66962 },
4785
+ hero: { x: 0.89715, y: 0.69647 },
4786
+ foe: { x: 0.91852, y: 0.64046 }
4787
+ },
4788
+ "Radiant Mountains": {
4789
+ building: { x: 0.82063, y: 0.64652 },
4790
+ skull: { x: 0.82727, y: 0.64883 },
4791
+ hero: { x: 0.81081, y: 0.6058 },
4792
+ foe: { x: 0.82294, y: 0.57982 },
4793
+ marker: { x: 0.85182, y: 0.59425 }
4794
+ },
4795
+ Rimeweald: {
4796
+ hero: { x: 0.58228, y: 0.83094 },
4797
+ foe: { x: 0.54746, y: 0.84576 },
4798
+ marker: { x: 0.614, y: 0.84852 }
4799
+ },
4800
+ "The Tundra": {
4801
+ hero: { x: 0.71123, y: 0.84955 },
4802
+ foe: { x: 0.69675, y: 0.82094 },
4803
+ marker: { x: 0.72778, y: 0.87369 }
4804
+ },
4805
+ "Tower Scar Desert": {
4806
+ marker: { x: 0.63365, y: 0.57476 },
4807
+ hero: { x: 0.64227, y: 0.53994 },
4808
+ foe: { x: 0.58642, y: 0.61028 }
4809
+ },
4810
+ "Upper Ice Fangs": {
4811
+ building: { x: 0.68089, y: 0.66613 },
4812
+ skull: { x: 0.68571, y: 0.66096 },
4813
+ foe: { x: 0.5478, y: 0.67889 },
4814
+ hero: { x: 0.63882, y: 0.7044 },
4815
+ marker: { x: 0.59159, y: 0.67578 }
4816
+ },
4817
+ "Big Sister": {
4818
+ hero: { x: 0.29269, y: 0.60241 },
4819
+ foe: { x: 0.28651, y: 0.56064 },
4820
+ marker: { x: 0.26136, y: 0.54023 }
4821
+ },
4822
+ "Bleak Wastes": {
4823
+ marker: { x: 0.45929, y: 0.67741 },
4824
+ hero: { x: 0.44885, y: 0.64751 },
4825
+ foe: { x: 0.47163, y: 0.65937 }
4826
+ },
4827
+ "Copper Grove": {
4828
+ hero: { x: 0.34044, y: 0.8742 },
4829
+ foe: { x: 0.37074, y: 0.91984 },
4830
+ marker: { x: 0.3369, y: 0.90017 }
4831
+ },
4832
+ "Dragontooth Lake": {
4833
+ marker: { x: 0.43784, y: 0.75253 },
4834
+ hero: { x: 0.38955, y: 0.70385 },
4835
+ foe: { x: 0.38599, y: 0.7367 }
4836
+ },
4837
+ Duwani: {
4838
+ building: { x: 0.21975, y: 0.79014 },
4839
+ skull: { x: 0.22094, y: 0.79251 },
4840
+ hero: { x: 0.20946, y: 0.83367 },
4841
+ foe: { x: 0.18729, y: 0.76599 },
4842
+ marker: { x: 0.16988, y: 0.77905 }
4843
+ },
4844
+ "Forest of Shades": {
4845
+ hero: { x: 0.36184, y: 0.57047 },
4846
+ foe: { x: 0.35393, y: 0.54355 },
4847
+ marker: { x: 0.37965, y: 0.6053 }
4848
+ },
4849
+ "Greater Tombstones": {
4850
+ building: { x: 0.31989, y: 0.66902 },
4851
+ skull: { x: 0.32384, y: 0.66467 },
4852
+ hero: { x: 0.27041, y: 0.67773 },
4853
+ foe: { x: 0.22133, y: 0.66546 },
4854
+ marker: { x: 0.25933, y: 0.65675 }
4855
+ },
4856
+ "Inner Kinghills": {
4857
+ building: { x: 0.4061, y: 0.83461 },
4858
+ skull: { x: 0.41279, y: 0.83658 },
4859
+ hero: { x: 0.45685, y: 0.81494 },
4860
+ foe: { x: 0.43364, y: 0.87592 },
4861
+ marker: { x: 0.46236, y: 0.84169 }
4862
+ },
4863
+ "Jewel Hills": {
4864
+ marker: { x: 0.26293, y: 0.86082 },
4865
+ foe: { x: 0.29301, y: 0.81688 },
4866
+ hero: { x: 0.27599, y: 0.84063 }
4867
+ },
4868
+ "Lake of Songs": {
4869
+ hero: { x: 0.1157, y: 0.71675 },
4870
+ foe: { x: 0.14498, y: 0.72862 },
4871
+ marker: { x: 0.10778, y: 0.68825 }
4872
+ },
4873
+ "Lesser Tombstones": {
4874
+ hero: { x: 0.19169, y: 0.5889 },
4875
+ foe: { x: 0.18179, y: 0.6178 },
4876
+ marker: { x: 0.1715, y: 0.655 }
4877
+ },
4878
+ "Outer Kinghills": {
4879
+ hero: { x: 0.49606, y: 0.90673 },
4880
+ foe: { x: 0.44183, y: 0.91306 },
4881
+ marker: { x: 0.46242, y: 0.9289 }
4882
+ },
4883
+ "The Decaying Wilds": {
4884
+ marker: { x: 0.34526, y: 0.79037 },
4885
+ hero: { x: 0.31399, y: 0.77414 },
4886
+ foe: { x: 0.33339, y: 0.76503 }
4887
+ },
4888
+ "Three Rivers": {
4889
+ building: { x: 0.10461, y: 0.59524 },
4890
+ skull: { x: 0.10976, y: 0.59445 },
4891
+ hero: { x: 0.10066, y: 0.56159 },
4892
+ foe: { x: 0.05672, y: 0.60869 },
4893
+ marker: { x: 0.07216, y: 0.56516 }
4894
+ },
4895
+ "Utar's Barrows": {
4896
+ hero: { x: 0.21227, y: 0.72268 },
4897
+ foe: { x: 0.27322, y: 0.75554 },
4898
+ marker: { x: 0.25502, y: 0.73416 }
4899
+ },
4900
+ Archmont: {
4901
+ marker: { x: 0.28906, y: 0.28849 },
4902
+ hero: { x: 0.2475, y: 0.26474 },
4903
+ foe: { x: 0.22375, y: 0.24297 }
4904
+ },
4905
+ "Azkol's Bane": {
4906
+ hero: { x: 0.31755, y: 0.2026 },
4907
+ foe: { x: 0.35515, y: 0.20339 },
4908
+ marker: { x: 0.2851, y: 0.22002 }
4909
+ },
4910
+ "Bone Hills": {
4911
+ marker: { x: 0.26112, y: 0.1386 },
4912
+ hero: { x: 0.23354, y: 0.15298 },
4913
+ foe: { x: 0.28374, y: 0.10136 }
4914
+ },
4915
+ "Howling Desert": {
4916
+ building: { x: 0.46416, y: 0.25928 },
4917
+ skull: { x: 0.46145, y: 0.25218 },
4918
+ hero: { x: 0.41951, y: 0.22546 },
4919
+ foe: { x: 0.47464, y: 0.2011 },
4920
+ marker: { x: 0.44488, y: 0.20009 }
4921
+ },
4922
+ Irontops: {
4923
+ hero: { x: 0.44934, y: 0.35744 },
4924
+ foe: { x: 0.46636, y: 0.32986 },
4925
+ marker: { x: 0.425, y: 0.33632 }
4926
+ },
4927
+ "Little Sister": {
4928
+ hero: { x: 0.23451, y: 0.34805 },
4929
+ foe: { x: 0.21272, y: 0.32604 },
4930
+ marker: { x: 0.19827, y: 0.36466 }
4931
+ },
4932
+ "Middle Sister": {
4933
+ hero: { x: 0.16203, y: 0.46453 },
4934
+ foe: { x: 0.1493, y: 0.49904 },
4935
+ marker: { x: 0.15513, y: 0.43088 }
4936
+ },
4937
+ "Mountains of the Watchers": {
4938
+ hero: { x: 0.49023, y: 0.12066 },
4939
+ foe: { x: 0.45633, y: 0.09633 },
4940
+ marker: { x: 0.4994, y: 0.18246 }
4941
+ },
4942
+ "Pine Barrens": {
4943
+ hero: { x: 0.06957, y: 0.49147 },
4944
+ foe: { x: 0.07316, y: 0.4185 },
4945
+ marker: { x: 0.06957, y: 0.45478 }
4946
+ },
4947
+ "Sands of Madness": {
4948
+ building: { x: 0.27133, y: 0.44721 },
4949
+ skull: { x: 0.26256, y: 0.44561 },
4950
+ hero: { x: 0.28249, y: 0.39777 },
4951
+ foe: { x: 0.30083, y: 0.33636 },
4952
+ marker: { x: 0.29964, y: 0.37464 }
4953
+ },
4954
+ "Southern Wastes": {
4955
+ marker: { x: 0.16607, y: 0.22512 },
4956
+ building: { x: 0.16248, y: 0.29211 },
4957
+ skull: { x: 0.1553, y: 0.28931 },
4958
+ hero: { x: 0.16686, y: 0.24466 },
4959
+ foe: { x: 0.17723, y: 0.19242 }
4960
+ },
4961
+ "The Cloister": {
4962
+ hero: { x: 0.35027, y: 0.30566 },
4963
+ foe: { x: 0.39692, y: 0.28014 },
4964
+ marker: { x: 0.36862, y: 0.28254 }
4965
+ },
4966
+ "The Emerald Expanse": {
4967
+ building: { x: 0.3746, y: 0.14179 },
4968
+ skull: { x: 0.37699, y: 0.14817 },
4969
+ hero: { x: 0.34071, y: 0.08955 },
4970
+ foe: { x: 0.43121, y: 0.05407 },
4971
+ marker: { x: 0.38177, y: 0.08357 }
4972
+ },
4973
+ "The Throne": {
4974
+ marker: { x: 0.35865, y: 0.43166 },
4975
+ hero: { x: 0.36782, y: 0.38979 },
4976
+ foe: { x: 0.33991, y: 0.46236 }
4977
+ },
4978
+ "Ulamel's Hollow": {
4979
+ foe: { x: 0.10307, y: 0.38022 },
4980
+ hero: { x: 0.11981, y: 0.34872 },
4981
+ marker: { x: 0.0935, y: 0.34952 }
4982
+ },
4983
+ Anza: {
4984
+ building: { x: 0.69519, y: 0.13269 },
4985
+ skull: { x: 0.69876, y: 0.1242 },
4986
+ hero: { x: 0.67642, y: 0.16664 },
4987
+ foe: { x: 0.72244, y: 0.16888 },
4988
+ marker: { x: 0.74656, y: 0.14609 }
4989
+ },
4990
+ Arkartus: {
4991
+ building: { x: 0.81849, y: 0.28504 },
4992
+ skull: { x: 0.82832, y: 0.27789 },
4993
+ hero: { x: 0.81983, y: 0.23544 },
4994
+ foe: { x: 0.87702, y: 0.25376 },
4995
+ marker: { x: 0.84753, y: 0.24036 }
4996
+ },
4997
+ "Ash Hills": {
4998
+ hero: { x: 0.68312, y: 0.44364 },
4999
+ foe: { x: 0.69161, y: 0.46732 },
5000
+ marker: { x: 0.71618, y: 0.45347 }
5001
+ },
5002
+ Cloudhold: {
5003
+ marker: { x: 0.80375, y: 0.41683 },
5004
+ hero: { x: 0.77694, y: 0.42041 },
5005
+ foe: { x: 0.79124, y: 0.44274 }
5006
+ },
5007
+ Delmsmire: {
5008
+ foe: { x: 0.83994, y: 0.34892 },
5009
+ hero: { x: 0.86675, y: 0.32703 },
5010
+ marker: { x: 0.89221, y: 0.31631 }
5011
+ },
5012
+ "Hissing Groves": {
5013
+ marker: { x: 0.67151, y: 0.33865 },
5014
+ building: { x: 0.70502, y: 0.3869 },
5015
+ skull: { x: 0.71127, y: 0.38422 },
5016
+ hero: { x: 0.66436, y: 0.38645 },
5017
+ foe: { x: 0.64783, y: 0.36054 }
5018
+ },
5019
+ "Idran Forest": {
5020
+ hero: { x: 0.54105, y: 0.21221 },
5021
+ foe: { x: 0.54865, y: 0.23455 },
5022
+ marker: { x: 0.58394, y: 0.22294 }
5023
+ },
5024
+ "Lonelight Hills": {
5025
+ marker: { x: 0.58573, y: 0.30291 },
5026
+ hero: { x: 0.58662, y: 0.32524 },
5027
+ foe: { x: 0.54507, y: 0.30112 }
5028
+ },
5029
+ "Lost Lands": {
5030
+ hero: { x: 0.56339, y: 0.08935 },
5031
+ foe: { x: 0.57813, y: 0.10633 },
5032
+ marker: { x: 0.57367, y: 0.0688 }
5033
+ },
5034
+ "Plains of Plovo": {
5035
+ marker: { x: 0.92348, y: 0.42532 },
5036
+ building: { x: 0.88238, y: 0.39717 },
5037
+ skull: { x: 0.89042, y: 0.3936 },
5038
+ hero: { x: 0.89221, y: 0.43158 },
5039
+ foe: { x: 0.91053, y: 0.44632 }
5040
+ },
5041
+ "Plains of Woldra": {
5042
+ hero: { x: 0.62862, y: 0.12107 },
5043
+ foe: { x: 0.57322, y: 0.15815 },
5044
+ marker: { x: 0.60851, y: 0.16798 }
5045
+ },
5046
+ "The Empty Glade": {
5047
+ hero: { x: 0.72646, y: 0.29352 },
5048
+ foe: { x: 0.78052, y: 0.34982 },
5049
+ marker: { x: 0.74746, y: 0.32078 }
5050
+ },
5051
+ "The Grass Sea": {
5052
+ hero: { x: 0.57099, y: 0.37662 },
5053
+ foe: { x: 0.62281, y: 0.43426 },
5054
+ marker: { x: 0.60181, y: 0.40343 }
5055
+ },
5056
+ "Weeping Waters": {
5057
+ hero: { x: 0.64202, y: 0.24795 },
5058
+ foe: { x: 0.65453, y: 0.23053 },
5059
+ marker: { x: 0.67464, y: 0.24393 }
5060
+ },
5061
+ Yellowpike: {
5062
+ marker: { x: 0.76533, y: 0.22338 },
5063
+ hero: { x: 0.79348, y: 0.19792 },
5064
+ foe: { x: 0.81492, y: 0.17379 }
5065
+ }
5066
+ };
5067
+
5068
+ // src/udtBoardAdjacency.ts
5069
+ var BOARD_ADJACENCY = {
5070
+ "Howling Desert": [
5071
+ "Azkol's Bane",
5072
+ "Idran Forest",
5073
+ "Irontops",
5074
+ "Lonelight Hills",
5075
+ "Mountains of the Watchers",
5076
+ "The Cloister",
5077
+ "The Emerald Expanse"
5078
+ ],
5079
+ "Mountains of the Watchers": [
5080
+ "Howling Desert",
5081
+ "Idran Forest",
5082
+ "Lost Lands",
5083
+ "Plains of Woldra",
5084
+ "The Emerald Expanse"
5085
+ ],
5086
+ "The Emerald Expanse": [
5087
+ "Azkol's Bane",
5088
+ "Bone Hills",
5089
+ "Howling Desert",
5090
+ "Lost Lands",
5091
+ "Mountains of the Watchers"
5092
+ ],
5093
+ "Azkol's Bane": [
5094
+ "Archmont",
5095
+ "Bone Hills",
5096
+ "Howling Desert",
5097
+ "The Cloister",
5098
+ "The Emerald Expanse"
5099
+ ],
5100
+ "The Cloister": [
5101
+ "Archmont",
5102
+ "Azkol's Bane",
5103
+ "Howling Desert",
5104
+ "Irontops",
5105
+ "Sands of Madness",
5106
+ "The Throne"
5107
+ ],
5108
+ Irontops: ["Howling Desert", "Lonelight Hills", "The Cloister", "The Grass Sea", "The Throne"],
5109
+ "Idran Forest": [
5110
+ "Anza",
5111
+ "Howling Desert",
5112
+ "Lonelight Hills",
5113
+ "Mountains of the Watchers",
5114
+ "Plains of Woldra",
5115
+ "Weeping Waters"
5116
+ ],
5117
+ "Lonelight Hills": [
5118
+ "Hissing Groves",
5119
+ "Howling Desert",
5120
+ "Idran Forest",
5121
+ "Irontops",
5122
+ "The Grass Sea",
5123
+ "Weeping Waters"
5124
+ ],
5125
+ "The Grass Sea": [
5126
+ "Ash Hills",
5127
+ "Hissing Groves",
5128
+ "Irontops",
5129
+ "Lonelight Hills",
5130
+ "Tower Scar Desert"
5131
+ ],
5132
+ "The Throne": [
5133
+ "Big Sister",
5134
+ "Forest of Shades",
5135
+ "Irontops",
5136
+ "Sands of Madness",
5137
+ "The Cloister"
5138
+ ],
5139
+ "Sands of Madness": [
5140
+ "Archmont",
5141
+ "Big Sister",
5142
+ "Little Sister",
5143
+ "Middle Sister",
5144
+ "The Cloister",
5145
+ "The Throne"
5146
+ ],
5147
+ Archmont: [
5148
+ "Azkol's Bane",
5149
+ "Bone Hills",
5150
+ "Little Sister",
5151
+ "Sands of Madness",
5152
+ "Southern Wastes",
5153
+ "The Cloister"
5154
+ ],
5155
+ "Bone Hills": ["Archmont", "Azkol's Bane", "Southern Wastes", "The Emerald Expanse"],
5156
+ "Little Sister": [
5157
+ "Archmont",
5158
+ "Middle Sister",
5159
+ "Sands of Madness",
5160
+ "Southern Wastes",
5161
+ "Ulamel's Hollow"
5162
+ ],
5163
+ "Forest of Shades": [
5164
+ "Big Sister",
5165
+ "Bleak Wastes",
5166
+ "Dragontooth Lake",
5167
+ "Greater Tombstones",
5168
+ "The Throne"
5169
+ ],
5170
+ "Big Sister": [
5171
+ "Forest of Shades",
5172
+ "Greater Tombstones",
5173
+ "Lesser Tombstones",
5174
+ "Middle Sister",
5175
+ "Sands of Madness",
5176
+ "The Throne"
5177
+ ],
5178
+ "Middle Sister": [
5179
+ "Big Sister",
5180
+ "Lesser Tombstones",
5181
+ "Little Sister",
5182
+ "Pine Barrens",
5183
+ "Sands of Madness",
5184
+ "Three Rivers",
5185
+ "Ulamel's Hollow"
5186
+ ],
5187
+ "Southern Wastes": ["Archmont", "Bone Hills", "Little Sister", "Ulamel's Hollow"],
5188
+ "Ulamel's Hollow": ["Little Sister", "Middle Sister", "Pine Barrens", "Southern Wastes"],
5189
+ "Pine Barrens": ["Middle Sister", "Three Rivers", "Ulamel's Hollow"],
5190
+ "Plains of Woldra": ["Anza", "Idran Forest", "Lost Lands", "Mountains of the Watchers"],
5191
+ "Lost Lands": ["Mountains of the Watchers", "Plains of Woldra", "The Emerald Expanse"],
5192
+ "Weeping Waters": [
5193
+ "Anza",
5194
+ "Hissing Groves",
5195
+ "Idran Forest",
5196
+ "Lonelight Hills",
5197
+ "The Empty Glade",
5198
+ "Yellowpike"
5199
+ ],
5200
+ Anza: ["Idran Forest", "Plains of Woldra", "Weeping Waters", "Yellowpike"],
5201
+ "Hissing Groves": [
5202
+ "Ash Hills",
5203
+ "Cloudhold",
5204
+ "Lonelight Hills",
5205
+ "The Empty Glade",
5206
+ "The Grass Sea",
5207
+ "Weeping Waters"
5208
+ ],
5209
+ "Ash Hills": [
5210
+ "Broken Lands",
5211
+ "Cloudhold",
5212
+ "Hissing Groves",
5213
+ "Muted Forest",
5214
+ "The Grass Sea",
5215
+ "Tower Scar Desert"
5216
+ ],
5217
+ "The Empty Glade": [
5218
+ "Arkartus",
5219
+ "Cloudhold",
5220
+ "Delmsmire",
5221
+ "Hissing Groves",
5222
+ "Weeping Waters",
5223
+ "Yellowpike"
5224
+ ],
5225
+ Yellowpike: ["Anza", "Arkartus", "The Empty Glade", "Weeping Waters"],
5226
+ Arkartus: ["Delmsmire", "The Empty Glade", "Yellowpike"],
5227
+ Delmsmire: ["Arkartus", "Cloudhold", "Plains of Plovo", "The Empty Glade"],
5228
+ Cloudhold: [
5229
+ "Ash Hills",
5230
+ "Broken Lands",
5231
+ "Delmsmire",
5232
+ "Hissing Groves",
5233
+ "Plains of Plovo",
5234
+ "The Empty Glade"
5235
+ ],
5236
+ "Plains of Plovo": ["Broken Lands", "Cloudhold", "Delmsmire", "Lodestone Mountains"],
5237
+ "Lodestone Mountains": [
5238
+ "Broken Lands",
5239
+ "Pearl of the North",
5240
+ "Plains of Plovo",
5241
+ "Radiant Mountains"
5242
+ ],
5243
+ "Broken Lands": [
5244
+ "Ash Hills",
5245
+ "Cloudhold",
5246
+ "Lodestone Mountains",
5247
+ "Muted Forest",
5248
+ "Plains of Plovo",
5249
+ "Radiant Mountains"
5250
+ ],
5251
+ "Muted Forest": [
5252
+ "Ash Hills",
5253
+ "Broken Lands",
5254
+ "Green Bridge",
5255
+ "Radiant Mountains",
5256
+ "Tower Scar Desert",
5257
+ "Upper Ice Fangs"
5258
+ ],
5259
+ "Tower Scar Desert": [
5260
+ "Ash Hills",
5261
+ "Bleak Wastes",
5262
+ "Muted Forest",
5263
+ "The Grass Sea",
5264
+ "Upper Ice Fangs"
5265
+ ],
5266
+ "Radiant Mountains": [
5267
+ "Broken Lands",
5268
+ "Fivepint",
5269
+ "Green Bridge",
5270
+ "Lodestone Mountains",
5271
+ "Muted Forest",
5272
+ "Pearl of the North"
5273
+ ],
5274
+ "Pearl of the North": ["Fivepint", "Lodestone Mountains", "Radiant Mountains"],
5275
+ "Green Bridge": [
5276
+ "Dayside",
5277
+ "Fivepint",
5278
+ "Lower Ice Fangs",
5279
+ "Muted Forest",
5280
+ "Radiant Mountains",
5281
+ "Upper Ice Fangs"
5282
+ ],
5283
+ Fivepint: ["Dayside", "Green Bridge", "Pearl of the North", "Radiant Mountains"],
5284
+ Dayside: ["Fivepint", "Green Bridge", "Lower Ice Fangs", "The Tundra"],
5285
+ "Upper Ice Fangs": [
5286
+ "Bleak Wastes",
5287
+ "Green Bridge",
5288
+ "Lower Ice Fangs",
5289
+ "Muted Forest",
5290
+ "Peaks of the Djinn",
5291
+ "Tower Scar Desert"
5292
+ ],
5293
+ "Bleak Wastes": [
5294
+ "Dragontooth Lake",
5295
+ "Forest of Shades",
5296
+ "Peaks of the Djinn",
5297
+ "Tower Scar Desert",
5298
+ "Upper Ice Fangs"
5299
+ ],
5300
+ "Lower Ice Fangs": [
5301
+ "Dayside",
5302
+ "Green Bridge",
5303
+ "Peaks of the Djinn",
5304
+ "Rimeweald",
5305
+ "The Tundra",
5306
+ "Upper Ice Fangs"
5307
+ ],
5308
+ "Peaks of the Djinn": [
5309
+ "Bleak Wastes",
5310
+ "Dragontooth Lake",
5311
+ "Inner Kinghills",
5312
+ "Lower Ice Fangs",
5313
+ "Rimeweald",
5314
+ "Upper Ice Fangs"
5315
+ ],
5316
+ Rimeweald: [
5317
+ "Egan's End",
5318
+ "Inner Kinghills",
5319
+ "Lower Ice Fangs",
5320
+ "Outer Kinghills",
5321
+ "Peaks of the Djinn",
5322
+ "The Tundra"
5323
+ ],
5324
+ "Egan's End": ["Outer Kinghills", "Rimeweald", "The Tundra"],
5325
+ "The Tundra": ["Dayside", "Egan's End", "Lower Ice Fangs", "Rimeweald"],
5326
+ "Outer Kinghills": ["Copper Grove", "Egan's End", "Inner Kinghills", "Rimeweald"],
5327
+ "Inner Kinghills": [
5328
+ "Copper Grove",
5329
+ "Dragontooth Lake",
5330
+ "Outer Kinghills",
5331
+ "Peaks of the Djinn",
5332
+ "Rimeweald",
5333
+ "The Decaying Wilds"
5334
+ ],
5335
+ "Copper Grove": ["Inner Kinghills", "Jewel Hills", "Outer Kinghills", "The Decaying Wilds"],
5336
+ "The Decaying Wilds": [
5337
+ "Copper Grove",
5338
+ "Dragontooth Lake",
5339
+ "Greater Tombstones",
5340
+ "Inner Kinghills",
5341
+ "Jewel Hills",
5342
+ "Utar's Barrows"
5343
+ ],
5344
+ "Dragontooth Lake": [
5345
+ "Bleak Wastes",
5346
+ "Forest of Shades",
5347
+ "Greater Tombstones",
5348
+ "Inner Kinghills",
5349
+ "Peaks of the Djinn",
5350
+ "The Decaying Wilds"
5351
+ ],
5352
+ "Greater Tombstones": [
5353
+ "Big Sister",
5354
+ "Dragontooth Lake",
5355
+ "Forest of Shades",
5356
+ "Lesser Tombstones",
5357
+ "The Decaying Wilds",
5358
+ "Utar's Barrows"
5359
+ ],
5360
+ "Jewel Hills": ["Copper Grove", "Duwani", "The Decaying Wilds", "Utar's Barrows"],
5361
+ "Utar's Barrows": [
5362
+ "Duwani",
5363
+ "Greater Tombstones",
5364
+ "Jewel Hills",
5365
+ "Lake of Songs",
5366
+ "Lesser Tombstones",
5367
+ "The Decaying Wilds"
5368
+ ],
5369
+ Duwani: ["Jewel Hills", "Lake of Songs", "Utar's Barrows"],
5370
+ "Lesser Tombstones": [
5371
+ "Big Sister",
5372
+ "Greater Tombstones",
5373
+ "Lake of Songs",
5374
+ "Middle Sister",
5375
+ "Three Rivers",
5376
+ "Utar's Barrows"
5377
+ ],
5378
+ "Lake of Songs": ["Duwani", "Lesser Tombstones", "Three Rivers", "Utar's Barrows"],
5379
+ "Three Rivers": ["Lake of Songs", "Lesser Tombstones", "Middle Sister", "Pine Barrens"]
5380
+ };
5381
+ function neighborsOf(loc) {
5382
+ return BOARD_ADJACENCY[loc] ?? [];
5383
+ }
5384
+ function stepDistance(a, b) {
5385
+ if (a === b) return 0;
5386
+ const visited = /* @__PURE__ */ new Set([a]);
5387
+ let frontier = [a];
5388
+ let dist = 0;
5389
+ while (frontier.length > 0) {
5390
+ dist++;
5391
+ const next = [];
5392
+ for (const node of frontier) {
5393
+ for (const n of neighborsOf(node)) {
5394
+ if (n === b) return dist;
5395
+ if (!visited.has(n)) {
5396
+ visited.add(n);
5397
+ next.push(n);
5398
+ }
5399
+ }
5400
+ }
5401
+ frontier = next;
5402
+ }
5403
+ return Infinity;
5404
+ }
5405
+ function shortestPath(a, b) {
5406
+ if (a === b) return [a];
5407
+ const prev = /* @__PURE__ */ new Map();
5408
+ const visited = /* @__PURE__ */ new Set([a]);
5409
+ let frontier = [a];
5410
+ while (frontier.length > 0) {
5411
+ const next = [];
5412
+ for (const node of frontier) {
5413
+ for (const n of neighborsOf(node)) {
5414
+ if (visited.has(n)) continue;
5415
+ visited.add(n);
5416
+ prev.set(n, node);
5417
+ if (n === b) {
5418
+ const path = [b];
5419
+ let cur = b;
5420
+ while (cur !== void 0 && cur !== a) {
5421
+ cur = prev.get(cur);
5422
+ if (cur !== void 0) path.push(cur);
5423
+ }
5424
+ return path.reverse();
5425
+ }
5426
+ next.push(n);
5427
+ }
5428
+ }
5429
+ frontier = next;
5430
+ }
5431
+ return [];
5432
+ }
5433
+
4532
5434
  // src/index.ts
4533
5435
  var index_default = UltimateDarkTower_default;
4534
5436
  export {
4535
5437
  ADVERSARIES,
5438
+ ADVERSARY_ROSTER,
4536
5439
  ALLIES,
5440
+ ALL_FOES,
4537
5441
  AUDIO_COMMAND_POS,
4538
5442
  BATTERY_STATUS_FREQUENCY,
5443
+ BOARD_ADJACENCY,
5444
+ BOARD_ANCHORS,
4539
5445
  BOARD_GROUPINGS,
5446
+ BOARD_IMAGE_INFO,
4540
5447
  BOARD_LOCATIONS,
4541
5448
  BOARD_LOCATION_BY_NAME,
4542
5449
  BluetoothAdapterFactory,
@@ -4565,8 +5472,14 @@ export {
4565
5472
  DIS_SYSTEM_ID_UUID,
4566
5473
  DOMOutput,
4567
5474
  DRUM_PACKETS,
5475
+ FOES,
5476
+ FOE_BY_ID,
5477
+ FOE_BY_NAME,
5478
+ FOE_STATUSES,
4568
5479
  GAME_SOURCES,
4569
5480
  GLYPHS,
5481
+ HEROES,
5482
+ HERO_BY_ID,
4570
5483
  InMemorySink,
4571
5484
  IndexedDBSink,
4572
5485
  LAYER_TO_POSITION,
@@ -4575,6 +5488,8 @@ export {
4575
5488
  LIGHT_EFFECTS,
4576
5489
  LIGHT_INDEX_TO_DIRECTION,
4577
5490
  Logger,
5491
+ MONUMENTS,
5492
+ MONUMENT_BY_ID,
4578
5493
  RING_LIGHT_POSITIONS,
4579
5494
  SKULL_DROP_COUNT_POS,
4580
5495
  STATE_DATA_LENGTH,
@@ -4619,9 +5534,12 @@ export {
4619
5534
  logger,
4620
5535
  milliVoltsToPercentage,
4621
5536
  milliVoltsToPercentageNumber,
5537
+ neighborsOf,
4622
5538
  parseDifferentialReadings,
4623
5539
  rtdt_pack_state,
4624
5540
  rtdt_unpack_state,
5541
+ shortestPath,
5542
+ stepDistance,
4625
5543
  validateSeed,
4626
5544
  valueToChar
4627
5545
  };