@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,39 @@
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 { stageTimings } from "./timings.js";
9
+ /** The stages of `method` in a recorded solve as segments (skipped stages — no time of their own — left out). */
10
+ export function stageSegments(method, rec) {
11
+ return stageTimings(method, rec)
12
+ .stages.filter((s) => !s.skipped && s.endMs > s.startMs)
13
+ .map((s) => ({
14
+ start: s.startMs,
15
+ end: s.endMs,
16
+ label: s.label,
17
+ id: s.stage,
18
+ ...(s.detail ? { detail: s.detail } : {}),
19
+ ...(s.recognitionMs > 0 ? { split: s.startMs + s.recognitionMs } : {}),
20
+ moves: s.moveCount,
21
+ }));
22
+ }
23
+ /** The segment `time` falls in (index), or −1. */
24
+ export function segmentAt(segments, time) {
25
+ for (let i = 0; i < segments.length; i++) {
26
+ const s = segments[i];
27
+ if (time >= s.start && (time < s.end || (i === segments.length - 1 && time <= s.end)))
28
+ return i;
29
+ }
30
+ return -1;
31
+ }
32
+ /** How much of a segment has played at `time` (0..1). */
33
+ export function segmentPlayed(s, time) {
34
+ if (time <= s.start)
35
+ return 0;
36
+ if (time >= s.end)
37
+ return 1;
38
+ return (time - s.start) / (s.end - s.start);
39
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Share payloads — a whole solve as one URL-safe string, e.g. for a link
3
+ * fragment (`…#s=<payload>`) that a static app can open with no backend.
4
+ * Building and reading the URL is the app's business; this is the codec. Only what can't be recomputed is stored: the recording
5
+ * (scramble, every move with its time, the total) and a few facts about it.
6
+ * Stage splits are NOT stored — the reader derives them (MethodTracker).
7
+ *
8
+ * Payload (version 1), base64url:
9
+ * byte version
10
+ * byte flags: bit0 DNF · bit1 hide times (show move counts only) · bit2 has start state
11
+ * text method id ("cfop", "roux"… or empty) — varint length + UTF-8
12
+ * [11 B] start state (core state codec bytes) when the solve didn't start from `scramble` on a solved cube
13
+ * … the recording (see codec.ts)
14
+ * A typical 100-move solve is ~250 characters.
15
+ */
16
+ import { type State } from "../core/index.js";
17
+ import type { Recording } from "./recording.js";
18
+ export declare const SHARE_VERSION = 1;
19
+ export interface SharedSolve {
20
+ recording: Recording;
21
+ /** Method id the solve was analysed with, e.g. "cfop". */
22
+ method?: string;
23
+ dnf?: boolean;
24
+ /** The sharer showed move counts, not times — a viewer should do the same. */
25
+ hideTimes?: boolean;
26
+ /** State the solve started from, when it isn't `scramble` applied to a solved cube (e.g. a scrambled-by-hand cube). */
27
+ start?: State;
28
+ }
29
+ export declare function encodeShare(solve: SharedSolve): string;
30
+ export declare function decodeShare(text: string): SharedSolve | null;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Share payloads — a whole solve as one URL-safe string, e.g. for a link
3
+ * fragment (`…#s=<payload>`) that a static app can open with no backend.
4
+ * Building and reading the URL is the app's business; this is the codec. Only what can't be recomputed is stored: the recording
5
+ * (scramble, every move with its time, the total) and a few facts about it.
6
+ * Stage splits are NOT stored — the reader derives them (MethodTracker).
7
+ *
8
+ * Payload (version 1), base64url:
9
+ * byte version
10
+ * byte flags: bit0 DNF · bit1 hide times (show move counts only) · bit2 has start state
11
+ * text method id ("cfop", "roux"… or empty) — varint length + UTF-8
12
+ * [11 B] start state (core state codec bytes) when the solve didn't start from `scramble` on a solved cube
13
+ * … the recording (see codec.ts)
14
+ * A typical 100-move solve is ~250 characters.
15
+ */
16
+ import { ByteReader, decodeState, encodeState, fromBase64Url, toBase64Url, writeText } from "../core/index.js";
17
+ import { readRecording, writeRecording } from "./codec.js";
18
+ export const SHARE_VERSION = 1;
19
+ export function encodeShare(solve) {
20
+ const out = [SHARE_VERSION, (solve.dnf ? 1 : 0) | (solve.hideTimes ? 2 : 0) | (solve.start ? 4 : 0)];
21
+ writeText(out, solve.method ?? "");
22
+ if (solve.start) {
23
+ const code = encodeState(solve.start);
24
+ if (!code)
25
+ throw new Error("start is not a reachable cube state");
26
+ out.push(...fromBase64Url(code));
27
+ }
28
+ writeRecording(out, solve.recording);
29
+ return toBase64Url(out);
30
+ }
31
+ export function decodeShare(text) {
32
+ const bytes = fromBase64Url(text);
33
+ if (!bytes)
34
+ return null;
35
+ const r = new ByteReader(bytes);
36
+ if (r.byte() !== SHARE_VERSION)
37
+ return null;
38
+ const flags = r.byte();
39
+ if (flags === null || flags > 7)
40
+ return null;
41
+ const method = r.text(64);
42
+ if (method === null)
43
+ return null;
44
+ let start;
45
+ if (flags & 4) {
46
+ const raw = r.bytes(11);
47
+ const decoded = raw && decodeState(toBase64Url(raw));
48
+ if (!decoded)
49
+ return null;
50
+ start = decoded;
51
+ }
52
+ const recording = readRecording(r);
53
+ if (!recording || !r.done)
54
+ return null;
55
+ return {
56
+ recording,
57
+ ...(method ? { method } : {}),
58
+ ...(flags & 1 ? { dnf: true } : {}),
59
+ ...(flags & 2 ? { hideTimes: true } : {}),
60
+ ...(start ? { start } : {}),
61
+ };
62
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Solve-time statistics, WCA / csTimer style. Times in ms; a DNF is
3
+ * `DNF` (Infinity) — it counts as the worst time, so one DNF in an ao5 is
4
+ * trimmed away, two make the average a DNF.
5
+ *
6
+ * ao(times, 5) // average of the last 5, best and worst dropped
7
+ * bestAo(times, 12) // best rolling average of 12 in the session
8
+ * sessionStats(times) // { count, best, worst, mean, mo3, ao5, ao12, ao100, bestAo5, bestAo12 }
9
+ */
10
+ export declare const DNF: number;
11
+ /** How many times an average of `n` drops at each end: 1 for ao5 / ao12, 5 % above that (ao50: 3, ao100: 5). */
12
+ export declare const trimCount: (n: number) => number;
13
+ /** Average of the `n` times ending at `end` (default: the last n); null if there aren't enough, DNF if too many DNFs. */
14
+ export declare function ao(times: readonly number[], n: number, end?: number): number | null;
15
+ /** Mean of the last `n` (nothing dropped; any DNF makes it a DNF) — mo3. */
16
+ export declare function mo(times: readonly number[], n: number, end?: number): number | null;
17
+ /** Best rolling average of `n` over the whole session. */
18
+ export declare function bestAo(times: readonly number[], n: number): number | null;
19
+ export interface SessionStats {
20
+ count: number;
21
+ /** Solves that weren't DNFs. */
22
+ finished: number;
23
+ best: number | null;
24
+ worst: number | null;
25
+ /** Mean of the finished solves. */
26
+ mean: number | null;
27
+ mo3: number | null;
28
+ ao5: number | null;
29
+ ao12: number | null;
30
+ ao100: number | null;
31
+ bestAo5: number | null;
32
+ bestAo12: number | null;
33
+ }
34
+ export declare function sessionStats(times: readonly number[]): SessionStats;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Solve-time statistics, WCA / csTimer style. Times in ms; a DNF is
3
+ * `DNF` (Infinity) — it counts as the worst time, so one DNF in an ao5 is
4
+ * trimmed away, two make the average a DNF.
5
+ *
6
+ * ao(times, 5) // average of the last 5, best and worst dropped
7
+ * bestAo(times, 12) // best rolling average of 12 in the session
8
+ * sessionStats(times) // { count, best, worst, mean, mo3, ao5, ao12, ao100, bestAo5, bestAo12 }
9
+ */
10
+ export const DNF = Number.POSITIVE_INFINITY;
11
+ /** How many times an average of `n` drops at each end: 1 for ao5 / ao12, 5 % above that (ao50: 3, ao100: 5). */
12
+ export const trimCount = (n) => (n <= 12 ? 1 : Math.ceil(n * 0.05));
13
+ /** Average of the `n` times ending at `end` (default: the last n); null if there aren't enough, DNF if too many DNFs. */
14
+ export function ao(times, n, end = times.length) {
15
+ if (n < 3 || end < n)
16
+ return null;
17
+ const sorted = times.slice(end - n, end).sort((a, b) => a - b);
18
+ const t = trimCount(n);
19
+ const kept = sorted.slice(t, n - t);
20
+ if (kept.some((x) => x === DNF))
21
+ return DNF;
22
+ return kept.reduce((s, x) => s + x, 0) / kept.length;
23
+ }
24
+ /** Mean of the last `n` (nothing dropped; any DNF makes it a DNF) — mo3. */
25
+ export function mo(times, n, end = times.length) {
26
+ if (end < n || n < 1)
27
+ return null;
28
+ const last = times.slice(end - n, end);
29
+ return last.some((x) => x === DNF) ? DNF : last.reduce((s, x) => s + x, 0) / n;
30
+ }
31
+ /** Best rolling average of `n` over the whole session. */
32
+ export function bestAo(times, n) {
33
+ let best = null;
34
+ for (let end = n; end <= times.length; end++) {
35
+ const a = ao(times, n, end);
36
+ if (best === null || a < best)
37
+ best = a;
38
+ }
39
+ return best;
40
+ }
41
+ export function sessionStats(times) {
42
+ const done = times.filter((t) => t !== DNF);
43
+ return {
44
+ count: times.length,
45
+ finished: done.length,
46
+ best: done.length ? Math.min(...done) : null,
47
+ worst: done.length ? Math.max(...done) : null,
48
+ mean: done.length ? done.reduce((s, x) => s + x, 0) / done.length : null,
49
+ mo3: mo(times, 3),
50
+ ao5: ao(times, 5),
51
+ ao12: ao(times, 12),
52
+ ao100: ao(times, 100),
53
+ bestAo5: bestAo(times, 5),
54
+ bestAo12: bestAo(times, 12),
55
+ };
56
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Per-stage timings of a recorded solve for a method, colour neutral
3
+ * (uses core's MethodTracker). For each stage: recognition = pause before its
4
+ * first move, execution = from that move to the stage's completion.
5
+ */
6
+ import { type Method } from "../core/index.js";
7
+ import type { Recording } from "./recording.js";
8
+ export interface StageTiming {
9
+ stage: string;
10
+ label: string;
11
+ /** Index of the first move of this stage and number of moves in it. */
12
+ firstMove: number;
13
+ moveCount: number;
14
+ startMs: number;
15
+ endMs: number;
16
+ recognitionMs: number;
17
+ executionMs: number;
18
+ totalMs: number;
19
+ /** Completed on the same move as the previous stage (or before the solve started). */
20
+ skipped: boolean;
21
+ detail?: string;
22
+ }
23
+ export interface SolveTimings {
24
+ stages: StageTiming[];
25
+ /** Share of the solve spent turning rather than looking (0..1); null if the time is 0. */
26
+ fluency: number | null;
27
+ /** Face the method anchored on (e.g. the cross face), null if no stage was reached. */
28
+ bottomFace: string | null;
29
+ }
30
+ export declare function stageTimings(method: Method, rec: Recording): SolveTimings;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Per-stage timings of a recorded solve for a method, colour neutral
3
+ * (uses core's MethodTracker). For each stage: recognition = pause before its
4
+ * first move, execution = from that move to the stage's completion.
5
+ */
6
+ import { MethodTracker, applyMoves, solvedState } from "../core/index.js";
7
+ export function stageTimings(method, rec) {
8
+ const tracker = new MethodTracker(method, applyMoves(solvedState(), rec.scramble));
9
+ rec.moves.forEach((m) => tracker.push(m.move, m.t));
10
+ const labels = new Map(method.stages.map((s) => [s.id, s.label]));
11
+ const timeAfter = (moveIndex) => (moveIndex === 0 ? 0 : rec.moves[moveIndex - 1].t);
12
+ let prevIndex = 0;
13
+ let prevEnd = 0;
14
+ const stages = tracker.boundaries.map((b) => {
15
+ const endMs = timeAfter(b.moveIndex);
16
+ const moveCount = b.moveIndex - prevIndex;
17
+ // A move's timestamp is when it completed; its start is ~the previous move's time.
18
+ const firstStart = moveCount > 0 ? (prevIndex === 0 ? 0 : rec.moves[prevIndex - 1].t) : endMs;
19
+ const firstDone = moveCount > 0 ? rec.moves[prevIndex].t : endMs;
20
+ const recognitionMs = moveCount > 0 ? Math.max(0, firstDone - firstStart - estimateTurnMs(rec)) : 0;
21
+ const t = {
22
+ stage: b.stage,
23
+ label: labels.get(b.stage) ?? b.stage,
24
+ firstMove: prevIndex,
25
+ moveCount,
26
+ startMs: prevEnd,
27
+ endMs,
28
+ recognitionMs,
29
+ executionMs: Math.max(0, endMs - prevEnd - recognitionMs),
30
+ totalMs: endMs - prevEnd,
31
+ skipped: moveCount === 0,
32
+ ...(b.detail ? { detail: b.detail } : {}),
33
+ };
34
+ prevIndex = b.moveIndex;
35
+ prevEnd = endMs;
36
+ return t;
37
+ });
38
+ const recognition = stages.reduce((s, x) => s + x.recognitionMs, 0);
39
+ return {
40
+ stages,
41
+ fluency: rec.totalMs > 0 ? 1 - recognition / rec.totalMs : null,
42
+ bottomFace: tracker.bottomFace,
43
+ };
44
+ }
45
+ /**
46
+ * Typical time of one turn in this solve (median gap between consecutive
47
+ * moves, capped): the part of a pause that is really the first move itself.
48
+ */
49
+ function estimateTurnMs(rec) {
50
+ const gaps = rec.moves.slice(1).map((m, i) => m.t - rec.moves[i].t).filter((g) => g > 0).sort((a, b) => a - b);
51
+ if (gaps.length === 0)
52
+ return 0;
53
+ return Math.min(gaps[Math.floor(gaps.length / 2)], 250);
54
+ }
package/zz/index.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * ZZ — EOLine (all edges oriented + DF/DB), two F2L blocks in either order,
3
+ * then the last layer. Checks, method and masks; colour neutral.
4
+ */
5
+ import { type Frame, type Mask, type MaskRule, type Method, type StageDef, type State } from "../core/index.js";
6
+ export declare const eoLine: (s: State) => boolean;
7
+ export declare const eoCross: (s: State) => boolean;
8
+ export declare const leftBlockSolved: (s: State) => boolean;
9
+ export declare const rightBlockSolved: (s: State) => boolean;
10
+ export declare const ZZ: Method;
11
+ export type ZzMask = "eoline" | "eocross" | "f2l";
12
+ export declare const ZZ_MASKS: Record<ZzMask, MaskRule>;
13
+ export declare const zzMask: (name: ZzMask, frame?: Frame) => Mask;
14
+ export declare const ZZ_TRAINERS: {
15
+ /**
16
+ * EOCross: cross on D and every edge oriented (F/B axis). Level 10 is a
17
+ * few in a million among random states — its cases come from a list found
18
+ * offline (seeds.ts).
19
+ */
20
+ readonly eocross: () => StageDef;
21
+ /** EOLine: every edge oriented (F/B axis) and DF + DB in place — classic ZZ's first step. */
22
+ readonly eoline: () => StageDef;
23
+ /**
24
+ * ZZ blocks after EOLine, built with R U L only (edges stay oriented):
25
+ * the left block (DL + the FL and BL pairs), then the right (DR + FR, BR);
26
+ * `side` = which one this stage adds (the other may already be built).
27
+ */
28
+ readonly block: (side: "left" | "right", other?: boolean) => StageDef;
29
+ };
package/zz/index.js ADDED
@@ -0,0 +1,68 @@
1
+ /**
2
+ * ZZ — EOLine (all edges oriented + DF/DB), two F2L blocks in either order,
3
+ * then the last layer. Checks, method and masks; colour neutral.
4
+ */
5
+ import { IDENTITY_FRAME, LAST_LAYER_STAGES, PIECE, buildMask, checks as C } from "../core/index.js";
6
+ import { EOCROSS_SEEDS } from "./seeds.js";
7
+ const DF = C.cubieAt(0, -1, 1);
8
+ const DB = C.cubieAt(0, -1, -1);
9
+ const LEFT_BLOCK = C.cubiesWhere((p) => p[0] === -1 && p[1] <= 0);
10
+ const RIGHT_BLOCK = C.cubiesWhere((p) => p[0] === 1 && p[1] <= 0);
11
+ export const eoLine = (s) => C.edgesOrientedFB(s) && C.cubieSolved(s, DF) && C.cubieSolved(s, DB);
12
+ export const eoCross = (s) => C.edgesOrientedFB(s) && C.crossSolved(s);
13
+ export const leftBlockSolved = (s) => C.allSolved(s, LEFT_BLOCK);
14
+ export const rightBlockSolved = (s) => C.allSolved(s, RIGHT_BLOCK);
15
+ export const ZZ = {
16
+ id: "zz",
17
+ name: "ZZ",
18
+ stages: [
19
+ { id: "eoline", label: "EOLine", done: eoLine },
20
+ {
21
+ id: "block-1",
22
+ label: "First block",
23
+ done: (s) => eoLine(s) && (leftBlockSolved(s) || rightBlockSolved(s)),
24
+ detail: (s) => (leftBlockSolved(s) ? "left" : "right"),
25
+ },
26
+ { id: "block-2", label: "Second block", done: (s) => C.f2lSolved(s) && C.edgesOrientedFB(s) },
27
+ ...LAST_LAYER_STAGES,
28
+ ],
29
+ };
30
+ export const ZZ_MASKS = {
31
+ // Every edge shown as good/bad orientation material, the line in colour.
32
+ eoline: (f, c) => c.kind === "edge" ? (c.pos[1] === -1 && c.pos[0] === 0 ? "regular" : "oriented") : c.kind === "center" ? "regular" : "ignored",
33
+ // EOCross: the whole cross in colour, the other edges as orientation material.
34
+ eocross: (f, c) => (c.kind === "edge" ? (c.pos[1] === -1 ? "regular" : "oriented") : c.kind === "center" ? "regular" : "ignored"),
35
+ f2l: (f, c) => (c.pos[1] <= 0 || c.kind === "center" ? "regular" : "ignored"),
36
+ };
37
+ export const zzMask = (name, frame = IDENTITY_FRAME) => buildMask(ZZ_MASKS[name], frame);
38
+ // ─── trainer stages (run them with @cubecore/solve) ───
39
+ export const ZZ_TRAINERS = {
40
+ /**
41
+ * EOCross: cross on D and every edge oriented (F/B axis). Level 10 is a
42
+ * few in a million among random states — its cases come from a list found
43
+ * offline (seeds.ts).
44
+ */
45
+ eocross: () => ({
46
+ name: "eocross",
47
+ pieces: [PIECE.DR, PIECE.DF, PIECE.DL, PIECE.DB],
48
+ eo: true,
49
+ groups: [[0, 1, 2, 3]],
50
+ seeds: { 10: EOCROSS_SEEDS },
51
+ }),
52
+ /** EOLine: every edge oriented (F/B axis) and DF + DB in place — classic ZZ's first step. */
53
+ eoline: () => ({ name: "eoline", pieces: [PIECE.DF, PIECE.DB], eo: true, groups: [[0, 1]] }),
54
+ /**
55
+ * ZZ blocks after EOLine, built with R U L only (edges stay oriented):
56
+ * the left block (DL + the FL and BL pairs), then the right (DR + FR, BR);
57
+ * `side` = which one this stage adds (the other may already be built).
58
+ */
59
+ block: (side, other = false) => {
60
+ const left = [PIECE.DL, PIECE.FL, PIECE.DLF, PIECE.BL, PIECE.DBL];
61
+ const right = [PIECE.DR, PIECE.FR, PIECE.DFR, PIECE.BR, PIECE.DRB];
62
+ const blocks = other ? [left, right] : [side === "left" ? left : right];
63
+ const pieces = [PIECE.DF, PIECE.DB, ...blocks.flat()];
64
+ // Tables: line + block edge + one pair, per pair.
65
+ const groups = blocks.flatMap((_, b) => [[0, 1, 2 + 5 * b, 3 + 5 * b, 4 + 5 * b], [0, 1, 2 + 5 * b, 5 + 5 * b, 6 + 5 * b]]);
66
+ return { name: `zz-${other ? "blocks" : `${side}-block`}`, pieces, groups, moves: ["R", "U", "L"] };
67
+ },
68
+ };
package/zz/seeds.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * EOCross cases at level 10 — a few in a million random states, too rare to
3
+ * draw at run time. Found offline by walking the level-9 states and
4
+ * keeping neighbours at 10, widened by the stage's symmetries (y2, L↔R and
5
+ * F↔B mirrors) and each checked with the solver. Placements of DR DF DL DB
6
+ * (position × 2 + orientation) and the flip coordinate; the rest of the
7
+ * cube stays random in a trainer scramble. 112 cases.
8
+ */
9
+ export declare const EOCROSS_SEEDS: readonly string[];
package/zz/seeds.js ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * EOCross cases at level 10 — a few in a million random states, too rare to
3
+ * draw at run time. Found offline by walking the level-9 states and
4
+ * keeping neighbours at 10, widened by the stage's symmetries (y2, L↔R and
5
+ * F↔B mirrors) and each checked with the solver. Placements of DR DF DL DB
6
+ * (position × 2 + orientation) and the flip coordinate; the rest of the
7
+ * cube stays random in a trainer scramble. 112 cases.
8
+ */
9
+ export const EOCROSS_SEEDS = [
10
+ "13.11.9.15:1279", "13.11.9.15:895", "13.11.9.15:511", "13.11.9.15:1663", "9.15.13.11:1535", "9.15.13.11:1917",
11
+ "9.15.13.11:1534", "9.15.13.11:1915", "11.18.16.3:2041", "20.7.15.22:2046", "18.16.11.3:2041", "15.7.22.20:2046",
12
+ "5.6.1.2:1407", "18.3.13.9:2013", "9.13.22.7:2039", "9.3.16.13:2011", "20.9.13.7:2038", "7.20.9.15:2014",
13
+ "13.11.3.16:2035", "13.22.7.15:2015", "3.11.9.18:2037", "9.3.5.7:2007", "1.3.13.7:2007", "9.15.13.11:1019",
14
+ "9.15.13.11:1790", "9.15.13.11:1789", "9.15.13.11:1023", "13.11.9.15:1019", "13.11.9.15:1790", "13.11.9.15:1789",
15
+ "13.11.9.15:1023", "23.15.21.7:2047", "17.3.19.11:2047", "20.22.7.15:2046", "3.11.16.18:2041", "7.20.22.15:2046",
16
+ "18.11.3.16:2041", "5.15.1.11:2047", "5.11.1.15:2047", "19.11.17.3:2047", "21.7.23.15:2047", "9.3.13.11:2047",
17
+ "9.15.13.7:2047", "1.11.5.15:2047", "1.4.2.7:1159", "6.3.5.0:775", "2.0.5.7:391", "1.3.6.4:1543",
18
+ "9.15.13.11:895", "9.15.13.11:1279", "9.15.13.11:1663", "9.15.13.11:511", "9.15.13.3:1887", "9.7.13.11:1527",
19
+ "9.15.2.11:1407", "6.15.13.11:1407", "2.15.13.11:1407", "9.15.6.11:1407", "2.15.6.11:1407", "6.15.2.11:1407",
20
+ "1.18.3.15:2013", "7.11.5.22:2039", "3.16.5.15:2011", "1.11.7.20:2038", "9.3.13.5:2047", "9.1.13.7:2047",
21
+ "9.3.13.1:2047", "9.5.13.7:2047", "1.3.5.7:1967", "2.0.6.4:7", "6.4.2.0:7", "9.3.13.7:2047",
22
+ "1.6.5.15:1887", "1.11.5.2:1527", "23.11.21.7:2041", "17.3.19.15:2046", "22.4.3.0:760", "7.4.18.0:760",
23
+ "3.0.20.4:760", "16.0.7.4:760", "9.15.13.2:1503", "9.6.13.11:1911", "1.3.13.7:2047", "9.3.5.7:2047",
24
+ "4.3.0.15:767", "4.11.0.7:767", "9.15.13.11:127", "5.7.1.2:1503", "5.6.1.3:1911", "4.7.1.3:1687",
25
+ "5.7.0.3:967", "9.3.13.11:1911", "9.15.13.7:1503", "5.15.1.11:1407", "13.11.9.15:127", "13.3.21.7:1683",
26
+ "17.3.9.7:964", "23.3.9.7:964", "13.3.19.7:1683", "13.11.9.15:2045", "13.11.9.15:2042", "4.3.0.7:767",
27
+ "13.16.9.22:1363", "13.18.9.20:1364", "13.7.9.3:2007", "20.3.11.17:2038", "15.21.16.7:2011", "11.3.22.19:2039",
28
+ "18.23.15.7:2013", "5.7.1.3:2007", "1.3.5.6:1911", "1.2.5.7:1503",
29
+ ];