@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
package/solve/lse.js ADDED
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Last-six-edges solver (Roux 4a–4c): M and U moves only, with the blocks
3
+ * and CMLL solved. Small enough for an exact table: breadth-first from the
4
+ * stage's goal states over what the stage is about (for EOLR: orientation
5
+ * of the six edges, where UL / UR are, centres, corner AUF — ~15 000
6
+ * combinations), keeping one state of each as a trainer case.
7
+ *
8
+ * States are read with the blocks at home: a smart cube reports an M as
9
+ * R L' with the centres fixed, which to the solver looks like the whole
10
+ * cube turned by x — so the state is first turned back by x^k until the
11
+ * blocks are home (the centres then show the M offset, as on the real cube).
12
+ */
13
+ import { CORNER_FACELETS, EDGE_FACELETS, IDENTITY_FRAME, applyMove, applyMoves, parseAlg, reframe, solvedState, unreframe, } from "../core/index.js";
14
+ const LSE_MOVES = parseAlg("U U2 U' M M2 M'");
15
+ const X_TURNS = ["", "x", "x2", "x'"];
16
+ /** Edges the M / U moves never touch (DR DL FR FL BL BR) and the D corners — the blocks. */
17
+ const BLOCK_FACELETS = [4, 6, 8, 9, 10, 11].flatMap((e) => EDGE_FACELETS[e]).concat([4, 5, 6, 7].flatMap((c) => CORNER_FACELETS[c]));
18
+ /** Six LSE edge positions: UF UB DF DB UL UR. */
19
+ const LSE_SLOTS = [1, 3, 5, 7, 2, 0];
20
+ const REFERENCE = new Set(EDGE_FACELETS.map((f) => f[0]));
21
+ const UL = new Set(EDGE_FACELETS[2]), UR = new Set(EDGE_FACELETS[0]);
22
+ const CENTRES = [4, 13, 22, 31, 40, 49];
23
+ const U_CORNERS = [0, 1, 2, 3].flatMap((c) => CORNER_FACELETS[c]);
24
+ /** The state turned by x^k so the blocks are home, or null if they aren't solved. */
25
+ function blocksHome(state) {
26
+ for (const x of X_TURNS) {
27
+ const t = x ? applyMoves(state, x) : state;
28
+ if (BLOCK_FACELETS.every((f) => t[f] === f))
29
+ return t;
30
+ }
31
+ return null;
32
+ }
33
+ /** Every LSE detail of a blocks-home state: each of the six edges' place and flip, centres, corner AUF. */
34
+ function lseKey(s) {
35
+ const edges = LSE_SLOTS.map((j) => `${s[EDGE_FACELETS[j][0]]}`).join(".");
36
+ return `${edges}|${CENTRES.map((c) => s[c]).join(".")}|${U_CORNERS.map((f) => s[f]).join(".")}`;
37
+ }
38
+ /** EOLR features of a blocks-home state. */
39
+ function eolrKey(s) {
40
+ const eo = LSE_SLOTS.map((j) => (REFERENCE.has(s[EDGE_FACELETS[j][0]]) ? 0 : 1)).join("");
41
+ const at = (piece) => LSE_SLOTS.findIndex((j) => piece.has(s[EDGE_FACELETS[j][0]]));
42
+ return `${eo}|${at(UL)}${at(UR)}|${CENTRES.map((c) => s[c]).join(".")}|${U_CORNERS.map((f) => s[f]).join(".")}`;
43
+ }
44
+ export class LseSolver {
45
+ def;
46
+ key;
47
+ /** feature → distance and one state with those features */
48
+ table = new Map();
49
+ byDepth = [];
50
+ constructor(def) {
51
+ this.def = def;
52
+ this.key = def.features === "lse" ? lseKey : eolrKey;
53
+ const keyFn = this.key;
54
+ let frontier = [];
55
+ for (const g of def.goals) {
56
+ const s = applyMoves(solvedState(), g);
57
+ const k = keyFn(s);
58
+ if (!this.table.has(k)) {
59
+ this.table.set(k, { d: 0, state: s });
60
+ frontier.push(s);
61
+ }
62
+ }
63
+ for (let d = 0; frontier.length; d++) {
64
+ this.byDepth[d] = frontier;
65
+ const next = [];
66
+ for (const s of frontier) {
67
+ for (const m of LSE_MOVES) {
68
+ const t = applyMove(s, m);
69
+ const k = keyFn(t);
70
+ if (!this.table.has(k)) {
71
+ this.table.set(k, { d: d + 1, state: t });
72
+ next.push(t);
73
+ }
74
+ }
75
+ }
76
+ frontier = next;
77
+ }
78
+ }
79
+ /** Deepest level. */
80
+ get maxDepth() {
81
+ return this.byDepth.length - 1;
82
+ }
83
+ canonical(state, frame) {
84
+ return blocksHome(frame === IDENTITY_FRAME ? state : reframe(state, frame));
85
+ }
86
+ /** Fewest M / U moves to the stage (−1 if the blocks and CMLL aren't solved). */
87
+ distance(state, options = {}) {
88
+ const s = this.canonical(state, options.frame ?? IDENTITY_FRAME);
89
+ return s ? (this.table.get(this.key(s))?.d ?? -1) : -1;
90
+ }
91
+ /** Optimal solutions in M and U moves (every one with `all`). */
92
+ solve(state, options = {}) {
93
+ const start = this.canonical(state, options.frame ?? IDENTITY_FRAME);
94
+ if (!start)
95
+ return [];
96
+ const d0 = this.table.get(this.key(start))?.d;
97
+ if (d0 === undefined)
98
+ return [];
99
+ const limit = options.all ? (options.limit ?? 256) : 1;
100
+ const out = [];
101
+ const walk = (s, d, path) => {
102
+ if (out.length >= limit)
103
+ return;
104
+ if (d === 0)
105
+ return void out.push([...path]);
106
+ for (const m of LSE_MOVES) {
107
+ if (path.length && path[path.length - 1].family === m.family)
108
+ continue;
109
+ const t = applyMove(s, m);
110
+ if (this.table.get(this.key(t))?.d === d - 1)
111
+ walk(t, d - 1, [...path, m]);
112
+ }
113
+ };
114
+ walk(start, d0, []);
115
+ return out;
116
+ }
117
+ nextMoves(state, options = {}) {
118
+ const seen = new Map();
119
+ for (const s of this.solve(state, { ...options, all: true }))
120
+ if (s[0])
121
+ seen.set(`${s[0].family}${s[0].amount}`, s[0]);
122
+ return [...seen.values()];
123
+ }
124
+ /**
125
+ * A case exactly `depth` M / U moves from the stage, as a state a cube
126
+ * can show with its centres home (turned by x when the case has the M
127
+ * slice off) — ready for a scramble. `frame` puts it on another face.
128
+ */
129
+ sampleState(depth, random = Math.random, frame = IDENTITY_FRAME) {
130
+ const level = this.byDepth[depth];
131
+ if (!level?.length)
132
+ return null;
133
+ let s = level[Math.floor(random() * level.length)];
134
+ for (const x of X_TURNS) {
135
+ const t = x ? applyMoves(s, x) : s;
136
+ if (CENTRES.every((c) => t[c] === c)) {
137
+ s = t;
138
+ break;
139
+ }
140
+ }
141
+ return frame === IDENTITY_FRAME ? s : unreframe(s, frame);
142
+ }
143
+ }
144
+ const lseSolvers = new Map();
145
+ export function lseSolver(def) {
146
+ const key = JSON.stringify(def);
147
+ let s = lseSolvers.get(key);
148
+ if (!s)
149
+ lseSolvers.set(key, (s = new LseSolver(def)));
150
+ return s;
151
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The cube as pieces for the solvers: Kociemba corner / edge permutation +
3
+ * orientation (core cubies.ts numbering), the 18 face turns as such piece
4
+ * permutations (derived from cubecore's move geometry), and composition.
5
+ */
6
+ import { type Move } from "../core/index.js";
7
+ export interface CC {
8
+ cp: number[];
9
+ co: number[];
10
+ ep: number[];
11
+ eo: number[];
12
+ }
13
+ /** The 18 face turns, index = face × 3 + (0: quarter, 1: half, 2: prime). */
14
+ export declare const MOVES: readonly Move[];
15
+ /** Pieces after `b` is applied to `a`. */
16
+ export declare function mul(a: CC, b: CC): CC;
17
+ export declare const SOLVED_CC: CC;
18
+ export declare const MOVE_CC: CC[];
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The cube as pieces for the solvers: Kociemba corner / edge permutation +
3
+ * orientation (core cubies.ts numbering), the 18 face turns as such piece
4
+ * permutations (derived from cubecore's move geometry), and composition.
5
+ */
6
+ import { applyMove, solvedState, toCubies } from "../core/index.js";
7
+ const FACE_ORDER = ["U", "R", "F", "D", "L", "B"];
8
+ /** The 18 face turns, index = face × 3 + (0: quarter, 1: half, 2: prime). */
9
+ export const MOVES = FACE_ORDER.flatMap((family) => [1, 2, -1].map((amount) => ({ family, amount })));
10
+ /** Pieces after `b` is applied to `a`. */
11
+ export function mul(a, b) {
12
+ return {
13
+ cp: b.cp.map((p) => a.cp[p]),
14
+ co: b.cp.map((p, i) => (a.co[p] + b.co[i]) % 3),
15
+ ep: b.ep.map((p) => a.ep[p]),
16
+ eo: b.ep.map((p, i) => (a.eo[p] + b.eo[i]) % 2),
17
+ };
18
+ }
19
+ export const SOLVED_CC = { cp: [0, 1, 2, 3, 4, 5, 6, 7], co: new Array(8).fill(0), ep: Array.from({ length: 12 }, (_, i) => i), eo: new Array(12).fill(0) };
20
+ // Each move as pieces, derived from cubecore's own move geometry (no hand-typed tables).
21
+ export const MOVE_CC = MOVES.map((m) => {
22
+ const c = toCubies(applyMove(solvedState(), m));
23
+ return { cp: c.cp, co: c.co, ep: c.ep, eo: c.eo };
24
+ });
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Messages between the solver worker and its client. Everything is plain
3
+ * data (structured-clone friendly): frames travel as their FRAMES id, a
4
+ * random source as a seed, stages as StageDef data.
5
+ */
6
+ import type { Move, Piece, State } from "../core/index.js";
7
+ import type { ScramblePreset } from "./scramble.js";
8
+ import type { AnyStageDef as StageDef } from "../core/index.js";
9
+ export interface Seeded {
10
+ /** Seed for reproducible results; omitted = Math.random. */
11
+ seed?: number;
12
+ /** FRAMES id (0 = canonical). */
13
+ frameId?: number;
14
+ from?: State;
15
+ maxLength?: number;
16
+ timeoutMs?: number;
17
+ }
18
+ export type Request = {
19
+ op: "warmUp";
20
+ stages: StageDef[];
21
+ } | {
22
+ op: "solve";
23
+ state: State;
24
+ maxLength?: number;
25
+ timeoutMs?: number;
26
+ } | {
27
+ op: "solveBetween";
28
+ from: State;
29
+ to: State;
30
+ maxLength?: number;
31
+ } | ({
32
+ op: "randomScramble";
33
+ preset?: ScramblePreset;
34
+ keep?: Piece[];
35
+ } & Seeded) | ({
36
+ op: "stageScramble";
37
+ stage: StageDef;
38
+ length: number;
39
+ } & Seeded) | {
40
+ op: "stageSolve";
41
+ stage: StageDef;
42
+ state: State;
43
+ frameId?: number;
44
+ all?: boolean;
45
+ limit?: number;
46
+ } | {
47
+ op: "stageDistance";
48
+ stage: StageDef;
49
+ state: State;
50
+ frameId?: number;
51
+ };
52
+ export type Response = {
53
+ id: number;
54
+ ok: true;
55
+ value: unknown;
56
+ } | {
57
+ id: number;
58
+ ok: false;
59
+ error: string;
60
+ };
61
+ export interface Envelope {
62
+ id: number;
63
+ request: Request;
64
+ }
65
+ export type ScrambleResult = {
66
+ moves: Move[];
67
+ state: State;
68
+ };
69
+ /** A small seeded generator (mulberry32). */
70
+ export declare function seededRandom(seed: number): () => number;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Messages between the solver worker and its client. Everything is plain
3
+ * data (structured-clone friendly): frames travel as their FRAMES id, a
4
+ * random source as a seed, stages as StageDef data.
5
+ */
6
+ /** A small seeded generator (mulberry32). */
7
+ export function seededRandom(seed) {
8
+ let a = seed >>> 0;
9
+ return () => {
10
+ a = (a + 0x6d2b79f5) >>> 0;
11
+ let t = a;
12
+ t = Math.imul(t ^ (t >>> 15), t | 1);
13
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
14
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
15
+ };
16
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Random-state scrambles: draw a random reachable state (optionally with
3
+ * some pieces kept solved or oriented), solve it with the two-phase solver,
4
+ * and use the inverse of the solution — every state is equally likely, like
5
+ * WCA scrambles.
6
+ *
7
+ * Constraints are written for the canonical frame (first layer D, last layer
8
+ * U, Roux blocks on L/R), so presets read naturally; `frame` then puts the
9
+ * same case on any face — colour neutral, e.g. "LL scramble with the cross
10
+ * on white": `randomScramble({ preset: "ll", frame: frameFor("U") })`.
11
+ */
12
+ import { type Cubie, type AnyStageDef, type Piece, type Frame, type Move, type State } from "../core/index.js";
13
+ import { type Placement } from "./stage.js";
14
+ import { type SolveOptions, TwoPhase } from "./twophase.js";
15
+ export type PiecePredicate = (cubie: Cubie) => boolean;
16
+ export interface RandomStateOptions {
17
+ /** Pieces kept solved (in place, oriented). */
18
+ solved?: PiecePredicate;
19
+ /** Pieces put exactly here (e.g. a cross sampled at a given distance), and optionally all edges' orientation. */
20
+ place?: Placement;
21
+ /** Pieces kept oriented, but permuted among themselves. */
22
+ oriented?: PiecePredicate;
23
+ /** Random source in [0, 1). Default Math.random. */
24
+ random?: () => number;
25
+ }
26
+ /** A predicate for `solved` / `oriented`: exactly these pieces (Kociemba ids, canonical positions). */
27
+ export declare function piecesPredicate(pieces: readonly Piece[]): PiecePredicate;
28
+ /** A uniformly random reachable state (centres at home) honouring the constraints. */
29
+ export declare function randomState(options?: RandomStateOptions): State;
30
+ export type ScramblePreset = "full" | "f2l" | "ls" | "ll" | "zbll" | "pll" | "ell" | "cmll";
31
+ export declare const SCRAMBLE_PRESETS: Record<ScramblePreset, {
32
+ label: string;
33
+ solved?: PiecePredicate;
34
+ oriented?: PiecePredicate;
35
+ }>;
36
+ export interface ScrambleOptions extends RandomStateOptions, SolveOptions {
37
+ preset?: ScramblePreset;
38
+ /** More pieces kept solved on top of the preset's (e.g. the F2L slots not being practised). */
39
+ keep?: readonly Piece[];
40
+ /** Put the case on another face: canonical D (cross / first block bottom) goes to `frame.face.D`. */
41
+ frame?: Frame;
42
+ /** A solver to reuse (default: one shared instance, built on first use). */
43
+ solver?: TwoPhase;
44
+ /**
45
+ * Where the cube is now (e.g. a smart cube's state after the last attempt):
46
+ * the scramble then goes from here to the random target — no need to solve
47
+ * the cube first. Default: solved.
48
+ */
49
+ from?: State;
50
+ }
51
+ /** The shared solver (tables built on first call, about half a second). */
52
+ export declare const sharedSolver: () => TwoPhase;
53
+ /** A random-state scramble: the moves, and the state they lead to (from `from`, default solved). */
54
+ export declare function randomScramble(options?: ScrambleOptions): {
55
+ moves: Move[];
56
+ state: State;
57
+ };
58
+ /** Moves from `options.from` (default solved) to `target`; null if the solver gave up or there is nothing to do. */
59
+ export declare function scrambleTo(target: State, options?: Pick<ScrambleOptions, "from" | "solver" | "maxLength" | "timeoutMs">): {
60
+ moves: Move[];
61
+ state: State;
62
+ } | null;
63
+ export interface StageScrambleOptions extends Omit<ScrambleOptions, "preset" | "solved" | "oriented" | "place"> {
64
+ /** The stage (piece stages, or a last-six-edges stage like EOLR) and how many moves its optimal solution must take. */
65
+ stage: AnyStageDef;
66
+ length: number;
67
+ }
68
+ /**
69
+ * A trainer scramble: a random-state scramble whose `stage` (cross, xcross,
70
+ * EOCross…) takes exactly `length` moves at best — on any face (`frame`),
71
+ * from wherever the cube is (`from`). Null if no such case was found.
72
+ */
73
+ export declare function stageScramble(options: StageScrambleOptions): {
74
+ moves: Move[];
75
+ state: State;
76
+ } | null;
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Random-state scrambles: draw a random reachable state (optionally with
3
+ * some pieces kept solved or oriented), solve it with the two-phase solver,
4
+ * and use the inverse of the solution — every state is equally likely, like
5
+ * WCA scrambles.
6
+ *
7
+ * Constraints are written for the canonical frame (first layer D, last layer
8
+ * U, Roux blocks on L/R), so presets read naturally; `frame` then puts the
9
+ * same case on any face — colour neutral, e.g. "LL scramble with the cross
10
+ * on white": `randomScramble({ preset: "ll", frame: frameFor("U") })`.
11
+ */
12
+ import { CORNER_FACELETS, CUBIE_OF_FACELET, EDGE_FACELETS, IDENTITY_FRAME, applyMoves, fromCubies, isLseStage, parity, solvedState, unreframe, } from "../core/index.js";
13
+ import { flipBits, setRandomStateHook, stageSolver } from "./stage.js";
14
+ import { lseSolver } from "./lse.js";
15
+ import { TwoPhase } from "./twophase.js";
16
+ const cornerCubie = (j) => CUBIE_OF_FACELET[CORNER_FACELETS[j][0]];
17
+ const edgeCubie = (j) => CUBIE_OF_FACELET[EDGE_FACELETS[j][0]];
18
+ function shuffle(items, random) {
19
+ for (let i = items.length - 1; i > 0; i--) {
20
+ const j = Math.floor(random() * (i + 1));
21
+ [items[i], items[j]] = [items[j], items[i]];
22
+ }
23
+ return items;
24
+ }
25
+ /** A predicate for `solved` / `oriented`: exactly these pieces (Kociemba ids, canonical positions). */
26
+ export function piecesPredicate(pieces) {
27
+ const cubies = new Set(pieces.map((p) => (p.kind === "edge" ? edgeCubie(p.id) : cornerCubie(p.id))));
28
+ return (c) => cubies.has(c);
29
+ }
30
+ /** A uniformly random reachable state (centres at home) honouring the constraints. */
31
+ export function randomState(options = {}) {
32
+ const random = options.random ?? Math.random;
33
+ const solved = options.solved ?? (() => false);
34
+ const oriented = options.oriented ?? (() => false);
35
+ const placed = { edge: new Map(), corner: new Map() };
36
+ for (const [piece, value] of options.place?.pieces ?? [])
37
+ placed[piece.kind].set(piece.id, value);
38
+ const flip = options.place?.flip;
39
+ const place = (kind, n, cubieOf, twists) => {
40
+ const perm = new Array(n).fill(-1);
41
+ const ori = new Array(n).fill(0);
42
+ const taken = new Set();
43
+ // Placed pieces, then solved ones, at their fixed positions.
44
+ for (const [id, value] of placed[kind]) {
45
+ const pos = Math.floor(value / twists);
46
+ perm[pos] = id;
47
+ ori[pos] = value % twists;
48
+ taken.add(id);
49
+ }
50
+ for (let j = 0; j < n; j++) {
51
+ if (taken.has(j) || !solved(cubieOf(j)))
52
+ continue;
53
+ perm[j] = j;
54
+ taken.add(j);
55
+ }
56
+ const free = perm.map((p, pos) => (p < 0 ? pos : -1)).filter((pos) => pos >= 0);
57
+ const pieces = shuffle(Array.from({ length: n }, (_, j) => j).filter((j) => !taken.has(j)), random);
58
+ free.forEach((pos, k) => (perm[pos] = pieces[k]));
59
+ if (kind === "edge" && flip !== undefined) {
60
+ const eo = flipBits(flip);
61
+ free.forEach((pos) => (ori[pos] = eo[pos]));
62
+ }
63
+ else {
64
+ // Pieces free to twist / flip; the last of them absorbs the sum rule.
65
+ const turnable = free.filter((pos) => !oriented(cubieOf(perm[pos])));
66
+ for (const pos of turnable)
67
+ ori[pos] = Math.floor(random() * twists);
68
+ if (turnable.length) {
69
+ const last = turnable[turnable.length - 1];
70
+ const rest = ori.reduce((a, b, i) => (i === last ? a : a + b), 0);
71
+ ori[last] = (twists - (rest % twists)) % twists;
72
+ }
73
+ }
74
+ return { perm, ori, free };
75
+ };
76
+ const corners = place("corner", 8, cornerCubie, 3);
77
+ const edges = place("edge", 12, edgeCubie, 2);
78
+ // Permutation parities must match: swap two free edges (or corners) if they don't.
79
+ if (parity(corners.perm) !== parity(edges.perm)) {
80
+ const swap = (p) => {
81
+ const [a, b] = p.free;
82
+ [p.perm[a], p.perm[b]] = [p.perm[b], p.perm[a]];
83
+ [p.ori[a], p.ori[b]] = [p.ori[b], p.ori[a]];
84
+ };
85
+ if (edges.free.length >= 2)
86
+ swap(edges);
87
+ else if (corners.free.length >= 2)
88
+ swap(corners);
89
+ }
90
+ return fromCubies({ frame: IDENTITY_FRAME, cp: corners.perm, co: corners.ori, ep: edges.perm, eo: edges.ori });
91
+ }
92
+ // ─── presets (canonical: first layer D, last layer U; Roux blocks on L/R) ───
93
+ const y = (c) => c.pos[1];
94
+ const isCrossEdge = (c) => c.kind === "edge" && y(c) === -1;
95
+ const inF2L = (c) => y(c) <= 0;
96
+ const inFR = (c) => c.pos[0] === 1 && c.pos[2] === 1 && y(c) <= 0;
97
+ export const SCRAMBLE_PRESETS = {
98
+ full: { label: "Random state" },
99
+ f2l: { label: "F2L (cross solved)", solved: isCrossEdge },
100
+ ls: { label: "Last slot (FR) + last layer", solved: (c) => inF2L(c) && !inFR(c) },
101
+ ll: { label: "Last layer (OLL + PLL)", solved: inF2L },
102
+ zbll: { label: "ZBLL (last-layer edges oriented)", solved: inF2L, oriented: (c) => c.kind === "edge" },
103
+ pll: { label: "PLL (last layer oriented)", solved: inF2L, oriented: () => true },
104
+ ell: { label: "ELL (corners solved)", solved: (c) => inF2L(c) || c.kind === "corner" },
105
+ cmll: { label: "CMLL (Roux blocks solved)", solved: (c) => c.pos[0] !== 0 && y(c) <= 0 },
106
+ };
107
+ let shared = null;
108
+ /** The shared solver (tables built on first call, about half a second). */
109
+ export const sharedSolver = () => (shared ??= TwoPhase.create());
110
+ /** A random-state scramble: the moves, and the state they lead to (from `from`, default solved). */
111
+ export function randomScramble(options = {}) {
112
+ const preset = SCRAMBLE_PRESETS[options.preset ?? "full"];
113
+ for (;;) {
114
+ const base = options.solved ?? preset.solved;
115
+ const extra = options.keep?.length ? piecesPredicate(options.keep) : null;
116
+ const solved = extra ? (c) => extra(c) || (base?.(c) ?? false) : base;
117
+ let target = randomState({ solved, oriented: options.oriented ?? preset.oriented, random: options.random });
118
+ if (options.frame && options.frame !== IDENTITY_FRAME)
119
+ target = unreframe(target, options.frame);
120
+ const result = scrambleTo(target, options);
121
+ if (result)
122
+ return result;
123
+ }
124
+ }
125
+ /** Moves from `options.from` (default solved) to `target`; null if the solver gave up or there is nothing to do. */
126
+ export function scrambleTo(target, options = {}) {
127
+ const solver = options.solver ?? sharedSolver();
128
+ const from = options.from ?? solvedState();
129
+ const moves = solver.solveBetween(from, target, options);
130
+ if (!moves || !moves.length)
131
+ return null;
132
+ return { moves, state: applyMoves(from, moves) };
133
+ }
134
+ /**
135
+ * A trainer scramble: a random-state scramble whose `stage` (cross, xcross,
136
+ * EOCross…) takes exactly `length` moves at best — on any face (`frame`),
137
+ * from wherever the cube is (`from`). Null if no such case was found.
138
+ */
139
+ export function stageScramble(options) {
140
+ const def = options.stage;
141
+ if (isLseStage(def)) {
142
+ // Last six edges: a case from the exact table (blocks and CMLL solved), put on the requested face.
143
+ const lse = lseSolver(def);
144
+ for (let attempt = 0; attempt < 20; attempt++) {
145
+ const target = lse.sampleState(options.length, options.random, options.frame);
146
+ if (!target)
147
+ return null;
148
+ const result = scrambleTo(target, options);
149
+ if (result && lse.distance(result.state, { frame: options.frame }) === options.length)
150
+ return result;
151
+ }
152
+ return null;
153
+ }
154
+ const solver = stageSolver(def);
155
+ // A rare level can miss once; a level that doesn't exist misses every time — give up after three.
156
+ let misses = 0;
157
+ for (let attempt = 0; attempt < 20; attempt++) {
158
+ let target;
159
+ if (def.neutral) {
160
+ // The best block depends on pieces outside the tracked set: draw whole states.
161
+ const s = solver.sampleState(options.length, options.random);
162
+ if (!s) {
163
+ if (++misses >= 3)
164
+ return null;
165
+ continue;
166
+ }
167
+ target = s;
168
+ }
169
+ else {
170
+ const place = solver.sample(options.length, options.random);
171
+ if (!place) {
172
+ if (++misses >= 3)
173
+ return null;
174
+ continue;
175
+ }
176
+ target = randomState({ place, random: options.random });
177
+ }
178
+ if (options.frame && options.frame !== IDENTITY_FRAME)
179
+ target = unreframe(target, options.frame);
180
+ const result = scrambleTo(target, options);
181
+ // Defence in depth: the scrambled cube must really be `length` away.
182
+ if (result && solver.distance(result.state, { frame: options.frame }) === options.length)
183
+ return result;
184
+ }
185
+ return null;
186
+ }
187
+ setRandomStateHook((random) => randomState({ random }));
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Optimal solvers for solving stages: cross, xcross, xxcross, EOCross, an F2L
3
+ * slot, the Roux first block — any set of pieces (plus, optionally, the
4
+ * orientation of every edge). Answers: how many moves (`distance`), every
5
+ * optimal solution (`solve`), the optimal next moves from here (`nextMoves`),
6
+ * and random placements at an exact distance (`sample`, for trainer
7
+ * scrambles: "a cross that takes exactly 6").
8
+ *
9
+ * Canonical frame: cross on D, slots FR / FL / BL / BR, Roux first block on
10
+ * L-bottom. Pass `frame` to work on another face (`frameFor("U")` = cross
11
+ * on U) — colour neutral like the rest of cubecore.
12
+ *
13
+ * How: IDA* over the tracked pieces. Each piece is one number (position ×
14
+ * orientations + orientation, 0..23); the heuristic is the max of exact
15
+ * distance tables for groups of up to five pieces (built by breadth-first
16
+ * search on first use, shared between stages — the cross + one slot piece
17
+ * tables are ~8 MB each and take a few hundred ms).
18
+ */
19
+ import { type Frame, type Move, type Piece, type StageDef, type State } from "../core/index.js";
20
+ export declare const setRandomStateHook: (f: (random: () => number) => State) => undefined;
21
+ export type { Piece, StageDef } from "../core/index.js";
22
+ export type Slot = "FR" | "FL" | "BL" | "BR";
23
+ export declare const STAGES: {
24
+ readonly cross: () => StageDef;
25
+ readonly eocross: () => StageDef;
26
+ readonly xcross: (slot?: Slot) => StageDef;
27
+ readonly xxcross: (a?: Slot, b?: Slot) => StageDef;
28
+ /** One F2L slot on its own (edge + corner in place). */
29
+ readonly slot: (slot?: Slot) => StageDef;
30
+ /** Roux first block: DL, FL, BL edges and the DLF, DBL corners. */
31
+ readonly "roux-fb": () => StageDef;
32
+ /**
33
+ * Both Roux blocks (first + second: DR, FR, BR edges, DFR, DRB corners) —
34
+ * for second-block practice from a solved first block. From a full
35
+ * scramble the optimum is long (~15+) and the search slow.
36
+ */
37
+ readonly "roux-blocks": () => StageDef;
38
+ };
39
+ export interface StageSolveOptions {
40
+ /** Hold the cube this way (canonical D → frame.face.D). Default: identity. */
41
+ frame?: Frame;
42
+ /** Longest solution looked for. Default 20. */
43
+ maxDepth?: number;
44
+ /** All optimal solutions (up to `limit`) instead of the first. */
45
+ all?: boolean;
46
+ limit?: number;
47
+ }
48
+ export declare class StageSolver {
49
+ readonly def: StageDef;
50
+ private readonly tables;
51
+ private readonly goal;
52
+ /** Goal placements (values of all pieces), when the stage has several (`def.goals`). */
53
+ private readonly goalSet;
54
+ /** Move indices the search may use (`def.moves`; the tables stay the all-moves ones — still a valid lower bound). */
55
+ private readonly allowed;
56
+ constructor(def: StageDef);
57
+ private isGoal;
58
+ /** Values of the tracked pieces (and flip) in `state`, seen through `frame`. */
59
+ private read;
60
+ private h;
61
+ /** Solutions of length exactly `depth` from values (all, or stop at the first). */
62
+ private search;
63
+ private optimal;
64
+ /**
65
+ * The frames to try: `frame`, or for an x-neutral stage every frame with
66
+ * the same L and R (the block on the L colour with any bottom colour).
67
+ */
68
+ private frames;
69
+ private distanceIn;
70
+ /** Fewest moves to finish the stage (−1 if not found within maxDepth). An x-neutral stage takes the best block. */
71
+ distance(state: State, options?: StageSolveOptions): number;
72
+ /** Optimal solutions (face turns, in the cube's own frame); for an x-neutral stage, those of the best blocks. */
73
+ solve(state: State, options?: StageSolveOptions): Move[][];
74
+ /**
75
+ * A whole random state whose stage takes exactly `depth` moves — for
76
+ * x-neutral stages, where the best block depends on pieces outside the
77
+ * tracked set. Uniform where levels are common; short levels come from
78
+ * random walks, the rarest deep ones one move beyond a shallower case.
79
+ */
80
+ sampleState(depth: number, random?: () => number, draw?: (random: () => number) => State, attempts?: number): State | null;
81
+ /** The first moves of every optimal solution — "what's a best next move from here?" */
82
+ nextMoves(state: State, options?: StageSolveOptions): Move[];
83
+ /**
84
+ * A random placement of the tracked pieces that takes exactly `depth` moves
85
+ * (canonical frame): piece → value (pos × orientations + ori), plus the
86
+ * flip coordinate of all edges for EO stages. Uniform rejection sampling;
87
+ * very short depths fall back to random walks.
88
+ */
89
+ sample(depth: number, random?: () => number, attempts?: number): Placement | null;
90
+ }
91
+ /** Orientation of the edge at each position (0..11) for a flip coordinate. */
92
+ export declare function flipBits(flip: number): number[];
93
+ /** Where some pieces are: piece → value (pos × 2 + ori for edges, pos × 3 + ori for corners); `flip` = orientation of all edges. */
94
+ export interface Placement {
95
+ pieces: Map<Piece, number>;
96
+ flip?: number;
97
+ }
98
+ /** Somewhere to keep distance tables between sessions (IndexedDB in browsers — see indexedDbTableStore). */
99
+ export interface TableStore {
100
+ get(key: string): Promise<Int8Array | null>;
101
+ set(key: string, table: Int8Array): Promise<void>;
102
+ }
103
+ /** Load the tables these stages need from `store`, or build and save them. Afterwards the solvers start instantly. */
104
+ export declare function preloadStageTables(stages: readonly StageDef[], store: TableStore): Promise<void>;
105
+ /** A TableStore in IndexedDB (works in pages and workers). */
106
+ export declare function indexedDbTableStore(dbName?: string): TableStore;
107
+ /** A shared solver for a stage definition (tables are shared anyway). */
108
+ export declare function stageSolver(def: StageDef): StageSolver;