@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,229 @@
1
+ /**
2
+ * Kociemba's two-phase algorithm, in TypeScript — solves any state in about
3
+ * 20 face turns, fast enough to make random-state scrambles.
4
+ *
5
+ * Phase 1 brings the cube into G1 = ⟨U, D, R2, L2, F2, B2⟩ (all twists and
6
+ * flips 0, the four middle-layer edges in the middle layer); phase 2 solves
7
+ * it using only G1 moves. Both are IDA* searches over small coordinates with
8
+ * pruning tables:
9
+ *
10
+ * phase 1: twist (3^7), flip (2^11), slice position (C(12,4) = 495)
11
+ * phase 2: corner permutation (8!), U/D edge permutation (8!), slice permutation (4!)
12
+ *
13
+ * Tables (~9 MB, move + pruning) are built once, on first use (about a
14
+ * second); build them early with `TwoPhase.create()` or in a worker.
15
+ * Pieces use the Kociemba numbering of core `cubies.ts`.
16
+ */
17
+ import { parity, permRank, permUnrank, relativeState, toCubies, transformMoves } from "../core/index.js";
18
+ import { MOVES, MOVE_CC, SOLVED_CC, mul } from "./pieces.js";
19
+ export { MOVES };
20
+ // ─── coordinates ───
21
+ const N_TWIST = 2187, N_FLIP = 2048, N_SLICE = 495, N_PERM8 = 40320, N_SLICEPERM = 24;
22
+ const twistOf = (c) => c.co.slice(0, 7).reduce((a, t) => a * 3 + t, 0);
23
+ const flipOf = (c) => c.eo.slice(0, 11).reduce((a, f) => a * 2 + f, 0);
24
+ const C = (n, k) => {
25
+ if (k < 0 || k > n)
26
+ return 0;
27
+ let r = 1;
28
+ for (let i = 0; i < k; i++)
29
+ r = (r * (n - i)) / (i + 1);
30
+ return r;
31
+ };
32
+ /** Which 4 of the 12 edge positions hold the middle-layer edges (8..11), as a combination rank. */
33
+ function sliceOf(c) {
34
+ let r = 0, k = 0;
35
+ for (let i = 0; i < 12; i++)
36
+ if (c.ep[i] >= 8)
37
+ r += C(i, ++k);
38
+ return r;
39
+ }
40
+ const SOLVED_SLICE = C(8, 1) + C(9, 2) + C(10, 3) + C(11, 4);
41
+ function withTwist(t) {
42
+ const co = new Array(8).fill(0);
43
+ for (let i = 6; i >= 0; i--, t = Math.floor(t / 3))
44
+ co[i] = t % 3;
45
+ co[7] = (3 - (co.reduce((a, b) => a + b, 0) % 3)) % 3;
46
+ return { ...SOLVED_CC, co };
47
+ }
48
+ function withFlip(f) {
49
+ const eo = new Array(12).fill(0);
50
+ for (let i = 10; i >= 0; i--, f = Math.floor(f / 2))
51
+ eo[i] = f % 2;
52
+ eo[11] = eo.reduce((a, b) => a + b, 0) % 2;
53
+ return { ...SOLVED_CC, eo };
54
+ }
55
+ function withSlice(r) {
56
+ const positions = [];
57
+ for (let k = 4, i = 11; k > 0; i--) {
58
+ if (r >= C(i, k)) {
59
+ r -= C(i, k);
60
+ positions.push(i);
61
+ k--;
62
+ }
63
+ }
64
+ const ep = new Array(12).fill(-1);
65
+ positions.sort((a, b) => a - b).forEach((p, j) => (ep[p] = 8 + j));
66
+ let e = 0;
67
+ for (let i = 0; i < 12; i++)
68
+ if (ep[i] < 0)
69
+ ep[i] = e++;
70
+ return { ...SOLVED_CC, ep };
71
+ }
72
+ // Phase 2 only turns with G1 moves.
73
+ const P2_MOVES = [0, 1, 2, 9, 10, 11, 4, 13, 7, 16];
74
+ function moveTable(n, make, coord, moves) {
75
+ const t = new Uint16Array(n * moves.length);
76
+ for (let i = 0; i < n; i++) {
77
+ const c = make(i);
78
+ moves.forEach((m, j) => (t[i * moves.length + j] = coord(mul(c, MOVE_CC[m]))));
79
+ }
80
+ return t;
81
+ }
82
+ /** Breadth-first distances over a product of two coordinates. */
83
+ function pruneTable(n1, n2, move1, move2, moveCount, start) {
84
+ const dist = new Int8Array(n1 * n2).fill(-1);
85
+ const queue = new Int32Array(n1 * n2);
86
+ let head = 0, tail = 0;
87
+ dist[start] = 0;
88
+ queue[tail++] = start;
89
+ while (head < tail) {
90
+ const s = queue[head++];
91
+ const a = Math.floor(s / n2), b = s % n2, d = dist[s] + 1;
92
+ for (let m = 0; m < moveCount; m++) {
93
+ const next = move1[a * moveCount + m] * n2 + move2[b * moveCount + m];
94
+ if (dist[next] < 0) {
95
+ dist[next] = d;
96
+ queue[tail++] = next;
97
+ }
98
+ }
99
+ }
100
+ return dist;
101
+ }
102
+ function buildTables() {
103
+ const all18 = MOVES.map((_, i) => i);
104
+ const twistMove = moveTable(N_TWIST, withTwist, twistOf, all18);
105
+ const flipMove = moveTable(N_FLIP, withFlip, flipOf, all18);
106
+ const sliceMove = moveTable(N_SLICE, withSlice, sliceOf, all18);
107
+ const cornerMove = moveTable(N_PERM8, (i) => ({ ...SOLVED_CC, cp: permUnrank(i, 8) }), (c) => permRank(c.cp), all18);
108
+ const udEdgeMove = moveTable(N_PERM8, (i) => ({ ...SOLVED_CC, ep: [...permUnrank(i, 8), 8, 9, 10, 11] }), (c) => permRank(c.ep.slice(0, 8)), P2_MOVES);
109
+ const slicePermMove = Uint8Array.from(moveTable(N_SLICEPERM, (i) => ({ ...SOLVED_CC, ep: [0, 1, 2, 3, 4, 5, 6, 7, ...permUnrank(i, 4).map((e) => e + 8)] }), (c) => permRank(c.ep.slice(8).map((e) => e - 8)), P2_MOVES));
110
+ // Phase-2 pruning walks P2 moves: pick the corner-table columns for them.
111
+ const cornerP2 = new Uint16Array(N_PERM8 * P2_MOVES.length);
112
+ for (let i = 0; i < N_PERM8; i++)
113
+ P2_MOVES.forEach((m, j) => (cornerP2[i * P2_MOVES.length + j] = cornerMove[i * 18 + m]));
114
+ return {
115
+ twistMove,
116
+ flipMove,
117
+ sliceMove,
118
+ cornerMove,
119
+ udEdgeMove,
120
+ slicePermMove,
121
+ pruneTwist: pruneTable(N_SLICE, N_TWIST, sliceMove, twistMove, 18, SOLVED_SLICE * N_TWIST),
122
+ pruneFlip: pruneTable(N_SLICE, N_FLIP, sliceMove, flipMove, 18, SOLVED_SLICE * N_FLIP),
123
+ pruneCorner: pruneTable(N_SLICEPERM, N_PERM8, slicePermMove, cornerP2, P2_MOVES.length, 0),
124
+ pruneEdge: pruneTable(N_SLICEPERM, N_PERM8, slicePermMove, udEdgeMove, P2_MOVES.length, 0),
125
+ };
126
+ }
127
+ const faceOf = (m) => Math.floor(m / 3);
128
+ /** Skip a turn of the same face, and the second of two opposite faces in the "wrong" order (U D ok, D U not). */
129
+ const redundant = (face, last) => last >= 0 && (face === last || face === last - 3);
130
+ export class TwoPhase {
131
+ t;
132
+ constructor(t) {
133
+ this.t = t;
134
+ }
135
+ /** Build the tables (about a second) — do it once, early, or in a worker. */
136
+ static create() {
137
+ return new TwoPhase(buildTables());
138
+ }
139
+ /** A solution for `state` (face turns, applied to `state` they solve it), or null. */
140
+ solve(state, options = {}) {
141
+ const cubies = toCubies(state);
142
+ if (!cubies)
143
+ return null;
144
+ // Solve the cube as seen with its centres at home, then say the same moves in the cube's own frame.
145
+ const c = { cp: cubies.cp, co: cubies.co, ep: cubies.ep, eo: cubies.eo };
146
+ if (parity(c.cp) !== parity(c.ep) || c.co.reduce((a, b) => a + b, 0) % 3 || c.eo.reduce((a, b) => a + b, 0) % 2)
147
+ return null;
148
+ const moves = this.search(c, options.maxLength ?? 21, performance.now() + (options.timeoutMs ?? 5000));
149
+ if (!moves)
150
+ return null;
151
+ return cubies.frame.id === 0 ? moves : transformMoves(moves, cubies.frame);
152
+ }
153
+ /** Moves taking `from` to `to` (e.g. from the cube's current state to a scramble's target). */
154
+ solveBetween(from, to, options = {}) {
155
+ return this.solve(relativeState(from, to), options);
156
+ }
157
+ search(c, maxLength, deadline) {
158
+ const t = this.t;
159
+ const path1 = [];
160
+ const path2 = [];
161
+ let nodes = 0;
162
+ let timedOut = false;
163
+ const phase2 = (corner, edge, slice, depth, last) => {
164
+ if (depth === 0)
165
+ return corner === 0 && edge === 0 && slice === 0;
166
+ const h = Math.max(t.pruneCorner[slice * N_PERM8 + corner], t.pruneEdge[slice * N_PERM8 + edge]);
167
+ if (h > depth)
168
+ return false;
169
+ for (let j = 0; j < P2_MOVES.length; j++) {
170
+ const m = P2_MOVES[j];
171
+ const f = faceOf(m);
172
+ if (redundant(f, last))
173
+ continue;
174
+ path2.push(m);
175
+ if (phase2(t.cornerMove[corner * 18 + m], t.udEdgeMove[edge * 10 + j], t.slicePermMove[slice * 10 + j], depth - 1, f))
176
+ return true;
177
+ path2.pop();
178
+ }
179
+ return false;
180
+ };
181
+ const startPhase2 = (budget) => {
182
+ let g1 = c;
183
+ for (const m of path1)
184
+ g1 = mul(g1, MOVE_CC[m]);
185
+ const corner = permRank(g1.cp), edge = permRank(g1.ep.slice(0, 8)), slice = permRank(g1.ep.slice(8).map((e) => e - 8));
186
+ const last = path1.length ? faceOf(path1[path1.length - 1]) : -1;
187
+ for (let d = 0; d <= Math.min(budget, 18); d++)
188
+ if (phase2(corner, edge, slice, d, last))
189
+ return true;
190
+ return false;
191
+ };
192
+ const phase1 = (twist, flip, slice, depth, last, total) => {
193
+ if ((++nodes & 4095) === 0 && performance.now() > deadline)
194
+ timedOut = true;
195
+ if (timedOut)
196
+ return false;
197
+ if (depth === 0) {
198
+ if (twist !== 0 || flip !== 0 || slice !== SOLVED_SLICE)
199
+ return false;
200
+ // A phase-1 path ending in a G1 move is a longer copy of one already tried.
201
+ const lm = path1[path1.length - 1];
202
+ if (path1.length && P2_MOVES.includes(lm))
203
+ return false;
204
+ return startPhase2(total - path1.length);
205
+ }
206
+ const h = Math.max(t.pruneTwist[slice * N_TWIST + twist], t.pruneFlip[slice * N_FLIP + flip]);
207
+ if (h > depth)
208
+ return false;
209
+ for (let m = 0; m < 18; m++) {
210
+ const f = faceOf(m);
211
+ if (redundant(f, last))
212
+ continue;
213
+ path1.push(m);
214
+ if (phase1(t.twistMove[twist * 18 + m], t.flipMove[flip * 18 + m], t.sliceMove[slice * 18 + m], depth - 1, f, total))
215
+ return true;
216
+ path1.pop();
217
+ }
218
+ return false;
219
+ };
220
+ const twist = twistOf(c), flip = flipOf(c), slice = sliceOf(c);
221
+ for (let d1 = 0; d1 <= maxLength; d1++) {
222
+ if (phase1(twist, flip, slice, d1, -1, maxLength))
223
+ return [...path1, ...path2].map((m) => MOVES[m]);
224
+ if (timedOut)
225
+ return null;
226
+ }
227
+ return null;
228
+ }
229
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The solver worker's entry point: tables are built here, off the main
3
+ * thread, and stay warm for the worker's lifetime. Use it through
4
+ * `createSolverWorker()` (client.ts).
5
+ */
6
+ export {};
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The solver worker's entry point: tables are built here, off the main
3
+ * thread, and stay warm for the worker's lifetime. Use it through
4
+ * `createSolverWorker()` (client.ts).
5
+ */
6
+ import { handle } from "./handle.js";
7
+ self.onmessage = async (e) => {
8
+ const { id, request } = e.data;
9
+ try {
10
+ self.postMessage({ id, ok: true, value: await handle(request) });
11
+ }
12
+ catch (err) {
13
+ self.postMessage({ id, ok: false, error: err instanceof Error ? err.message : String(err) });
14
+ }
15
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Compact, URL-safe encoding of a Recording (e.g. for share links or storing
3
+ * many solves). Version 1, base64url without padding:
4
+ * byte version
5
+ * varint totalMs
6
+ * varint scramble length, then 6-bit move symbols
7
+ * varint move count, then 6-bit move symbols
8
+ * varint× per move: time since the previous move in 10 ms ticks (rounded on
9
+ * the absolute time, so rounding never accumulates)
10
+ * A move symbol = family index (MOVE_FAMILIES order) × 3 + amount (1, ', 2),
11
+ * so every move family — slices, wide turns, rotations — fits.
12
+ * Decoding is strict: unknown version, truncation, trailing bytes, absurd sizes
13
+ * and bad symbols return null.
14
+ */
15
+ import { ByteReader } from "../core/index.js";
16
+ import type { Recording } from "./recording.js";
17
+ export declare const CODEC_VERSION = 1;
18
+ /** Append a recording's bytes (everything after the version byte). */
19
+ export declare function writeRecording(out: number[], rec: Recording): void;
20
+ /** Read what `writeRecording` wrote; null on anything malformed. */
21
+ export declare function readRecording(r: ByteReader): Recording | null;
22
+ export declare function encodeRecording(rec: Recording): string;
23
+ export declare function decodeRecording(text: string): Recording | null;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Compact, URL-safe encoding of a Recording (e.g. for share links or storing
3
+ * many solves). Version 1, base64url without padding:
4
+ * byte version
5
+ * varint totalMs
6
+ * varint scramble length, then 6-bit move symbols
7
+ * varint move count, then 6-bit move symbols
8
+ * varint× per move: time since the previous move in 10 ms ticks (rounded on
9
+ * the absolute time, so rounding never accumulates)
10
+ * A move symbol = family index (MOVE_FAMILIES order) × 3 + amount (1, ', 2),
11
+ * so every move family — slices, wide turns, rotations — fits.
12
+ * Decoding is strict: unknown version, truncation, trailing bytes, absurd sizes
13
+ * and bad symbols return null.
14
+ */
15
+ import { ByteReader, MOVE_FAMILIES, fromBase64Url, packBits, toBase64Url, writeVarint } from "../core/index.js";
16
+ export const CODEC_VERSION = 1;
17
+ const TICK_MS = 10;
18
+ const AMOUNTS = [1, -1, 2];
19
+ const MAX_MOVES = 10_000;
20
+ const MAX_TIME = 24 * 3600 * 1000;
21
+ const symbolOf = (m) => MOVE_FAMILIES.indexOf(m.family) * 3 + AMOUNTS.indexOf(m.amount);
22
+ const moveOf = (s) => {
23
+ const family = MOVE_FAMILIES[Math.floor(s / 3)];
24
+ return family ? { family, amount: AMOUNTS[s % 3] } : null;
25
+ };
26
+ /** Append a recording's bytes (everything after the version byte). */
27
+ export function writeRecording(out, rec) {
28
+ writeVarint(out, rec.totalMs);
29
+ writeVarint(out, rec.scramble.length);
30
+ packBits(out, rec.scramble.map(symbolOf), 6);
31
+ writeVarint(out, rec.moves.length);
32
+ packBits(out, rec.moves.map((m) => symbolOf(m.move)), 6);
33
+ let prev = 0;
34
+ for (const m of rec.moves) {
35
+ const tick = Math.max(prev, Math.round(Math.max(0, m.t) / TICK_MS));
36
+ writeVarint(out, tick - prev);
37
+ prev = tick;
38
+ }
39
+ }
40
+ /** Read what `writeRecording` wrote; null on anything malformed. */
41
+ export function readRecording(r) {
42
+ const totalMs = r.varint();
43
+ if (totalMs === null || totalMs > MAX_TIME)
44
+ return null;
45
+ const sLen = r.varint();
46
+ if (sLen === null || sLen > MAX_MOVES)
47
+ return null;
48
+ const scramble = r.packed(sLen, 6)?.map(moveOf);
49
+ if (!scramble || scramble.some((m) => m === null))
50
+ return null;
51
+ const n = r.varint();
52
+ if (n === null || n > MAX_MOVES)
53
+ return null;
54
+ const moves = r.packed(n, 6)?.map(moveOf);
55
+ if (!moves || moves.some((m) => m === null))
56
+ return null;
57
+ const timed = [];
58
+ let tick = 0;
59
+ for (let i = 0; i < n; i++) {
60
+ const d = r.varint();
61
+ if (d === null)
62
+ return null;
63
+ tick += d;
64
+ timed.push({ move: moves[i], t: tick * TICK_MS });
65
+ }
66
+ if (timed.length && timed.at(-1).t > totalMs + TICK_MS)
67
+ return null;
68
+ return { scramble: scramble, moves: timed, totalMs };
69
+ }
70
+ export function encodeRecording(rec) {
71
+ const out = [CODEC_VERSION];
72
+ writeRecording(out, rec);
73
+ return toBase64Url(out);
74
+ }
75
+ export function decodeRecording(text) {
76
+ const bytes = fromBase64Url(text);
77
+ if (!bytes)
78
+ return null;
79
+ const r = new ByteReader(bytes);
80
+ if (r.byte() !== CODEC_VERSION)
81
+ return null;
82
+ const rec = readRecording(r);
83
+ return rec && r.done ? rec : null;
84
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./recording.js";
2
+ export * from "./codec.js";
3
+ export * from "./replay.js";
4
+ export * from "./timings.js";
5
+ export * from "./share.js";
6
+ export * from "./segments.js";
7
+ export * from "./stats.js";
@@ -0,0 +1,7 @@
1
+ export * from "./recording.js";
2
+ export * from "./codec.js";
3
+ export * from "./replay.js";
4
+ export * from "./timings.js";
5
+ export * from "./share.js";
6
+ export * from "./segments.js";
7
+ export * from "./stats.js";
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A recorded solve: the scramble and every move with the time it was made
3
+ * (ms since the timer started), plus the final time. Smart cubes report a
4
+ * move when it finishes, so `t` is the moment the move COMPLETED.
5
+ */
6
+ import { type Move } from "../core/index.js";
7
+ export interface TimedMove {
8
+ move: Move;
9
+ /** ms since the timer started, when the move completed. Non-decreasing. */
10
+ t: number;
11
+ }
12
+ export interface Recording {
13
+ scramble: Move[];
14
+ moves: TimedMove[];
15
+ /** Final time in ms (≥ the last move's t). */
16
+ totalMs: number;
17
+ }
18
+ /** Convenience builder: `recording("R U", [["R", 120], ["U'", 300]], 450)`. */
19
+ export declare function recording(scramble: string, moves: readonly [string, number][], totalMs?: number): Recording;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * A recorded solve: the scramble and every move with the time it was made
3
+ * (ms since the timer started), plus the final time. Smart cubes report a
4
+ * move when it finishes, so `t` is the moment the move COMPLETED.
5
+ */
6
+ import { parseAlg } from "../core/index.js";
7
+ /** Convenience builder: `recording("R U", [["R", 120], ["U'", 300]], 450)`. */
8
+ export function recording(scramble, moves, totalMs) {
9
+ const timed = moves.map(([m, t]) => {
10
+ const parsed = parseAlg(m);
11
+ if (parsed.length !== 1)
12
+ throw new Error(`"${m}" is not a single move`);
13
+ return { move: parsed[0], t };
14
+ });
15
+ for (let i = 1; i < timed.length; i++)
16
+ if (timed[i].t < timed[i - 1].t)
17
+ throw new Error(`Move ${i} goes back in time`);
18
+ const last = timed.at(-1)?.t ?? 0;
19
+ return { scramble: parseAlg(scramble), moves: timed, totalMs: Math.max(totalMs ?? last, last) };
20
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Time-accurate replay. A move's animation ENDS at its recorded time (smart
3
+ * cubes report a move when it completes) and lasts at most `maxAnimMs`, never
4
+ * overlapping the previous move — so fast triggers stay fast and pauses stay
5
+ * pauses, unlike a fixed-tempo player.
6
+ */
7
+ import type { Recording } from "./recording.js";
8
+ export interface ReplayOptions {
9
+ /** Longest a single move animates (ms of recording time). Default 150. */
10
+ maxAnimMs?: number;
11
+ }
12
+ export interface Position {
13
+ /** Moves fully done at this time. */
14
+ applied: number;
15
+ /** The move animating right now, if any, with progress 0..1. */
16
+ active?: {
17
+ index: number;
18
+ progress: number;
19
+ };
20
+ }
21
+ /** Animation window [start, end] of every move. */
22
+ export declare function moveWindows(rec: Recording, options?: ReplayOptions): [number, number][];
23
+ export declare function positionAt(rec: Recording, timeMs: number, options?: ReplayOptions): Position;
24
+ export interface ClockHost {
25
+ now(): number;
26
+ /** Ask for a callback on the next frame; returns a cancel function. */
27
+ frame(cb: () => void): () => void;
28
+ }
29
+ type Listener = (time: number, position: Position) => void;
30
+ /**
31
+ * Drives a replay in real time. `rate` scales playback (2 = twice as fast)
32
+ * keeping the proportions of pauses. Listeners get the recording time and
33
+ * the position on every frame and after every seek.
34
+ */
35
+ export declare class ReplayClock {
36
+ readonly recording: Recording;
37
+ private readonly options;
38
+ private readonly host;
39
+ private time;
40
+ private playing;
41
+ private anchorReal;
42
+ private anchorTime;
43
+ private cancel;
44
+ private listeners;
45
+ private _rate;
46
+ constructor(recording: Recording, options?: ReplayOptions, host?: ClockHost);
47
+ get currentTime(): number;
48
+ get isPlaying(): boolean;
49
+ get rate(): number;
50
+ set rate(r: number);
51
+ onChange(listener: Listener): () => void;
52
+ play(): void;
53
+ pause(): void;
54
+ seek(timeMs: number): void;
55
+ /** Jump to the moment move `index` completed (−1 = start). */
56
+ seekToMove(index: number): void;
57
+ private rebase;
58
+ private tick;
59
+ private loop;
60
+ private emit;
61
+ }
62
+ /**
63
+ * A recording with every pause longer than `maxPauseMs` shortened to it —
64
+ * long recognitions (or a break mid-solve) don't drag the replay. Moves keep
65
+ * their order and the gaps up to the cap; the total shrinks accordingly.
66
+ */
67
+ export declare function compressPauses(rec: Recording, maxPauseMs: number): Recording;
68
+ export {};
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Time-accurate replay. A move's animation ENDS at its recorded time (smart
3
+ * cubes report a move when it completes) and lasts at most `maxAnimMs`, never
4
+ * overlapping the previous move — so fast triggers stay fast and pauses stay
5
+ * pauses, unlike a fixed-tempo player.
6
+ */
7
+ /** Animation window [start, end] of every move. */
8
+ export function moveWindows(rec, options = {}) {
9
+ const max = options.maxAnimMs ?? 150;
10
+ let prevEnd = 0;
11
+ return rec.moves.map(({ t }) => {
12
+ const start = Math.max(t - max, prevEnd);
13
+ prevEnd = t;
14
+ return [start, t];
15
+ });
16
+ }
17
+ export function positionAt(rec, timeMs, options = {}) {
18
+ const windows = moveWindows(rec, options);
19
+ let applied = 0;
20
+ while (applied < windows.length && windows[applied][1] <= timeMs)
21
+ applied++;
22
+ const next = windows[applied];
23
+ if (next && timeMs > next[0]) {
24
+ const span = next[1] - next[0];
25
+ return { applied, active: { index: applied, progress: span > 0 ? (timeMs - next[0]) / span : 1 } };
26
+ }
27
+ return { applied };
28
+ }
29
+ const browserHost = {
30
+ now: () => performance.now(),
31
+ frame: (cb) => {
32
+ const id = requestAnimationFrame(() => cb());
33
+ return () => cancelAnimationFrame(id);
34
+ },
35
+ };
36
+ /**
37
+ * Drives a replay in real time. `rate` scales playback (2 = twice as fast)
38
+ * keeping the proportions of pauses. Listeners get the recording time and
39
+ * the position on every frame and after every seek.
40
+ */
41
+ export class ReplayClock {
42
+ recording;
43
+ options;
44
+ host;
45
+ time = 0;
46
+ playing = false;
47
+ anchorReal = 0;
48
+ anchorTime = 0;
49
+ cancel = null;
50
+ listeners = new Set();
51
+ _rate = 1;
52
+ constructor(recording, options = {}, host = browserHost) {
53
+ this.recording = recording;
54
+ this.options = options;
55
+ this.host = host;
56
+ }
57
+ get currentTime() {
58
+ return this.time;
59
+ }
60
+ get isPlaying() {
61
+ return this.playing;
62
+ }
63
+ get rate() {
64
+ return this._rate;
65
+ }
66
+ set rate(r) {
67
+ this.rebase();
68
+ this._rate = r;
69
+ }
70
+ onChange(listener) {
71
+ this.listeners.add(listener);
72
+ return () => this.listeners.delete(listener);
73
+ }
74
+ play() {
75
+ if (this.playing)
76
+ return;
77
+ if (this.time >= this.recording.totalMs)
78
+ this.time = 0;
79
+ this.playing = true;
80
+ this.rebase();
81
+ this.loop();
82
+ }
83
+ pause() {
84
+ this.tick();
85
+ this.playing = false;
86
+ this.cancel?.();
87
+ this.cancel = null;
88
+ }
89
+ seek(timeMs) {
90
+ this.time = Math.min(Math.max(0, timeMs), this.recording.totalMs);
91
+ this.rebase();
92
+ this.emit();
93
+ }
94
+ /** Jump to the moment move `index` completed (−1 = start). */
95
+ seekToMove(index) {
96
+ this.seek(index < 0 ? 0 : this.recording.moves[Math.min(index, this.recording.moves.length - 1)].t);
97
+ }
98
+ rebase() {
99
+ this.anchorReal = this.host.now();
100
+ this.anchorTime = this.time;
101
+ }
102
+ tick() {
103
+ if (!this.playing)
104
+ return;
105
+ this.time = Math.min(this.anchorTime + (this.host.now() - this.anchorReal) * this._rate, this.recording.totalMs);
106
+ this.emit();
107
+ if (this.time >= this.recording.totalMs) {
108
+ this.playing = false;
109
+ this.cancel?.();
110
+ this.cancel = null;
111
+ }
112
+ }
113
+ loop() {
114
+ this.cancel = this.host.frame(() => {
115
+ this.tick();
116
+ if (this.playing)
117
+ this.loop();
118
+ });
119
+ }
120
+ emit() {
121
+ const pos = positionAt(this.recording, this.time, this.options);
122
+ for (const l of this.listeners)
123
+ l(this.time, pos);
124
+ }
125
+ }
126
+ /**
127
+ * A recording with every pause longer than `maxPauseMs` shortened to it —
128
+ * long recognitions (or a break mid-solve) don't drag the replay. Moves keep
129
+ * their order and the gaps up to the cap; the total shrinks accordingly.
130
+ */
131
+ export function compressPauses(rec, maxPauseMs) {
132
+ let removed = 0;
133
+ let prev = 0;
134
+ const moves = rec.moves.map((m) => {
135
+ const gap = m.t - prev;
136
+ prev = m.t;
137
+ if (gap > maxPauseMs)
138
+ removed += gap - maxPauseMs;
139
+ return { move: m.move, t: m.t - removed };
140
+ });
141
+ const tail = rec.totalMs - prev;
142
+ if (tail > maxPauseMs)
143
+ removed += tail - maxPauseMs;
144
+ return { scramble: rec.scramble, moves, totalMs: rec.totalMs - removed };
145
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Segments — sections of a timeline for a progress bar: where each part
3
+ * starts and ends, what it's called, and optionally where "looking" turns
4
+ * into "turning". Plain data, independent of any method: a player shows
5
+ * whatever segments it's given (guide chapters, stored stage times…);
6
+ * `stageSegments` computes them from a recorded solve for any Method.
7
+ */
8
+ import type { Method } from "../core/index.js";
9
+ import type { Recording } from "./recording.js";
10
+ export interface Segment {
11
+ /** ms of recording time. */
12
+ start: number;
13
+ end: number;
14
+ label: string;
15
+ /** Stable id — becomes ::part(segment-<id>) for styling. */
16
+ id?: string;
17
+ /** Extra detail, e.g. the F2L slot or the OLL case. */
18
+ detail?: string;
19
+ /** End of the recognition pause (start ≤ split ≤ end): the part before it was looking, after it turning. */
20
+ split?: number;
21
+ /** Moves in the segment. */
22
+ moves?: number;
23
+ /** A colour of your own (else the player's palette). */
24
+ color?: string;
25
+ }
26
+ /** The stages of `method` in a recorded solve as segments (skipped stages — no time of their own — left out). */
27
+ export declare function stageSegments(method: Method, rec: Recording): Segment[];
28
+ /** The segment `time` falls in (index), or −1. */
29
+ export declare function segmentAt(segments: readonly Segment[], time: number): number;
30
+ /** How much of a segment has played at `time` (0..1). */
31
+ export declare function segmentPlayed(s: Segment, time: number): number;