@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/skin/skin.js ADDED
@@ -0,0 +1,412 @@
1
+ /**
2
+ * Skin — how the cube looks, as plain data (serialisable, presets below).
3
+ * Everything the renderer draws comes from here, per theme.
4
+ */
5
+ /** The skin as it should look on a `theme` page (its `themes` overrides applied). */
6
+ export function themed(skin, theme) {
7
+ const t = skin.themes?.[theme];
8
+ if (!t)
9
+ return skin;
10
+ return {
11
+ ...skin,
12
+ ...(t.body !== undefined ? { body: t.body } : {}),
13
+ ...(t.background !== undefined ? { background: t.background } : {}),
14
+ mask: { ...skin.mask, ...t.mask },
15
+ hints: { ...skin.hints, ...t.hints },
16
+ };
17
+ }
18
+ /** Back-sticker colour: the hint palette if the skin has one, else the sticker's own. */
19
+ export function hintColor(skin, colorClass, state) {
20
+ const own = skin.hints.colors?.[colorClass];
21
+ return own && state === "regular" ? own : stickerColor(skin, colorClass, state);
22
+ }
23
+ const WESTERN = ["#ffffff", "#e8322f", "#1fb24a", "#ffd500", "#ff8a00", "#1e5eff"];
24
+ /**
25
+ * QiYi SC tile outlines (corner, edge), measured from the plates of the cube
26
+ * model in QiYi's app: rounded corners, a big round towards the face centre
27
+ * and the edge tiles' long curve there. Stickerless — outer sides run to the
28
+ * cube edge.
29
+ */
30
+ /**
31
+ * QiYi's stickered cubes, measured from the models in QiYi's app ("black
32
+ * rounded" / "black square"): flat stickers on black pedestals, ~0.87–0.89 of
33
+ * a face, kept 0.21 from the cube's edges; a big round on a corner sticker
34
+ * at the cube's corner; round centre stickers on a domed centre. The two
35
+ * differ only in how round the body is: at the edges 0.24 / 0.15, at the
36
+ * corners 0.37 / 0.22.
37
+ */
38
+ export const QIYI_ROUND_STICKERS = {
39
+ size: 0.892,
40
+ centerSize: 0.86,
41
+ margin: 0.211,
42
+ shape: { corner: { inner: 0.06, outer: 0.06 }, edge: { inner: 0.2, outer: 0.06 }, center: 0.5 },
43
+ cornerRadius: 0.3,
44
+ edgeRadius: 0.24,
45
+ cornerRound: 0.37,
46
+ centerDome: { flat: 0.65, drop: 0.05 },
47
+ pedestal: true,
48
+ body: "#2e2e2e",
49
+ // The plastic under them: a round-cornered centre, edges rounded towards it almost like their stickers.
50
+ base: { corner: { inner: 0.14, outer: 0.03 }, edge: { inner: 0.28, outer: 0.03 }, center: 0.45 },
51
+ };
52
+ /** The square one: the same stickers and pieces, only less rounded at the cube's edges and corners. */
53
+ export const QIYI_SQUARE_STICKERS = { ...QIYI_ROUND_STICKERS, edgeRadius: 0.15, cornerRound: 0.22 };
54
+ const QIYI_SC_PATHS = {
55
+ corner: "M0 0.499 L0 0 L0.950 0 L0.978 0.004 L0.993 0.015 L1 0.033 L1 0.856 L0.992 0.899 L0.982 0.926 L0.959 0.959 L0.931 0.979 L0.899 0.992 L0.856 1 L0.033 1 L0.015 0.993 L0.004 0.978 L0 0.950 Z",
56
+ edge: "M1 0.499 L1 0.782 L0.995 0.810 L0.979 0.850 L0.957 0.879 L0.939 0.898 L0.902 0.927 L0.847 0.955 L0.809 0.968 L0.718 0.988 L0.610 1 L0.393 1 L0.253 0.983 L0.214 0.975 L0.152 0.955 L0.092 0.924 L0.060 0.897 L0.039 0.875 L0.020 0.849 L0 0.788 L0 0.031 L0.013 0.010 L0.044 0 L0.954 0 L0.987 0.009 L1 0.031 Z",
57
+ };
58
+ /**
59
+ * Presets are tuned for dark pages; on light ones the greys of masked
60
+ * stickers go lighter and a white back sticker becomes blue-grey (on white it
61
+ * would vanish — cubing.js #394).
62
+ */
63
+ const LIGHT_PAGE = {
64
+ mask: { ignored: "#c4c7cd" },
65
+ hints: { opacity: 0.85, ignoredOpacity: 0.55, colors: ["#6f7b8a", "#e8322f", "#1fb24a", "#e0bb00", "#ff8a00", "#1e5eff"] },
66
+ };
67
+ /**
68
+ * Earlier skins, kept for reference (not in SKINS): the first GAN look, the
69
+ * i4 (with its holes — `default` is built from it), the GAN 356 M measured
70
+ * from a 3D model (`gan` / `ganStickers` are built from it), and the first
71
+ * generic looks `standard` (black plastic, stickers) and `stickerless`.
72
+ */
73
+ export const ARCHIVED_SKINS = {
74
+ /**
75
+ * GAN-style stickerless: tiles almost fill each cubie face; corner tiles
76
+ * round off the corner facing the centre, edge tiles round their inner side
77
+ * into a tongue, centre tiles are nearly round. Add a logo as a decal.
78
+ */
79
+ gan: {
80
+ body: "#0a0a0a",
81
+ cubieSize: 0.99,
82
+ cubieRadius: 0.06,
83
+ bodyInset: 0.02,
84
+ pieces: { depth: 0.3, taper: 0.07, core: 0.6 },
85
+ stickers: {
86
+ colors: ["#f7f7f5", "#f5303a", "#1fc25a", "#ffe01a", "#ff8a1f", "#1f73ea"],
87
+ size: 0.97,
88
+ radius: 0.08,
89
+ shape: { corner: { inner: 0.34, outer: 0.07 }, edge: { inner: 0.3, outer: 0.07 }, center: 0.36 },
90
+ thickness: 0.035,
91
+ bevel: 0.016,
92
+ edgeRadius: 0.03,
93
+ material: "plastic",
94
+ fillOuter: true,
95
+ },
96
+ mask: { ignored: "#5a5a5a", oriented: "#39c7d4", dimAmount: 0.5 },
97
+ hints: { enabled: false, distance: 1.4, opacity: 0.75, ignoredOpacity: 0.35 },
98
+ background: null,
99
+ themes: { light: LIGHT_PAGE },
100
+ },
101
+ /**
102
+ * GAN i4-style smart cube (from product photos): light translucent-grey
103
+ * internals showing in minimal gaps, thick matte tiles with soft edges, the
104
+ * cube's edges rounded so colour runs round them, squarish centres with
105
+ * four adjustment holes. The brand logo is up to the app (a decal).
106
+ */
107
+ ganI4: {
108
+ body: "#cfd3d9",
109
+ cubieSize: 0.992,
110
+ cubieRadius: 0.07,
111
+ bodyInset: 0.02,
112
+ pieces: { depth: 0.3, taper: 0.07, core: 0.6 },
113
+ stickers: {
114
+ colors: ["#f3f2ee", "#f2323d", "#24c95c", "#ffe03a", "#ff7b22", "#2d6cf0"],
115
+ size: 0.978,
116
+ radius: 0.1,
117
+ shape: { corner: { inner: 0.18, outer: 0.06 }, edge: { inner: 0.22, outer: 0.06 }, center: 0.22 },
118
+ thickness: 0.04,
119
+ bevel: 0.018,
120
+ edgeRadius: 0.05,
121
+ material: "plastic",
122
+ roughness: 0.55,
123
+ fillOuter: true,
124
+ },
125
+ // Tension-adjustment holes near the corners of every centre cap.
126
+ features: [{ select: { kinds: ["center"] }, type: "holes", params: { radius: 0.045, at: [[0.64, 0.64], [-0.64, 0.64], [-0.64, -0.64], [0.64, -0.64]] } }],
127
+ mask: { ignored: "#6a6d72", oriented: "#39c7d4", dimAmount: 0.5 },
128
+ hints: { enabled: false, distance: 1.4, opacity: 0.75, ignoredOpacity: 0.35 },
129
+ background: null,
130
+ themes: { light: LIGHT_PAGE },
131
+ },
132
+ /**
133
+ * GAN 356 M (stickerless): square corner tiles, edge tiles with a round
134
+ * "tongue" towards the centre, near-round centres; thin glossy tiles.
135
+ * Shapes, sizes and colours measured from "GAN CUBE 356s M air" by Amyyu
136
+ * (https://sketchfab.com/3d-models/gan-cube-356s-m-air-dd4768b8fe2841c78e418230e5d9e192,
137
+ * CC BY 4.0); smoothed after the cube in GAN's app. No logo — that's up to the app (a decal).
138
+ */
139
+ gan356m: {
140
+ body: "#1e2023",
141
+ // The pieces nearly touch; the core is hidden (sharp is fine), a mechanism ball fills the middle.
142
+ cubieSize: 0.998,
143
+ cubieRadius: 0.01,
144
+ bodyInset: 0.015,
145
+ // Black plastic under the tiles, following them (straight walls, then narrowing), corner-cutting reliefs.
146
+ pieces: { depth: 0.3, taper: 0.3, wall: 0.35, relief: { radius: 0.3, depth: 0.12, start: 0.16, tile: 0.01 }, mechanism: 1, core: 0.5, fill: "solid" },
147
+ stickers: {
148
+ colors: ["#fafafa", "#e10b2a", "#008526", "#ffe700", "#f58d1f", "#00319d"],
149
+ size: 0.985,
150
+ radius: 0.015,
151
+ shape: { corner: { inner: 0.016, outer: 0.014 }, edge: { inner: 0.375, outer: 0.015 }, center: 0.285 },
152
+ // Thick tiles with soft rounded edges and a well-rounded cube edge (smooth, as GAN's app draws it).
153
+ thickness: 0.04,
154
+ bevel: 0.034,
155
+ kinds: { center: { size: 0.979, thickness: 0.05, bevel: 0.04 } }, // the centre cap: a little smaller, standing out more
156
+ edgeRadius: 0.14,
157
+ material: "plastic",
158
+ roughness: 0.3,
159
+ fillOuter: true,
160
+ },
161
+ // GAN's stickers (from the sticker atlas in GAN's app): rounded squares, edges with a round tongue, round centres.
162
+ stickerSet: {
163
+ size: 0.82,
164
+ centerSize: 0.867,
165
+ margin: 0.09,
166
+ shape: { corner: { inner: 0.07, outer: 0.07 }, edge: { inner: 0.4, outer: 0.07 }, center: 0.5 },
167
+ edgeRadius: 0.1,
168
+ },
169
+ mask: { ignored: "#5a5a5a", oriented: "#39c7d4", dimAmount: 0.5 },
170
+ hints: { enabled: false, distance: 1.4, opacity: 0.75, ignoredOpacity: 0.35 },
171
+ background: null,
172
+ themes: { light: LIGHT_PAGE },
173
+ },
174
+ /** Black plastic, rounded stickers — a typical modern speed cube. */
175
+ standard: {
176
+ body: "#101010",
177
+ cubieSize: 0.97,
178
+ cubieRadius: 0.1,
179
+ stickers: { colors: WESTERN, size: 0.86, radius: 0.18 },
180
+ mask: { ignored: "#5a5a5a", oriented: "#39c7d4", dimAmount: 0.5 },
181
+ hints: { enabled: false, distance: 1.4, opacity: 0.75, ignoredOpacity: 0.35 },
182
+ background: null,
183
+ themes: { light: LIGHT_PAGE },
184
+ },
185
+ /** Stickerless look: tiles fill the face, small radius, dark grey core. */
186
+ stickerless: {
187
+ body: "#1c1c1c",
188
+ cubieSize: 0.99,
189
+ cubieRadius: 0.08,
190
+ bodyInset: 0.015,
191
+ stickers: { colors: WESTERN, size: 0.975, radius: 0.1, fillOuter: true, thickness: 0.03, bevel: 0.012, edgeRadius: 0.03, material: "plastic" },
192
+ mask: { ignored: "#4a4a4a", oriented: "#39c7d4", dimAmount: 0.5 },
193
+ hints: { enabled: false, distance: 1.4, opacity: 0.75, ignoredOpacity: 0.35 },
194
+ background: null,
195
+ themes: { light: LIGHT_PAGE },
196
+ },
197
+ };
198
+ const BRANDS = {
199
+ /**
200
+ * The default look: a modern stickerless speed cube, after the GAN i4's
201
+ * pieces (the sample glTF models are exported from it) — thick matte tiles
202
+ * with soft edges, corners towards the centre rounded, squarish centres,
203
+ * pieces moulded in colour. Stickered: `defaultStickers`.
204
+ */
205
+ default: {
206
+ ...ARCHIVED_SKINS.ganI4,
207
+ features: undefined, // plain centres (the i4's tension holes stay with the archived i4)
208
+ body: "#1f1f1f",
209
+ cubieSize: 0.998,
210
+ cubieRadius: 0.01,
211
+ bodyInset: 0.02,
212
+ pieces: { depth: 0.3, taper: 0.3, wall: 0.35, relief: { radius: 0.3, depth: 0.12, start: 0.16, tile: 0.01 }, mechanism: 1, core: 0.5, colored: true, fill: "solid" },
213
+ // The stickered cube: the same shapes, smaller, the plastic under them following their curves.
214
+ stickerSet: { size: 0.84, centerSize: 0.84, margin: 0.08, shape: ARCHIVED_SKINS.ganI4.stickers.shape, edgeRadius: 0.08 },
215
+ },
216
+ /**
217
+ * GAN (stickerless), after the GAN 356 M: square corner tiles, edge tiles
218
+ * with a round tongue towards the centre, near-round centres — thick tiles
219
+ * with soft, well-rounded edges on pieces moulded in colour. No logo —
220
+ * that's up to the app (a decal).
221
+ */
222
+ gan: {
223
+ ...ARCHIVED_SKINS.gan356m,
224
+ body: "#2b2a28",
225
+ pictureBody: null,
226
+ pieces: { ...ARCHIVED_SKINS.gan356m.pieces, colored: true },
227
+ stickers: {
228
+ ...ARCHIVED_SKINS.gan356m.stickers,
229
+ thickness: 0.06,
230
+ bevel: 0.05,
231
+ edgeRadius: 0.16,
232
+ kinds: { center: { size: 0.979, thickness: 0.07, bevel: 0.055 } },
233
+ },
234
+ // The stickered cube: the same shapes as the tiles, smaller, so the black plastic shows round them.
235
+ stickerSet: { size: 0.76, centerSize: 0.8, margin: 0.12, shape: ARCHIVED_SKINS.gan356m.stickers.shape, edgeRadius: 0.1 },
236
+ },
237
+ /**
238
+ * QiYi QY-SC smart cube (from photos): stickerless, moulded in colour
239
+ * (a corner is three coloured parts, an edge two) around an ivory core;
240
+ * thick "pillow" tiles with minimal gaps — corner tiles nearly square,
241
+ * edge tiles rounded towards the centre, a smaller raised squircle
242
+ * centre. The brand logo is up to the app (a decal).
243
+ */
244
+ qiyiSC: {
245
+ // Dark mechanism behind the gaps (as QiYi's app draws it): the tile shapes read, nothing shows through.
246
+ body: "#34322e",
247
+ pictureBody: null, // ivory plastic would blur with the white tiles in 2D
248
+ // Pieces nearly touch: the gaps are hairlines (the core is hidden, so its corners can be sharp).
249
+ cubieSize: 0.998,
250
+ cubieRadius: 0.01,
251
+ bodyInset: 0.02,
252
+ // Pieces solid in colour, following the tile outline: straight walls 0.35 deep, then narrowing towards the
253
+ // middle (as the real pieces); the small core stays hidden, the mechanism ball fills the middle.
254
+ // Corner pieces: behind the tile corner next to the centre a corner-cutting relief, reaching up into the tile
255
+ // (a thin overhanging corner, then the cut) — where real QiYi corners are cut away.
256
+ pieces: { depth: 0.32, taper: 0.35, wall: 0.35, relief: { radius: 0.3, depth: 0.12, start: 0.16, tile: 0.01 }, mechanism: 1, core: 0.5, colored: true, fill: "solid" },
257
+ stickers: {
258
+ colors: ["#ebe8df", "#d8061a", "#0bc21a", "#ffe51c", "#fd7501", "#1a72f5"],
259
+ // Tiles touch; soft, pillowy edges and a well-rounded cube edge (as the real cube).
260
+ size: 0.996,
261
+ radius: 0.04,
262
+ shape: { corner: { inner: 0.14, outer: 0.03 }, edge: { inner: 0.25, outer: 0.03 }, center: 0.28 },
263
+ paths: QIYI_SC_PATHS,
264
+ thickness: 0.07,
265
+ bevel: 0.06,
266
+ // The centre cap, measured from QiYi's app model: a flat circle (radius 0.47) level with the other
267
+ // tiles, nearly touching the sides (a crisp rim there), then a straight slope down to the rounded corners.
268
+ kinds: { center: { size: 0.99, bevel: 0.01, dome: { flat: 0.82, drop: 0.083 } } },
269
+ edgeRadius: 0.2,
270
+ material: "plastic",
271
+ roughness: 0.35,
272
+ fillOuter: true,
273
+ },
274
+ // QiYi's stickers: the round black cube in QiYi's app (see QIYI_SQUARE_STICKERS for the square one).
275
+ stickerSet: QIYI_ROUND_STICKERS,
276
+ mask: { ignored: "#5a5a5a", oriented: "#39c7d4", dimAmount: 0.5 },
277
+ hints: { enabled: false, distance: 1.4, opacity: 0.75, ignoredOpacity: 0.35 },
278
+ background: null,
279
+ themes: { light: LIGHT_PAGE },
280
+ },
281
+ /**
282
+ * MoYu smart cubes (WCU — MY32 and friends): stickerless, moulded in colour;
283
+ * thick tiles, corner tiles nearly square with a rounder corner towards the
284
+ * centre, edge tiles well rounded towards the centre, rounded-square
285
+ * centres. Shapes and colours from the cube model in MoYu's app (WCU CUBE).
286
+ * No logo — that's up to the app (a decal).
287
+ */
288
+ moyu: {
289
+ body: "#2b2a28",
290
+ pictureBody: null,
291
+ cubieSize: 0.998,
292
+ cubieRadius: 0.01,
293
+ bodyInset: 0.02,
294
+ pieces: { depth: 0.32, taper: 0.3, wall: 0.35, relief: { radius: 0.3, depth: 0.12, start: 0.16, tile: 0.01 }, mechanism: 1, core: 0.5, colored: true, fill: "solid" },
295
+ stickers: {
296
+ colors: ["#f7f7f5", "#d50000", "#63dd16", "#ffd600", "#ff6d00", "#2861ff"],
297
+ size: 0.97,
298
+ radius: 0.05,
299
+ shape: { corner: { inner: 0.12, outer: 0.05 }, edge: { inner: 0.3, outer: 0.05 }, center: 0.3 },
300
+ thickness: 0.06,
301
+ bevel: 0.05,
302
+ edgeRadius: 0.15,
303
+ material: "plastic",
304
+ roughness: 0.4,
305
+ fillOuter: true,
306
+ },
307
+ // MoYu's stickers (the stickered cube in MoYu's app): rounded squares, the corners towards the centre rounder.
308
+ // The pieces under them (the stickered cube in MoYu's app): nearly square blocks, the corners towards the
309
+ // centre rounded almost like the stickers, the cube's edges hardly rounded; MoYu's dark grey plastic.
310
+ stickerSet: {
311
+ size: 0.875,
312
+ margin: 0.0625,
313
+ shape: { corner: { inner: 0.25, outer: 0.04 }, edge: { inner: 0.25, outer: 0.04 }, center: 0.25 },
314
+ base: { corner: { inner: 0.32, outer: 0.026 }, edge: { inner: 0.31, outer: 0.026 }, center: 0.32 },
315
+ edgeRadius: 0.03,
316
+ body: "#313131",
317
+ },
318
+ mask: { ignored: "#5a5a5a", oriented: "#39c7d4", dimAmount: 0.5 },
319
+ hints: { enabled: false, distance: 1.4, opacity: 0.75, ignoredOpacity: 0.35 },
320
+ background: null,
321
+ themes: { light: LIGHT_PAGE },
322
+ },
323
+ };
324
+ export const SKINS = {
325
+ ...BRANDS,
326
+ /** GAN, stickered: GAN's stickers (rounded squares, edges with a round tongue, round centres), flat prints on black. */
327
+ ganStickers: withStickers(BRANDS.gan, "flat"),
328
+ /** MoYu, stickered: MoYu's stickers on black. */
329
+ moyuStickers: withStickers(BRANDS.moyu, "thin"),
330
+ /** The default look, stickered: its shapes as stickers on black plastic that follows their curves. */
331
+ defaultStickers: withStickers(BRANDS.default, "thin"),
332
+ /** QiYi, stickered — the round black cube ("black rounded" in QiYi's app). */
333
+ qiyiStickersRounded: withStickers(BRANDS.qiyiSC, "raised", { set: QIYI_ROUND_STICKERS }),
334
+ /** QiYi, stickered — the square black cube ("black square" in QiYi's app). */
335
+ qiyiStickersSquare: withStickers(BRANDS.qiyiSC, "raised", { set: QIYI_SQUARE_STICKERS }),
336
+ };
337
+ /** Tile side of a piece kind, relative to a cubie face (`stickers.kinds` or `stickers.size`). */
338
+ export const tileSize = (skin, kind) => skin.stickers.kinds?.[kind]?.size ?? skin.stickers.size;
339
+ /** How far a piece kind's tiles stand out (`stickers.kinds` or `stickers.thickness`). */
340
+ export const tileThickness = (skin, kind) => skin.stickers.kinds?.[kind]?.thickness ?? skin.stickers.thickness ?? 0;
341
+ export function stickerColor(skin, colorClass, state) {
342
+ const base = skin.stickers.colors[colorClass];
343
+ switch (state) {
344
+ case "regular":
345
+ return base;
346
+ case "dim":
347
+ return mix(base, skin.mask.ignored, skin.mask.dimAmount);
348
+ case "ignored":
349
+ return skin.mask.ignored;
350
+ case "oriented":
351
+ return skin.mask.oriented;
352
+ case "invisible":
353
+ return null;
354
+ }
355
+ }
356
+ export function mix(hex, towards, amount) {
357
+ const p = (h) => [1, 3, 5].map((i) => parseInt(h.slice(i, i + 2), 16));
358
+ const a = p(hex), b = p(towards);
359
+ return "#" + a.map((v, i) => Math.round(v + (b[i] - v) * amount).toString(16).padStart(2, "0")).join("");
360
+ }
361
+ /** The same skin with other sticker colours (U R F D L B home-face order) — e.g. a Japanese scheme or a custom one. */
362
+ export function withColors(skin, colors) {
363
+ return { ...skin, stickers: { ...skin.stickers, colors } };
364
+ }
365
+ /**
366
+ * The stickered version of a skin: a black, rounded body with the brand's
367
+ * stickers (`skin.stickerSet`, else the skin's own shapes, at most 0.88 of a face)
368
+ * on top — `raised` (thick, rounded), `thin` (like a real sticker) or `flat`
369
+ * (a print). `options.set`: another sticker set of the brand (e.g. QiYi's
370
+ * square cube). Colours, logos and the rest stay.
371
+ */
372
+ export function withStickers(skin, style = "thin", options = {}) {
373
+ const styles = {
374
+ raised: { thickness: 0.03, bevel: 0.012 },
375
+ thin: { thickness: 0.015, bevel: 0.005 },
376
+ flat: { thickness: 0.002, bevel: 0 },
377
+ };
378
+ const own = skin.stickers.shape ?? { corner: { inner: skin.stickers.radius, outer: skin.stickers.radius }, edge: { inner: skin.stickers.radius, outer: skin.stickers.radius }, center: skin.stickers.radius };
379
+ const size = Math.min(skin.stickers.size, 0.88);
380
+ const set = options.set ?? skin.stickerSet ?? { size, margin: (1 - size) / 2, shape: own };
381
+ const edgeRadius = set.edgeRadius ?? 0.1;
382
+ const platform = 0.03;
383
+ return {
384
+ ...skin,
385
+ body: set.body ?? "#222222",
386
+ pictureBody: undefined,
387
+ // The black body: flat faces nearly touching, rounded only at the cube's edges (deep enough for the round).
388
+ cubieSize: 0.998,
389
+ cubieRadius: 0.01,
390
+ bodyInset: Math.max(0.01, (1 - Math.SQRT1_2) * edgeRadius - platform + 0.005),
391
+ pieces: { depth: 0.3, taper: 0.3, wall: 0.35, mechanism: 1, core: 0.5, fill: "solid" },
392
+ stickers: {
393
+ ...skin.stickers,
394
+ size: 0.995,
395
+ // The plastic under the stickers follows their curves (as on real stickered cubes) unless the set says otherwise.
396
+ shape: set.base ?? set.shape,
397
+ paths: undefined,
398
+ kinds: set.centerDome ? { center: { dome: set.centerDome } } : undefined,
399
+ thickness: platform,
400
+ bevel: 0.012,
401
+ edgeRadius,
402
+ cornerRound: set.cornerRound,
403
+ fillOuter: true,
404
+ material: "plastic",
405
+ overlay: { ...set, ...styles[style] },
406
+ },
407
+ };
408
+ }
409
+ /** The same skin with another finish on its tiles / stickers: `matte`, `uv` (a glossy coat) or the skin's own (undefined). */
410
+ export function withFinish(skin, finish) {
411
+ return { ...skin, stickers: { ...skin.stickers, finish } };
412
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The solvers in a Web Worker, with a Promise API — the main thread never
3
+ * waits for table building (two-phase ~0.5 s, xcross tables ~3 s once).
4
+ *
5
+ * const solver = createSolverWorker();
6
+ * await solver.warmUp([STAGES.xcross("FR")]); // optional: build early
7
+ * const { moves } = await solver.stageScramble({ stage: STAGES.cross(), length: 6, frame: frameFor("U"), from: cube.state });
8
+ *
9
+ * Requests are answered in order; call `terminate()` when done.
10
+ */
11
+ import type { Frame, Move, State } from "../core/index.js";
12
+ import type { ScrambleResult } from "./protocol.js";
13
+ import type { ScramblePreset } from "./scramble.js";
14
+ import type { AnyStageDef as StageDef, Piece } from "../core/index.js";
15
+ interface Common {
16
+ frame?: Frame;
17
+ from?: State;
18
+ seed?: number;
19
+ maxLength?: number;
20
+ timeoutMs?: number;
21
+ }
22
+ export interface SolverClient {
23
+ warmUp(stages?: StageDef[]): Promise<boolean>;
24
+ solve(state: State, options?: {
25
+ maxLength?: number;
26
+ timeoutMs?: number;
27
+ }): Promise<Move[] | null>;
28
+ solveBetween(from: State, to: State, options?: {
29
+ maxLength?: number;
30
+ }): Promise<Move[] | null>;
31
+ randomScramble(options?: Common & {
32
+ preset?: ScramblePreset;
33
+ keep?: Piece[];
34
+ }): Promise<ScrambleResult>;
35
+ stageScramble(options: Common & {
36
+ stage: StageDef;
37
+ length: number;
38
+ }): Promise<ScrambleResult | null>;
39
+ stageSolve(stage: StageDef, state: State, options?: {
40
+ frame?: Frame;
41
+ all?: boolean;
42
+ limit?: number;
43
+ }): Promise<Move[][]>;
44
+ stageDistance(stage: StageDef, state: State, options?: {
45
+ frame?: Frame;
46
+ }): Promise<number>;
47
+ terminate(): void;
48
+ }
49
+ /** Wrap a worker (or anything that speaks the protocol) in the Promise API. */
50
+ export declare function solverClient(worker: Worker): SolverClient;
51
+ /**
52
+ * Start the solver worker. By default bundlers (Vite, webpack 5, Bun build)
53
+ * pick up the worker from `new URL("./worker.js", import.meta.url)`; pass
54
+ * `url` to load a worker you built yourself (e.g. a dev server that doesn't
55
+ * rewrite worker URLs — see demo/serve.ts).
56
+ */
57
+ export declare function createSolverWorker(url?: string | URL): SolverClient;
58
+ export {};
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The solvers in a Web Worker, with a Promise API — the main thread never
3
+ * waits for table building (two-phase ~0.5 s, xcross tables ~3 s once).
4
+ *
5
+ * const solver = createSolverWorker();
6
+ * await solver.warmUp([STAGES.xcross("FR")]); // optional: build early
7
+ * const { moves } = await solver.stageScramble({ stage: STAGES.cross(), length: 6, frame: frameFor("U"), from: cube.state });
8
+ *
9
+ * Requests are answered in order; call `terminate()` when done.
10
+ */
11
+ /** Wrap a worker (or anything that speaks the protocol) in the Promise API. */
12
+ export function solverClient(worker) {
13
+ let nextId = 1;
14
+ const pending = new Map();
15
+ worker.onmessage = (e) => {
16
+ const p = pending.get(e.data.id);
17
+ if (!p)
18
+ return;
19
+ pending.delete(e.data.id);
20
+ if (e.data.ok)
21
+ p.resolve(e.data.value);
22
+ else
23
+ p.reject(new Error(e.data.error));
24
+ };
25
+ const call = (request) => new Promise((resolve, reject) => {
26
+ const id = nextId++;
27
+ pending.set(id, { resolve: resolve, reject });
28
+ worker.postMessage({ id, request });
29
+ });
30
+ const common = (o = {}) => ({ frameId: o.frame?.id, from: o.from, seed: o.seed, maxLength: o.maxLength, timeoutMs: o.timeoutMs });
31
+ return {
32
+ warmUp: (stages = []) => call({ op: "warmUp", stages }),
33
+ solve: (state, o = {}) => call({ op: "solve", state, ...o }),
34
+ solveBetween: (from, to, o = {}) => call({ op: "solveBetween", from, to, ...o }),
35
+ randomScramble: (o = {}) => call({ op: "randomScramble", preset: o.preset, keep: o.keep, ...common(o) }),
36
+ stageScramble: (o) => call({ op: "stageScramble", stage: o.stage, length: o.length, ...common(o) }),
37
+ stageSolve: (stage, state, o = {}) => call({ op: "stageSolve", stage, state, frameId: o.frame?.id, all: o.all, limit: o.limit }),
38
+ stageDistance: (stage, state, o = {}) => call({ op: "stageDistance", stage, state, frameId: o.frame?.id }),
39
+ terminate: () => {
40
+ worker.terminate();
41
+ for (const p of pending.values())
42
+ p.reject(new Error("Solver worker terminated"));
43
+ pending.clear();
44
+ },
45
+ };
46
+ }
47
+ /**
48
+ * Start the solver worker. By default bundlers (Vite, webpack 5, Bun build)
49
+ * pick up the worker from `new URL("./worker.js", import.meta.url)`; pass
50
+ * `url` to load a worker you built yourself (e.g. a dev server that doesn't
51
+ * rewrite worker URLs — see demo/serve.ts).
52
+ */
53
+ export function createSolverWorker(url) {
54
+ // Written out in full: bundlers only recognise the literal `new Worker(new URL(…), { type: "module" })`.
55
+ return solverClient(url ? new Worker(url, { type: "module" }) : new Worker(new URL("./worker.js", import.meta.url), { type: "module" }));
56
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Runs one request — the same code in a worker (worker.ts) and on the main
3
+ * thread (for tests, or apps that don't mind the wait).
4
+ */
5
+ import { type Request } from "./protocol.js";
6
+ export declare function handle(r: Request): Promise<unknown>;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Runs one request — the same code in a worker (worker.ts) and on the main
3
+ * thread (for tests, or apps that don't mind the wait).
4
+ */
5
+ import { FRAMES, isLseStage } from "../core/index.js";
6
+ import { lseSolver } from "./lse.js";
7
+ import { seededRandom } from "./protocol.js";
8
+ import { randomScramble, sharedSolver, stageScramble } from "./scramble.js";
9
+ import { indexedDbTableStore, preloadStageTables, stageSolver } from "./stage.js";
10
+ /** Tables kept across sessions when the environment has IndexedDB (browsers, workers). */
11
+ const store = typeof indexedDB !== "undefined" ? indexedDbTableStore() : null;
12
+ export async function handle(r) {
13
+ const frame = "frameId" in r && r.frameId !== undefined ? FRAMES[r.frameId] : undefined;
14
+ const random = "seed" in r && r.seed !== undefined ? seededRandom(r.seed) : undefined;
15
+ switch (r.op) {
16
+ case "warmUp":
17
+ sharedSolver();
18
+ if (store)
19
+ await preloadStageTables(r.stages.filter((s) => !isLseStage(s)), store);
20
+ for (const s of r.stages)
21
+ solverFor(s);
22
+ return true;
23
+ case "solve":
24
+ return sharedSolver().solve(r.state, r);
25
+ case "solveBetween":
26
+ return sharedSolver().solveBetween(r.from, r.to, r);
27
+ case "randomScramble":
28
+ return randomScramble({ preset: r.preset, keep: r.keep, frame, from: r.from, random, maxLength: r.maxLength, timeoutMs: r.timeoutMs });
29
+ case "stageScramble":
30
+ return stageScramble({ stage: r.stage, length: r.length, frame, from: r.from, random, maxLength: r.maxLength, timeoutMs: r.timeoutMs });
31
+ case "stageSolve":
32
+ return solverFor(r.stage).solve(r.state, { frame, all: r.all, limit: r.limit });
33
+ case "stageDistance":
34
+ return solverFor(r.stage).distance(r.state, { frame });
35
+ }
36
+ }
37
+ /** The solver for a stage: piece stages (cross, blocks…) or last-six-edges stages (EOLR). */
38
+ const solverFor = (def) => (isLseStage(def) ? lseSolver(def) : stageSolver(def));
@@ -0,0 +1,8 @@
1
+ export * from "./twophase.js";
2
+ export * from "./scramble.js";
3
+ export * from "./stage.js";
4
+ export * from "./client.js";
5
+ export { handle as solveRequest } from "./handle.js";
6
+ export { seededRandom } from "./protocol.js";
7
+ export type { Request as SolveRequest, ScrambleResult } from "./protocol.js";
8
+ export * from "./lse.js";
package/solve/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export * from "./twophase.js";
2
+ export * from "./scramble.js";
3
+ export * from "./stage.js";
4
+ export * from "./client.js";
5
+ export { handle as solveRequest } from "./handle.js";
6
+ export { seededRandom } from "./protocol.js";
7
+ export * from "./lse.js";
package/solve/lse.d.ts ADDED
@@ -0,0 +1,42 @@
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 { type Frame, type LseStageDef, type Move, type State } from "../core/index.js";
14
+ export interface LseSolveOptions {
15
+ /** Hold the cube this way (colour neutral). */
16
+ frame?: Frame;
17
+ all?: boolean;
18
+ limit?: number;
19
+ }
20
+ export declare class LseSolver {
21
+ readonly def: LseStageDef;
22
+ private readonly key;
23
+ /** feature → distance and one state with those features */
24
+ private readonly table;
25
+ private readonly byDepth;
26
+ constructor(def: LseStageDef);
27
+ /** Deepest level. */
28
+ get maxDepth(): number;
29
+ private canonical;
30
+ /** Fewest M / U moves to the stage (−1 if the blocks and CMLL aren't solved). */
31
+ distance(state: State, options?: LseSolveOptions): number;
32
+ /** Optimal solutions in M and U moves (every one with `all`). */
33
+ solve(state: State, options?: LseSolveOptions): Move[][];
34
+ nextMoves(state: State, options?: LseSolveOptions): Move[];
35
+ /**
36
+ * A case exactly `depth` M / U moves from the stage, as a state a cube
37
+ * can show with its centres home (turned by x when the case has the M
38
+ * slice off) — ready for a scramble. `frame` puts it on another face.
39
+ */
40
+ sampleState(depth: number, random?: () => number, frame?: Frame): State | null;
41
+ }
42
+ export declare function lseSolver(def: LseStageDef): LseSolver;