@wodzik/cubecore 0.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 (279) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +118 -0
  3. package/analyze/client.d.ts +18 -0
  4. package/analyze/client.js +42 -0
  5. package/analyze/index.d.ts +114 -0
  6. package/analyze/index.js +151 -0
  7. package/analyze/rotations.d.ts +3 -0
  8. package/analyze/rotations.js +15 -0
  9. package/analyze/roux.d.ts +67 -0
  10. package/analyze/roux.js +95 -0
  11. package/analyze/worker.d.ts +6 -0
  12. package/analyze/worker.js +31 -0
  13. package/analyze/zz.d.ts +47 -0
  14. package/analyze/zz.js +102 -0
  15. package/bld/index.d.ts +14 -0
  16. package/bld/index.js +14 -0
  17. package/bld/memo.d.ts +66 -0
  18. package/bld/memo.js +139 -0
  19. package/bld/scheme.d.ts +30 -0
  20. package/bld/scheme.js +35 -0
  21. package/bld/skin.d.ts +26 -0
  22. package/bld/skin.js +46 -0
  23. package/bld/tracker.d.ts +68 -0
  24. package/bld/tracker.js +145 -0
  25. package/bluetooth/clock.d.ts +22 -0
  26. package/bluetooth/clock.js +37 -0
  27. package/bluetooth/cubeSkins.d.ts +31 -0
  28. package/bluetooth/cubeSkins.js +32 -0
  29. package/bluetooth/gyro.d.ts +31 -0
  30. package/bluetooth/gyro.js +46 -0
  31. package/bluetooth/index.d.ts +5 -0
  32. package/bluetooth/index.js +5 -0
  33. package/bluetooth/session.d.ts +140 -0
  34. package/bluetooth/session.js +201 -0
  35. package/bluetooth/simulated.d.ts +52 -0
  36. package/bluetooth/simulated.js +80 -0
  37. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-definitions.d.ts +38 -0
  38. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-definitions.js +51 -0
  39. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-encrypter.d.ts +35 -0
  40. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-encrypter.js +70 -0
  41. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-protocol.d.ts +273 -0
  42. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-protocol.js +846 -0
  43. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen1.d.ts +38 -0
  44. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen1.js +340 -0
  45. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen234-packet-validate.d.ts +11 -0
  46. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen234-packet-validate.js +118 -0
  47. package/bluetooth/vendor/smartcube-web-bluetooth/gan-mac-salt.d.ts +2 -0
  48. package/bluetooth/vendor/smartcube-web-bluetooth/gan-mac-salt.js +15 -0
  49. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-cube.d.ts +18 -0
  50. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-cube.js +152 -0
  51. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-timer.d.ts +74 -0
  52. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-timer.js +161 -0
  53. package/bluetooth/vendor/smartcube-web-bluetooth/gan-write-queue.d.ts +7 -0
  54. package/bluetooth/vendor/smartcube-web-bluetooth/gan-write-queue.js +11 -0
  55. package/bluetooth/vendor/smartcube-web-bluetooth/gatt-characteristic-write.d.ts +2 -0
  56. package/bluetooth/vendor/smartcube-web-bluetooth/gatt-characteristic-write.js +36 -0
  57. package/bluetooth/vendor/smartcube-web-bluetooth/index.d.ts +4 -0
  58. package/bluetooth/vendor/smartcube-web-bluetooth/index.js +4 -0
  59. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/address-hints.d.ts +12 -0
  60. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/address-hints.js +122 -0
  61. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/build-picker-options.d.ts +11 -0
  62. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/build-picker-options.js +55 -0
  63. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/default-optional-services.d.ts +21 -0
  64. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/default-optional-services.js +47 -0
  65. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/device-name-scan-filters.d.ts +6 -0
  66. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/device-name-scan-filters.js +24 -0
  67. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/gatt-snapshot.d.ts +6 -0
  68. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/gatt-snapshot.js +57 -0
  69. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-candidates.d.ts +11 -0
  70. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-candidates.js +67 -0
  71. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-moyu32.d.ts +8 -0
  72. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-moyu32.js +98 -0
  73. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-qiyi.d.ts +8 -0
  74. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-qiyi.js +128 -0
  75. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/moyu32-session-crypto.d.ts +5 -0
  76. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/moyu32-session-crypto.js +60 -0
  77. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/normalize-uuid.d.ts +2 -0
  78. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/normalize-uuid.js +8 -0
  79. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/packet-sanity.d.ts +7 -0
  80. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/packet-sanity.js +142 -0
  81. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/profile-rank.d.ts +6 -0
  82. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/profile-rank.js +21 -0
  83. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/types.d.ts +32 -0
  84. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/types.js +1 -0
  85. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/ble-utils.d.ts +6 -0
  86. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/ble-utils.js +68 -0
  87. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/connect.d.ts +3 -0
  88. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/connect.js +178 -0
  89. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/cubie-cube.d.ts +16 -0
  90. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/cubie-cube.js +141 -0
  91. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/index.d.ts +11 -0
  92. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/index.js +10 -0
  93. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocol.d.ts +26 -0
  94. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocol.js +8 -0
  95. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gan.d.ts +3 -0
  96. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gan.js +265 -0
  97. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/giiker.d.ts +3 -0
  98. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/giiker.js +345 -0
  99. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gocube.d.ts +15 -0
  100. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gocube.js +382 -0
  101. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-mhc.d.ts +3 -0
  102. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-mhc.js +352 -0
  103. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-v1.d.ts +57 -0
  104. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-v1.js +243 -0
  105. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu32.d.ts +3 -0
  106. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu32.js +454 -0
  107. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/qiyi.d.ts +3 -0
  108. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/qiyi.js +424 -0
  109. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/types.d.ts +78 -0
  110. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/types.js +1 -0
  111. package/bluetooth/vendor/smartcube-web-bluetooth/utils.d.ts +45 -0
  112. package/bluetooth/vendor/smartcube-web-bluetooth/utils.js +150 -0
  113. package/cfop/caseData.d.ts +16 -0
  114. package/cfop/caseData.js +88 -0
  115. package/cfop/cases.d.ts +42 -0
  116. package/cfop/cases.js +116 -0
  117. package/cfop/f2l.d.ts +28 -0
  118. package/cfop/f2l.js +88 -0
  119. package/cfop/f2lData.d.ts +15 -0
  120. package/cfop/f2lData.js +49 -0
  121. package/cfop/index.d.ts +41 -0
  122. package/cfop/index.js +110 -0
  123. package/cfop/slots.d.ts +2 -0
  124. package/cfop/slots.js +1 -0
  125. package/core/arrows.d.ts +24 -0
  126. package/core/arrows.js +22 -0
  127. package/core/bytes.d.ts +27 -0
  128. package/core/bytes.js +145 -0
  129. package/core/centers.d.ts +32 -0
  130. package/core/centers.js +83 -0
  131. package/core/checks.d.ts +53 -0
  132. package/core/checks.js +85 -0
  133. package/core/cubies.d.ts +45 -0
  134. package/core/cubies.js +169 -0
  135. package/core/frames.d.ts +61 -0
  136. package/core/frames.js +126 -0
  137. package/core/geometry.d.ts +58 -0
  138. package/core/geometry.js +132 -0
  139. package/core/index.d.ts +19 -0
  140. package/core/index.js +19 -0
  141. package/core/masks.d.ts +32 -0
  142. package/core/masks.js +39 -0
  143. package/core/method.d.ts +35 -0
  144. package/core/method.js +30 -0
  145. package/core/moves.d.ts +48 -0
  146. package/core/moves.js +88 -0
  147. package/core/notation.d.ts +48 -0
  148. package/core/notation.js +226 -0
  149. package/core/physical.d.ts +53 -0
  150. package/core/physical.js +99 -0
  151. package/core/practice.d.ts +62 -0
  152. package/core/practice.js +87 -0
  153. package/core/sequence.d.ts +90 -0
  154. package/core/sequence.js +157 -0
  155. package/core/stages.d.ts +90 -0
  156. package/core/stages.js +21 -0
  157. package/core/state.d.ts +55 -0
  158. package/core/state.js +140 -0
  159. package/core/stateCodec.d.ts +12 -0
  160. package/core/stateCodec.js +51 -0
  161. package/core/stream.d.ts +45 -0
  162. package/core/stream.js +75 -0
  163. package/core/tracker.d.ts +62 -0
  164. package/core/tracker.js +121 -0
  165. package/element/alg.d.ts +45 -0
  166. package/element/alg.js +150 -0
  167. package/element/base.d.ts +2 -0
  168. package/element/base.js +3 -0
  169. package/element/bld.d.ts +82 -0
  170. package/element/bld.js +222 -0
  171. package/element/index.d.ts +8 -0
  172. package/element/index.js +21 -0
  173. package/element/model.d.ts +36 -0
  174. package/element/model.js +64 -0
  175. package/element/player.d.ts +196 -0
  176. package/element/player.js +680 -0
  177. package/element/practice.d.ts +75 -0
  178. package/element/practice.js +150 -0
  179. package/element/scramble.d.ts +50 -0
  180. package/element/scramble.js +107 -0
  181. package/element/sequence.d.ts +126 -0
  182. package/element/sequence.js +254 -0
  183. package/element/styles.d.ts +32 -0
  184. package/element/styles.js +196 -0
  185. package/image/cache.d.ts +21 -0
  186. package/image/cache.js +66 -0
  187. package/image/index.d.ts +2 -0
  188. package/image/index.js +2 -0
  189. package/image/png-node.d.ts +6 -0
  190. package/image/png-node.js +10 -0
  191. package/image/svg.d.ts +36 -0
  192. package/image/svg.js +215 -0
  193. package/lbl/index.d.ts +10 -0
  194. package/lbl/index.js +31 -0
  195. package/methods/index.d.ts +20 -0
  196. package/methods/index.js +33 -0
  197. package/package.json +133 -0
  198. package/petrus/index.d.ts +11 -0
  199. package/petrus/index.js +27 -0
  200. package/react/index.d.ts +176 -0
  201. package/react/index.js +228 -0
  202. package/render/arrows.d.ts +68 -0
  203. package/render/arrows.js +190 -0
  204. package/render/build/attachments.d.ts +35 -0
  205. package/render/build/attachments.js +94 -0
  206. package/render/build/models.d.ts +9 -0
  207. package/render/build/models.js +27 -0
  208. package/render/build/tiles.d.ts +48 -0
  209. package/render/build/tiles.js +194 -0
  210. package/render/gltf.d.ts +10 -0
  211. package/render/gltf.js +106 -0
  212. package/render/index.d.ts +12 -0
  213. package/render/index.js +12 -0
  214. package/render/layers.d.ts +21 -0
  215. package/render/layers.js +24 -0
  216. package/render/pictures.d.ts +46 -0
  217. package/render/pictures.js +80 -0
  218. package/render/pieceModels.d.ts +38 -0
  219. package/render/pieceModels.js +43 -0
  220. package/render/renderer.d.ts +165 -0
  221. package/render/renderer.js +647 -0
  222. package/render/tile.d.ts +114 -0
  223. package/render/tile.js +390 -0
  224. package/render/viewports.d.ts +25 -0
  225. package/render/viewports.js +26 -0
  226. package/roux/cmll.d.ts +35 -0
  227. package/roux/cmll.js +121 -0
  228. package/roux/cmllData.d.ts +12 -0
  229. package/roux/cmllData.js +49 -0
  230. package/roux/index.d.ts +52 -0
  231. package/roux/index.js +175 -0
  232. package/skin/attachments.d.ts +82 -0
  233. package/skin/attachments.js +110 -0
  234. package/skin/index.d.ts +3 -0
  235. package/skin/index.js +3 -0
  236. package/skin/shapes.d.ts +112 -0
  237. package/skin/shapes.js +147 -0
  238. package/skin/skin.d.ts +911 -0
  239. package/skin/skin.js +412 -0
  240. package/solve/client.d.ts +58 -0
  241. package/solve/client.js +56 -0
  242. package/solve/handle.d.ts +6 -0
  243. package/solve/handle.js +38 -0
  244. package/solve/index.d.ts +8 -0
  245. package/solve/index.js +7 -0
  246. package/solve/lse.d.ts +42 -0
  247. package/solve/lse.js +151 -0
  248. package/solve/pieces.d.ts +18 -0
  249. package/solve/pieces.js +24 -0
  250. package/solve/protocol.d.ts +70 -0
  251. package/solve/protocol.js +16 -0
  252. package/solve/scramble.d.ts +76 -0
  253. package/solve/scramble.js +187 -0
  254. package/solve/stage.d.ts +108 -0
  255. package/solve/stage.js +532 -0
  256. package/solve/twophase.d.ts +36 -0
  257. package/solve/twophase.js +229 -0
  258. package/solve/worker.d.ts +6 -0
  259. package/solve/worker.js +15 -0
  260. package/timeline/codec.d.ts +23 -0
  261. package/timeline/codec.js +84 -0
  262. package/timeline/index.d.ts +7 -0
  263. package/timeline/index.js +7 -0
  264. package/timeline/recording.d.ts +19 -0
  265. package/timeline/recording.js +20 -0
  266. package/timeline/replay.d.ts +68 -0
  267. package/timeline/replay.js +145 -0
  268. package/timeline/segments.d.ts +31 -0
  269. package/timeline/segments.js +39 -0
  270. package/timeline/share.d.ts +30 -0
  271. package/timeline/share.js +62 -0
  272. package/timeline/stats.d.ts +34 -0
  273. package/timeline/stats.js +56 -0
  274. package/timeline/timings.d.ts +30 -0
  275. package/timeline/timings.js +54 -0
  276. package/zz/index.d.ts +29 -0
  277. package/zz/index.js +68 -0
  278. package/zz/seeds.d.ts +9 -0
  279. package/zz/seeds.js +29 -0
@@ -0,0 +1,424 @@
1
+ import { Subject } from 'rxjs';
2
+ import { ModeOfOperation } from 'aes-js';
3
+ import { normalizeUuid } from '../attachment/normalize-uuid.js';
4
+ import { getCachedMacForDevice } from '../attachment/address-hints.js';
5
+ import { buildQiYiMacCandidatesFromName } from '../attachment/mac-candidates.js';
6
+ import { probeQiYiMac } from '../attachment/mac-probe-qiyi.js';
7
+ import { registerProtocol } from '../protocol.js';
8
+ import { CubieCube } from '../cubie-cube.js';
9
+ import { now, findCharacteristic, waitForAdvertisements, extractMacFromManufacturerData } from '../ble-utils.js';
10
+ import { writeGattCharacteristicValue } from '../../gatt-characteristic-write.js';
11
+ const UUID_SUFFIX = '-0000-1000-8000-00805f9b34fb';
12
+ const SERVICE_UUID = '0000fff0' + UUID_SUFFIX;
13
+ const CHRCT_UUID_CUBE = '0000fff6' + UUID_SUFFIX;
14
+ const QIYI_CIC_LIST = [0x0504];
15
+ const QIYI_KEY = [87, 177, 249, 171, 205, 90, 232, 167, 156, 185, 140, 231, 87, 140, 81, 8];
16
+ /** Kociemba facelet string for solved cube */
17
+ const QIYI_SOLVED_FACELETS = 'UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB';
18
+ function crc16modbus(data) {
19
+ let crc = 0xFFFF;
20
+ for (let i = 0; i < data.length; i++) {
21
+ crc ^= data[i];
22
+ for (let j = 0; j < 8; j++) {
23
+ crc = (crc & 0x1) > 0 ? (crc >> 1) ^ 0xa001 : crc >> 1;
24
+ }
25
+ }
26
+ return crc;
27
+ }
28
+ class QiYiEncrypter {
29
+ cipher;
30
+ constructor() {
31
+ this.cipher = new ModeOfOperation.ecb(new Uint8Array(QIYI_KEY));
32
+ }
33
+ encrypt(data) {
34
+ const result = [];
35
+ for (let i = 0; i < data.length; i += 16) {
36
+ const block = data.slice(i, i + 16);
37
+ const encrypted = this.cipher.encrypt(new Uint8Array(block));
38
+ for (let j = 0; j < 16; j++) {
39
+ result[i + j] = encrypted[j];
40
+ }
41
+ }
42
+ return result;
43
+ }
44
+ decrypt(data) {
45
+ const result = [];
46
+ for (let i = 0; i < data.length; i += 16) {
47
+ const block = data.slice(i, i + 16);
48
+ const decrypted = this.cipher.decrypt(new Uint8Array(block));
49
+ for (let j = 0; j < 16; j++) {
50
+ result[i + j] = decrypted[j];
51
+ }
52
+ }
53
+ return result;
54
+ }
55
+ }
56
+ function parseFacelet(faceMsg) {
57
+ const ret = [];
58
+ for (let i = 0; i < 54; i++) {
59
+ ret.push("LRDUFB".charAt((faceMsg[i >> 1] >> ((i % 2) << 2)) & 0xf));
60
+ }
61
+ return ret.join("");
62
+ }
63
+ /** Device timestamps in bytes 3–6 and history slots: big-endian uint32 */
64
+ function readQiYiTimestampBE(msg, offset) {
65
+ return (msg[offset] << 24) | (msg[offset + 1] << 16) | (msg[offset + 2] << 8) | msg[offset + 3];
66
+ }
67
+ /** Collect primary + all 11 history slots (bytes 36..90) */
68
+ function collectQiYiStateChangeMoves(msg, headerTs) {
69
+ const out = [[msg[34], headerTs]];
70
+ for (let i = 0; i < 11; i++) {
71
+ const off = 36 + 5 * i;
72
+ if (off + 5 > msg.length)
73
+ break;
74
+ let allFf = true;
75
+ for (let j = 0; j < 5; j++) {
76
+ if (msg[off + j] !== 0xff) {
77
+ allFf = false;
78
+ break;
79
+ }
80
+ }
81
+ if (allFf)
82
+ continue;
83
+ const slotTs = readQiYiTimestampBE(msg, off);
84
+ const code = msg[off + 4];
85
+ out.push([code, slotTs]);
86
+ }
87
+ out.sort((a, b) => a[1] - b[1]);
88
+ const seen = new Set();
89
+ return out.filter(([code, t]) => {
90
+ const k = `${code},${t}`;
91
+ if (seen.has(k))
92
+ return false;
93
+ seen.add(k);
94
+ return true;
95
+ });
96
+ }
97
+ const QIYI_PROTOCOL = { id: 'qiyi', name: 'QiYi' };
98
+ class QiYiConnection {
99
+ deviceName;
100
+ deviceMAC;
101
+ protocol = QIYI_PROTOCOL;
102
+ capabilities = {
103
+ gyroscope: false,
104
+ battery: true,
105
+ facelets: true,
106
+ hardware: true,
107
+ reset: false
108
+ };
109
+ events$;
110
+ device;
111
+ cubeChrct = null;
112
+ encrypter;
113
+ curCubie = new CubieCube();
114
+ prevCubie = new CubieCube();
115
+ lastTs = 0;
116
+ lastBatteryLevel = null;
117
+ forceNextBatteryEmission = false;
118
+ writeChain = Promise.resolve();
119
+ constructor(device, mac) {
120
+ this.device = device;
121
+ this.deviceName = device.name || 'QiYi';
122
+ this.deviceMAC = mac;
123
+ this.events$ = new Subject();
124
+ this.encrypter = new QiYiEncrypter();
125
+ }
126
+ sendMessage(content) {
127
+ if (!this.cubeChrct)
128
+ return Promise.reject();
129
+ const ch = this.cubeChrct;
130
+ const run = async () => {
131
+ const msg = [0xfe];
132
+ msg.push(4 + content.length);
133
+ for (let i = 0; i < content.length; i++) {
134
+ msg.push(content[i]);
135
+ }
136
+ const crc = crc16modbus(msg);
137
+ msg.push(crc & 0xff, crc >> 8);
138
+ const npad = (16 - msg.length % 16) % 16;
139
+ for (let i = 0; i < npad; i++) {
140
+ msg.push(0);
141
+ }
142
+ const encMsg = this.encrypter.encrypt(msg);
143
+ await writeGattCharacteristicValue(ch, new Uint8Array(encMsg).buffer);
144
+ };
145
+ this.writeChain = this.writeChain.then(run, run);
146
+ return this.writeChain;
147
+ }
148
+ sendHello() {
149
+ const macBytes = this.deviceMAC.split(/[:-\s]+/).map(c => parseInt(c, 16));
150
+ const content = [0x00, 0x6b, 0x01, 0x00, 0x00, 0x22, 0x06, 0x00, 0x02, 0x08, 0x00];
151
+ for (let i = 5; i >= 0; i--) {
152
+ content.push(macBytes[i] || 0);
153
+ }
154
+ return this.sendMessage(content);
155
+ }
156
+ onCubeEvent = (event) => {
157
+ const value = event.target.value;
158
+ if (!value)
159
+ return;
160
+ const encMsg = [];
161
+ for (let i = 0; i < value.byteLength; i++) {
162
+ encMsg[i] = value.getUint8(i);
163
+ }
164
+ const msg = this.encrypter.decrypt(encMsg);
165
+ if (msg[0] === 0xCC && msg[1] === 0x10) {
166
+ this.handleQuaternionPacket(msg);
167
+ return;
168
+ }
169
+ const trimmed = msg.slice(0, msg[1]);
170
+ if (trimmed.length < 3 || crc16modbus(trimmed) !== 0) {
171
+ return;
172
+ }
173
+ this.parseCubeData(trimmed);
174
+ };
175
+ handleQuaternionPacket(msg) {
176
+ if (msg.length < 16)
177
+ return;
178
+ const expectedCrc = crc16modbus(msg.slice(0, 14));
179
+ const actualCrc = msg[14] | (msg[15] << 8);
180
+ if (expectedCrc !== actualCrc)
181
+ return;
182
+ if (!this.capabilities.gyroscope) {
183
+ this.capabilities.gyroscope = true;
184
+ }
185
+ const dv = new DataView(Uint8Array.from(msg).buffer);
186
+ const ax = dv.getInt16(6, false) / 1000;
187
+ const ay = dv.getInt16(8, false) / 1000;
188
+ const az = dv.getInt16(10, false) / 1000;
189
+ const aw = dv.getInt16(12, false) / 1000;
190
+ this.events$.next({
191
+ timestamp: now(),
192
+ type: "GYRO",
193
+ quaternion: {
194
+ x: ax,
195
+ y: -az,
196
+ z: ay,
197
+ w: aw
198
+ }
199
+ });
200
+ }
201
+ emitHardwareEvent() {
202
+ this.events$.next({
203
+ timestamp: now(),
204
+ type: "HARDWARE",
205
+ hardwareName: this.deviceName,
206
+ gyroSupported: this.capabilities.gyroscope
207
+ });
208
+ }
209
+ emitBatteryLevel(rawLevel, timestamp = now()) {
210
+ if (!Number.isFinite(rawLevel)) {
211
+ return;
212
+ }
213
+ const batteryLevel = Math.min(100, Math.max(0, Math.round(rawLevel)));
214
+ const forceEmission = this.forceNextBatteryEmission;
215
+ this.forceNextBatteryEmission = false;
216
+ if (!forceEmission && this.lastBatteryLevel === batteryLevel) {
217
+ return;
218
+ }
219
+ this.lastBatteryLevel = batteryLevel;
220
+ this.events$.next({
221
+ timestamp,
222
+ type: 'BATTERY',
223
+ batteryLevel,
224
+ });
225
+ }
226
+ parseCubeData(msg) {
227
+ const timestamp = now();
228
+ if (msg[0] !== 0xfe)
229
+ return;
230
+ const opcode = msg[2];
231
+ const ts = readQiYiTimestampBE(msg, 3);
232
+ if (opcode === 0x2) {
233
+ // Hello response — always ACK
234
+ this.sendMessage(msg.slice(2, 7)).catch(() => { });
235
+ const newFacelet = parseFacelet(msg.slice(7, 34));
236
+ this.events$.next({
237
+ timestamp,
238
+ type: "FACELETS",
239
+ facelets: newFacelet
240
+ });
241
+ this.emitBatteryLevel(msg[35], timestamp);
242
+ this.prevCubie.fromFacelet(newFacelet);
243
+ this.lastTs = ts;
244
+ return;
245
+ }
246
+ if (opcode === 0x3) {
247
+ const needsAck = msg.length > 91 && msg[91] !== 0;
248
+ if (needsAck) {
249
+ this.sendMessage(msg.slice(2, 7)).catch(() => { });
250
+ }
251
+ const candidates = collectQiYiStateChangeMoves(msg, ts);
252
+ const newMoves = candidates.filter(([code, moveTs]) => code >= 1 && code <= 12 && moveTs > this.lastTs);
253
+ for (let k = 0; k < newMoves.length; k++) {
254
+ const [code, moveTs] = newMoves[k];
255
+ const axis = [4, 1, 3, 0, 2, 5][(code - 1) >> 1];
256
+ const power = [0, 2][code & 1];
257
+ const m = axis * 3 + power;
258
+ const moveStr = ("URFDLB".charAt(axis) + " 2'".charAt(power)).trim();
259
+ CubieCube.CubeMult(this.prevCubie, CubieCube.moveCube[m], this.curCubie);
260
+ const facelet = this.curCubie.toFaceCube();
261
+ this.events$.next({
262
+ timestamp,
263
+ type: "MOVE",
264
+ face: axis,
265
+ direction: power === 0 ? 0 : 1,
266
+ move: moveStr,
267
+ localTimestamp: k === newMoves.length - 1 ? timestamp : null,
268
+ cubeTimestamp: Math.trunc(moveTs / 1.6)
269
+ });
270
+ this.events$.next({
271
+ timestamp,
272
+ type: "FACELETS",
273
+ facelets: facelet
274
+ });
275
+ const tmp = this.curCubie;
276
+ this.curCubie = this.prevCubie;
277
+ this.prevCubie = tmp;
278
+ }
279
+ if (newMoves.length > 0) {
280
+ this.lastTs = newMoves[newMoves.length - 1][1];
281
+ }
282
+ this.emitBatteryLevel(msg[35], timestamp);
283
+ return;
284
+ }
285
+ if (opcode === 0x4) {
286
+ // Sync confirmation: emit solved state; no ACK for op 4 in reference protocol.
287
+ if (msg[1] !== 38)
288
+ return;
289
+ this.events$.next({
290
+ timestamp,
291
+ type: "FACELETS",
292
+ facelets: QIYI_SOLVED_FACELETS
293
+ });
294
+ this.prevCubie.fromFacelet(QIYI_SOLVED_FACELETS);
295
+ this.lastTs = ts;
296
+ return;
297
+ }
298
+ // Unknown opcode: do not advance lastTs (avoids skewing move history filters).
299
+ }
300
+ onDisconnect = () => {
301
+ this.device.removeEventListener('gattserverdisconnected', this.onDisconnect);
302
+ this.lastBatteryLevel = null;
303
+ this.forceNextBatteryEmission = false;
304
+ this.events$.next({ timestamp: now(), type: "DISCONNECT" });
305
+ this.events$.complete();
306
+ };
307
+ async init() {
308
+ this.device.addEventListener('gattserverdisconnected', this.onDisconnect);
309
+ const gatt = await this.device.gatt.connect();
310
+ const service = await gatt.getPrimaryService(SERVICE_UUID);
311
+ const chrcts = await service.getCharacteristics();
312
+ this.cubeChrct = findCharacteristic(chrcts, CHRCT_UUID_CUBE);
313
+ if (!this.cubeChrct) {
314
+ throw new Error('[QiYi] Cannot find required characteristic');
315
+ }
316
+ this.cubeChrct.addEventListener('characteristicvaluechanged', this.onCubeEvent);
317
+ await this.cubeChrct.startNotifications();
318
+ await this.sendHello();
319
+ }
320
+ async sendCommand(command) {
321
+ if (command.type === "REQUEST_FACELETS" || command.type === "REQUEST_BATTERY") {
322
+ if (command.type === "REQUEST_BATTERY") {
323
+ this.forceNextBatteryEmission = true;
324
+ }
325
+ await this.sendHello();
326
+ }
327
+ else if (command.type === "REQUEST_HARDWARE") {
328
+ this.emitHardwareEvent();
329
+ }
330
+ }
331
+ async disconnect() {
332
+ if (this.cubeChrct) {
333
+ this.cubeChrct.removeEventListener('characteristicvaluechanged', this.onCubeEvent);
334
+ await this.cubeChrct.stopNotifications().catch(() => { });
335
+ this.cubeChrct = null;
336
+ }
337
+ this.lastBatteryLevel = null;
338
+ this.forceNextBatteryEmission = false;
339
+ this.device.removeEventListener('gattserverdisconnected', this.onDisconnect);
340
+ this.events$.next({ timestamp: now(), type: "DISCONNECT" });
341
+ this.events$.complete();
342
+ if (this.device.gatt?.connected) {
343
+ this.device.gatt.disconnect();
344
+ }
345
+ }
346
+ }
347
+ function parseQiYiMacFromMf(mfData) {
348
+ if (!mfData) {
349
+ return null;
350
+ }
351
+ return extractMacFromManufacturerData(mfData, QIYI_CIC_LIST, true);
352
+ }
353
+ async function connectQiYiDevice(device, macProvider, context) {
354
+ let mac = parseQiYiMacFromMf(context?.advertisementManufacturerData ?? null);
355
+ mac = mac || getCachedMacForDevice(device);
356
+ if (!mac && macProvider) {
357
+ const r = await macProvider(device, false);
358
+ if (r) {
359
+ mac = r;
360
+ }
361
+ }
362
+ if (!mac) {
363
+ const mfData = await waitForAdvertisements(device, context?.enableAddressSearch ? 8000 : 5000);
364
+ mac = parseQiYiMacFromMf(mfData);
365
+ }
366
+ if (!mac) {
367
+ const c = buildQiYiMacCandidatesFromName(device.name);
368
+ if (c.length === 1) {
369
+ mac = c[0];
370
+ }
371
+ }
372
+ if (!mac && context?.enableAddressSearch) {
373
+ const candidates = buildQiYiMacCandidatesFromName(device.name);
374
+ const timeoutMs = 3000;
375
+ for (let i = 0; i < candidates.length; i++) {
376
+ if (context.signal?.aborted) {
377
+ break;
378
+ }
379
+ context.onStatus?.(`Testing address (${i + 1}/${candidates.length})…`);
380
+ try {
381
+ if (await probeQiYiMac(device, candidates[i], {
382
+ timeoutMs,
383
+ signal: context.signal,
384
+ })) {
385
+ mac = candidates[i];
386
+ break;
387
+ }
388
+ }
389
+ catch {
390
+ /* try next */
391
+ }
392
+ }
393
+ }
394
+ if (!mac && macProvider) {
395
+ const r = await macProvider(device, true);
396
+ if (r) {
397
+ mac = r;
398
+ }
399
+ }
400
+ if (!mac) {
401
+ throw new Error('Unable to determine QiYi cube MAC address');
402
+ }
403
+ const conn = new QiYiConnection(device, mac);
404
+ await conn.init();
405
+ return conn;
406
+ }
407
+ const qiyiProtocol = {
408
+ nameFilters: [
409
+ { namePrefix: "QY-QYSC" },
410
+ { namePrefix: "XMD-TornadoV4-i" }
411
+ ],
412
+ optionalServices: [SERVICE_UUID],
413
+ optionalManufacturerData: QIYI_CIC_LIST,
414
+ matchesDevice(device) {
415
+ const name = device.name || '';
416
+ return name.startsWith('QY-QYSC') || name.startsWith('XMD-TornadoV4-i');
417
+ },
418
+ gattAffinity(serviceUuids, _device) {
419
+ return serviceUuids.has(normalizeUuid(SERVICE_UUID)) ? 110 : 0;
420
+ },
421
+ connect: connectQiYiDevice
422
+ };
423
+ registerProtocol(qiyiProtocol);
424
+ export { qiyiProtocol };
@@ -0,0 +1,78 @@
1
+ /// <reference types="web-bluetooth" />
2
+ import { Observable } from 'rxjs';
3
+ type SmartCubeMoveEvent = {
4
+ type: "MOVE";
5
+ face: number;
6
+ direction: number;
7
+ move: string;
8
+ localTimestamp: number | null;
9
+ cubeTimestamp: number | null;
10
+ };
11
+ type SmartCubeFaceletsEvent = {
12
+ type: "FACELETS";
13
+ facelets: string;
14
+ };
15
+ type SmartCubeGyroEvent = {
16
+ type: "GYRO";
17
+ quaternion: {
18
+ x: number;
19
+ y: number;
20
+ z: number;
21
+ w: number;
22
+ };
23
+ velocity?: {
24
+ x: number;
25
+ y: number;
26
+ z: number;
27
+ };
28
+ };
29
+ type SmartCubeBatteryEvent = {
30
+ type: "BATTERY";
31
+ batteryLevel: number;
32
+ };
33
+ type SmartCubeProtocolInfo = {
34
+ id: string;
35
+ name: string;
36
+ };
37
+ type SmartCubeHardwareEvent = {
38
+ type: "HARDWARE";
39
+ hardwareName?: string;
40
+ softwareVersion?: string;
41
+ hardwareVersion?: string;
42
+ productDate?: string;
43
+ gyroSupported?: boolean;
44
+ };
45
+ type SmartCubeDisconnectEvent = {
46
+ type: "DISCONNECT";
47
+ };
48
+ type SmartCubeEventMessage = SmartCubeMoveEvent | SmartCubeFaceletsEvent | SmartCubeGyroEvent | SmartCubeBatteryEvent | SmartCubeHardwareEvent | SmartCubeDisconnectEvent;
49
+ type SmartCubeEvent = {
50
+ timestamp: number;
51
+ } & SmartCubeEventMessage;
52
+ type SmartCubeCommand = {
53
+ type: "REQUEST_FACELETS";
54
+ } | {
55
+ type: "REQUEST_BATTERY";
56
+ } | {
57
+ type: "REQUEST_HARDWARE";
58
+ } | {
59
+ type: "REQUEST_RESET";
60
+ };
61
+ interface SmartCubeCapabilities {
62
+ gyroscope: boolean;
63
+ battery: boolean;
64
+ facelets: boolean;
65
+ hardware: boolean;
66
+ reset: boolean;
67
+ }
68
+ interface SmartCubeConnection {
69
+ readonly deviceName: string;
70
+ readonly deviceMAC: string;
71
+ readonly protocol: SmartCubeProtocolInfo;
72
+ readonly capabilities: SmartCubeCapabilities;
73
+ events$: Observable<SmartCubeEvent>;
74
+ sendCommand(command: SmartCubeCommand): Promise<void>;
75
+ disconnect(): Promise<void>;
76
+ }
77
+ type MacAddressProvider = (device: BluetoothDevice, isFallbackCall?: boolean) => Promise<string | null>;
78
+ export type { SmartCubeEvent, SmartCubeEventMessage, SmartCubeMoveEvent, SmartCubeFaceletsEvent, SmartCubeGyroEvent, SmartCubeBatteryEvent, SmartCubeProtocolInfo, SmartCubeHardwareEvent, SmartCubeDisconnectEvent, SmartCubeCommand, SmartCubeCapabilities, SmartCubeConnection, MacAddressProvider };
@@ -0,0 +1,45 @@
1
+ import { GanCubeMove } from './gan-cube-protocol.js';
2
+ /**
3
+ * Return current host clock timestamp with millisecond precision
4
+ * Use monotonic clock when available
5
+ * @returns Current host clock timestamp in milliseconds
6
+ */
7
+ declare const now: () => number;
8
+ /**
9
+ * Use linear regression to fit timestamps reported by cube hardware with host device timestamps
10
+ * @param cubeMoves List representing window of cube moves to operate on
11
+ * @returns New copy of move list with fitted cubeTimestamp values
12
+ */
13
+ declare function cubeTimestampLinearFit(cubeMoves: Array<GanCubeMove>): Array<GanCubeMove>;
14
+ /**
15
+ * Calculate time skew degree in percent between cube hardware and host device
16
+ * @param cubeMoves List representing window of cube moves to operate on
17
+ * @returns Time skew value in percent
18
+ */
19
+ declare function cubeTimestampCalcSkew(cubeMoves: Array<GanCubeMove>): number;
20
+ /**
21
+ *
22
+ * Convert Corner/Edge Permutation/Orientation cube state to the Kociemba facelets representation string
23
+ *
24
+ * Example - solved state:
25
+ * cp = [0, 1, 2, 3, 4, 5, 6, 7]
26
+ * co = [0, 0, 0, 0, 0, 0, 0, 0]
27
+ * ep = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
28
+ * eo = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
29
+ * facelets = "UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB"
30
+ * Example - state after F R moves made:
31
+ * cp = [0, 5, 2, 1, 7, 4, 6, 3]
32
+ * co = [1, 2, 0, 2, 1, 1, 0, 2]
33
+ * ep = [1, 9, 2, 3, 11, 8, 6, 7, 4, 5, 10, 0]
34
+ * eo = [1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0]
35
+ * facelets = "UUFUUFLLFUUURRRRRRFFRFFDFFDRRBDDBDDBLLDLLDLLDLBBUBBUBB"
36
+ *
37
+ * @param cp Corner Permutation
38
+ * @param co Corner Orientation
39
+ * @param ep Egde Permutation
40
+ * @param eo Edge Orientation
41
+ * @returns Cube state in the Kociemba facelets representation string
42
+ *
43
+ */
44
+ declare function toKociembaFacelets(cp: Array<number>, co: Array<number>, ep: Array<number>, eo: Array<number>): string;
45
+ export { now, cubeTimestampLinearFit, cubeTimestampCalcSkew, toKociembaFacelets, };
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Return current host clock timestamp with millisecond precision
3
+ * Use monotonic clock when available
4
+ * @returns Current host clock timestamp in milliseconds
5
+ */
6
+ const now = typeof window != 'undefined' && typeof window.performance?.now == 'function' ?
7
+ () => Math.floor(window.performance.now()) :
8
+ typeof process != 'undefined' && typeof process.hrtime?.bigint == 'function' ?
9
+ () => Number(process.hrtime.bigint() / 1000000n) :
10
+ () => Date.now();
11
+ function linregress(X, Y) {
12
+ var sumX = 0;
13
+ var sumY = 0;
14
+ var sumXY = 0;
15
+ var sumXX = 0;
16
+ var sumYY = 0;
17
+ var n = 0;
18
+ for (var i = 0; i < X.length; i++) {
19
+ var x = X[i];
20
+ var y = Y[i];
21
+ if (x == null || y == null) {
22
+ continue;
23
+ }
24
+ n++;
25
+ sumX += x;
26
+ sumY += y;
27
+ sumXY += x * y;
28
+ sumXX += x * x;
29
+ sumYY += y * y;
30
+ }
31
+ var varX = n * sumXX - sumX * sumX;
32
+ var covXY = n * sumXY - sumX * sumY;
33
+ var slope = varX < 1e-3 ? 1 : covXY / varX;
34
+ var intercept = n < 1 ? 0 : sumY / n - slope * sumX / n;
35
+ return [slope, intercept];
36
+ }
37
+ /**
38
+ * Use linear regression to fit timestamps reported by cube hardware with host device timestamps
39
+ * @param cubeMoves List representing window of cube moves to operate on
40
+ * @returns New copy of move list with fitted cubeTimestamp values
41
+ */
42
+ function cubeTimestampLinearFit(cubeMoves) {
43
+ var res = [];
44
+ // Calculate and fix timestamp values for missed and recovered cube moves.
45
+ if (cubeMoves.length >= 2) {
46
+ // 1st pass - tail-to-head, align missed move cube timestamps to next move -50ms
47
+ for (let i = cubeMoves.length - 1; i > 0; i--) {
48
+ if (cubeMoves[i].cubeTimestamp != null && cubeMoves[i - 1].cubeTimestamp == null)
49
+ cubeMoves[i - 1].cubeTimestamp = cubeMoves[i].cubeTimestamp - 50;
50
+ }
51
+ // 2nd pass - head-to-tail, align missed move cube timestamp to prev move +50ms
52
+ for (let i = 0; i < cubeMoves.length - 1; i++) {
53
+ if (cubeMoves[i].cubeTimestamp != null && cubeMoves[i + 1].cubeTimestamp == null)
54
+ cubeMoves[i + 1].cubeTimestamp = cubeMoves[i].cubeTimestamp + 50;
55
+ }
56
+ }
57
+ // Apply linear regression to the cube timestamps
58
+ if (cubeMoves.length > 0) {
59
+ var [slope, intercept] = linregress(cubeMoves.map(m => m.cubeTimestamp), cubeMoves.map(m => m.localTimestamp));
60
+ var first = Math.round(slope * cubeMoves[0].cubeTimestamp + intercept);
61
+ cubeMoves.forEach(m => {
62
+ res.push({
63
+ face: m.face,
64
+ direction: m.direction,
65
+ move: m.move,
66
+ localTimestamp: m.localTimestamp,
67
+ cubeTimestamp: Math.round(slope * m.cubeTimestamp + intercept) - first
68
+ });
69
+ });
70
+ }
71
+ return res;
72
+ }
73
+ /**
74
+ * Calculate time skew degree in percent between cube hardware and host device
75
+ * @param cubeMoves List representing window of cube moves to operate on
76
+ * @returns Time skew value in percent
77
+ */
78
+ function cubeTimestampCalcSkew(cubeMoves) {
79
+ if (!cubeMoves.length)
80
+ return 0;
81
+ var [slope] = linregress(cubeMoves.map(m => m.localTimestamp), cubeMoves.map(m => m.cubeTimestamp));
82
+ return Math.round((slope - 1) * 100000) / 1000;
83
+ }
84
+ const CORNER_FACELET_MAP = [
85
+ [8, 9, 20], // URF
86
+ [6, 18, 38], // UFL
87
+ [0, 36, 47], // ULB
88
+ [2, 45, 11], // UBR
89
+ [29, 26, 15], // DFR
90
+ [27, 44, 24], // DLF
91
+ [33, 53, 42], // DBL
92
+ [35, 17, 51] // DRB
93
+ ];
94
+ const EDGE_FACELET_MAP = [
95
+ [5, 10], // UR
96
+ [7, 19], // UF
97
+ [3, 37], // UL
98
+ [1, 46], // UB
99
+ [32, 16], // DR
100
+ [28, 25], // DF
101
+ [30, 43], // DL
102
+ [34, 52], // DB
103
+ [23, 12], // FR
104
+ [21, 41], // FL
105
+ [50, 39], // BL
106
+ [48, 14] // BR
107
+ ];
108
+ /**
109
+ *
110
+ * Convert Corner/Edge Permutation/Orientation cube state to the Kociemba facelets representation string
111
+ *
112
+ * Example - solved state:
113
+ * cp = [0, 1, 2, 3, 4, 5, 6, 7]
114
+ * co = [0, 0, 0, 0, 0, 0, 0, 0]
115
+ * ep = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
116
+ * eo = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
117
+ * facelets = "UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB"
118
+ * Example - state after F R moves made:
119
+ * cp = [0, 5, 2, 1, 7, 4, 6, 3]
120
+ * co = [1, 2, 0, 2, 1, 1, 0, 2]
121
+ * ep = [1, 9, 2, 3, 11, 8, 6, 7, 4, 5, 10, 0]
122
+ * eo = [1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0]
123
+ * facelets = "UUFUUFLLFUUURRRRRRFFRFFDFFDRRBDDBDDBLLDLLDLLDLBBUBBUBB"
124
+ *
125
+ * @param cp Corner Permutation
126
+ * @param co Corner Orientation
127
+ * @param ep Egde Permutation
128
+ * @param eo Edge Orientation
129
+ * @returns Cube state in the Kociemba facelets representation string
130
+ *
131
+ */
132
+ function toKociembaFacelets(cp, co, ep, eo) {
133
+ var faces = "URFDLB";
134
+ var facelets = [];
135
+ for (let i = 0; i < 54; i++) {
136
+ facelets[i] = faces[~~(i / 9)];
137
+ }
138
+ for (let i = 0; i < 8; i++) {
139
+ for (let p = 0; p < 3; p++) {
140
+ facelets[CORNER_FACELET_MAP[i][(p + co[i]) % 3]] = faces[~~(CORNER_FACELET_MAP[cp[i]][p] / 9)];
141
+ }
142
+ }
143
+ for (let i = 0; i < 12; i++) {
144
+ for (let p = 0; p < 2; p++) {
145
+ facelets[EDGE_FACELET_MAP[i][(p + eo[i]) % 2]] = faces[~~(EDGE_FACELET_MAP[ep[i]][p] / 9)];
146
+ }
147
+ }
148
+ return facelets.join('');
149
+ }
150
+ export { now, cubeTimestampLinearFit, cubeTimestampCalcSkew, toKociembaFacelets, };