@cubingcenter/scrambler 0.6.3 → 0.8.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 (191) hide show
  1. package/README.md +17 -4
  2. package/dist/core/api.d.ts.map +1 -1
  3. package/dist/core/api.js +10 -2
  4. package/dist/puzzles/cube2/solver/browser-loader.d.ts +4 -7
  5. package/dist/puzzles/cube2/solver/browser-loader.d.ts.map +1 -1
  6. package/dist/puzzles/cube2/solver/browser-loader.js +42 -14
  7. package/dist/puzzles/cube2/solver/generated-assets.d.ts.map +1 -1
  8. package/dist/puzzles/cube2/solver/generated-assets.js +8 -3
  9. package/dist/puzzles/cube2/solver/search.d.ts.map +1 -1
  10. package/dist/puzzles/cube2/solver/search.js +3 -3
  11. package/dist/puzzles/cube2/solver/tables.d.ts +6 -0
  12. package/dist/puzzles/cube2/solver/tables.d.ts.map +1 -1
  13. package/dist/puzzles/cube2/solver/tables.js +16 -2
  14. package/dist/puzzles/cube3/index.d.ts +1 -0
  15. package/dist/puzzles/cube3/index.d.ts.map +1 -1
  16. package/dist/puzzles/cube3/moves.d.ts.map +1 -1
  17. package/dist/puzzles/cube3/moves.js +104 -18
  18. package/dist/puzzles/cube3/scrambler.d.ts +16 -1
  19. package/dist/puzzles/cube3/scrambler.d.ts.map +1 -1
  20. package/dist/puzzles/cube3/scrambler.js +1 -1
  21. package/dist/puzzles/cube3/solver/browser-loader.d.ts.map +1 -1
  22. package/dist/puzzles/cube3/solver/browser-loader.js +48 -22
  23. package/dist/puzzles/cube3/solver/generated-assets.d.ts.map +1 -1
  24. package/dist/puzzles/cube3/solver/generated-assets.js +9 -3
  25. package/dist/puzzles/cube3/solver/index.d.ts +5 -3
  26. package/dist/puzzles/cube3/solver/index.d.ts.map +1 -1
  27. package/dist/puzzles/cube3/solver/index.js +2 -1
  28. package/dist/puzzles/cube3/solver/node-loader.js +9 -2
  29. package/dist/puzzles/cube3/solver/phase1.d.ts +37 -0
  30. package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
  31. package/dist/puzzles/cube3/solver/phase1.js +156 -15
  32. package/dist/puzzles/cube3/solver/phase2.d.ts +75 -0
  33. package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
  34. package/dist/puzzles/cube3/solver/phase2.js +279 -68
  35. package/dist/puzzles/cube3/solver/search-utils.d.ts +1 -1
  36. package/dist/puzzles/cube3/solver/search-utils.d.ts.map +1 -1
  37. package/dist/puzzles/cube3/solver/symmetry.d.ts +24 -0
  38. package/dist/puzzles/cube3/solver/symmetry.d.ts.map +1 -0
  39. package/dist/puzzles/cube3/solver/symmetry.js +74 -0
  40. package/dist/puzzles/cube3/solver/tables.d.ts +6 -1
  41. package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
  42. package/dist/puzzles/cube3/solver/tables.js +14 -12
  43. package/dist/puzzles/cube3/validation.d.ts.map +1 -1
  44. package/dist/puzzles/cube3/validation.js +15 -7
  45. package/dist/puzzles/cube4/browser-worker-pool.d.ts +82 -0
  46. package/dist/puzzles/cube4/browser-worker-pool.d.ts.map +1 -0
  47. package/dist/puzzles/cube4/browser-worker-pool.js +205 -0
  48. package/dist/puzzles/cube4/browser-worker.d.ts +2 -0
  49. package/dist/puzzles/cube4/browser-worker.d.ts.map +1 -0
  50. package/dist/puzzles/cube4/browser-worker.js +31 -0
  51. package/dist/puzzles/cube4/index.d.ts +10 -2
  52. package/dist/puzzles/cube4/index.d.ts.map +1 -1
  53. package/dist/puzzles/cube4/index.js +6 -2
  54. package/dist/puzzles/cube4/moves.d.ts +5 -0
  55. package/dist/puzzles/cube4/moves.d.ts.map +1 -1
  56. package/dist/puzzles/cube4/moves.js +212 -11
  57. package/dist/puzzles/cube4/reduction/browser-loader.d.ts +29 -1
  58. package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -1
  59. package/dist/puzzles/cube4/reduction/browser-loader.js +227 -32
  60. package/dist/puzzles/cube4/reduction/center2.d.ts +44 -3
  61. package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -1
  62. package/dist/puzzles/cube4/reduction/center2.js +152 -10
  63. package/dist/puzzles/cube4/reduction/edge3.d.ts +4 -0
  64. package/dist/puzzles/cube4/reduction/edge3.d.ts.map +1 -1
  65. package/dist/puzzles/cube4/reduction/edge3.js +23 -3
  66. package/dist/puzzles/cube4/reduction/finish3.d.ts +53 -0
  67. package/dist/puzzles/cube4/reduction/finish3.d.ts.map +1 -0
  68. package/dist/puzzles/cube4/reduction/finish3.js +393 -0
  69. package/dist/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
  70. package/dist/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
  71. package/dist/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
  72. package/dist/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
  73. package/dist/puzzles/cube4/reduction/generated/hc-manifest.json +44 -0
  74. package/dist/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
  75. package/dist/puzzles/cube4/reduction/hc-pdb.d.ts +52 -0
  76. package/dist/puzzles/cube4/reduction/hc-pdb.d.ts.map +1 -0
  77. package/dist/puzzles/cube4/reduction/hc-pdb.js +220 -0
  78. package/dist/puzzles/cube4/reduction/node-loader.d.ts +15 -1
  79. package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -1
  80. package/dist/puzzles/cube4/reduction/node-loader.js +199 -25
  81. package/dist/puzzles/cube4/reduction/pair-transversal.d.ts +28 -0
  82. package/dist/puzzles/cube4/reduction/pair-transversal.d.ts.map +1 -0
  83. package/dist/puzzles/cube4/reduction/pair-transversal.js +107 -0
  84. package/dist/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
  85. package/dist/puzzles/cube4/reduction/phase3-search.d.ts +116 -7
  86. package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -1
  87. package/dist/puzzles/cube4/reduction/phase3-search.js +458 -32
  88. package/dist/puzzles/cube4/reduction/rotation-fix.d.ts +28 -0
  89. package/dist/puzzles/cube4/reduction/rotation-fix.d.ts.map +1 -0
  90. package/dist/puzzles/cube4/reduction/rotation-fix.js +212 -0
  91. package/dist/puzzles/cube4/reduction/solve.d.ts +172 -2
  92. package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -1
  93. package/dist/puzzles/cube4/reduction/solve.js +1201 -180
  94. package/dist/puzzles/cube4/scramble-cache.d.ts +93 -0
  95. package/dist/puzzles/cube4/scramble-cache.d.ts.map +1 -0
  96. package/dist/puzzles/cube4/scramble-cache.js +150 -0
  97. package/dist/puzzles/cube4/scrambler.d.ts +201 -6
  98. package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
  99. package/dist/puzzles/cube4/scrambler.js +414 -176
  100. package/dist/puzzles/cube4/state.d.ts.map +1 -1
  101. package/dist/puzzles/cube4/state.js +4 -1
  102. package/dist/puzzles/cube4/worker-pool.d.ts +139 -0
  103. package/dist/puzzles/cube4/worker-pool.d.ts.map +1 -0
  104. package/dist/puzzles/cube4/worker-pool.js +405 -0
  105. package/dist/puzzles/cube4/worker-protocol.d.ts +58 -0
  106. package/dist/puzzles/cube4/worker-protocol.d.ts.map +1 -0
  107. package/dist/puzzles/cube4/worker-protocol.js +58 -0
  108. package/dist/puzzles/cube4/worker.d.ts +26 -0
  109. package/dist/puzzles/cube4/worker.d.ts.map +1 -0
  110. package/dist/puzzles/cube4/worker.js +49 -0
  111. package/dist/puzzles/pyraminx/solver/browser-loader.d.ts +4 -8
  112. package/dist/puzzles/pyraminx/solver/browser-loader.d.ts.map +1 -1
  113. package/dist/puzzles/pyraminx/solver/browser-loader.js +44 -15
  114. package/dist/puzzles/pyraminx/solver/generated-assets.d.ts.map +1 -1
  115. package/dist/puzzles/pyraminx/solver/generated-assets.js +8 -3
  116. package/dist/puzzles/pyraminx/solver/search.d.ts.map +1 -1
  117. package/dist/puzzles/pyraminx/solver/search.js +3 -6
  118. package/dist/puzzles/pyraminx/solver/tables.d.ts +6 -0
  119. package/dist/puzzles/pyraminx/solver/tables.d.ts.map +1 -1
  120. package/dist/puzzles/pyraminx/solver/tables.js +17 -0
  121. package/dist/puzzles/shared/compressed-fetch.d.ts +37 -1
  122. package/dist/puzzles/shared/compressed-fetch.d.ts.map +1 -1
  123. package/dist/puzzles/shared/compressed-fetch.js +226 -31
  124. package/dist/puzzles/shared/load-once.d.ts +16 -0
  125. package/dist/puzzles/shared/load-once.d.ts.map +1 -0
  126. package/dist/puzzles/shared/load-once.js +11 -0
  127. package/dist/puzzles/skewb/solver/browser-loader.d.ts +4 -11
  128. package/dist/puzzles/skewb/solver/browser-loader.d.ts.map +1 -1
  129. package/dist/puzzles/skewb/solver/browser-loader.js +44 -16
  130. package/dist/puzzles/skewb/solver/generated-assets.d.ts.map +1 -1
  131. package/dist/puzzles/skewb/solver/generated-assets.js +8 -3
  132. package/dist/puzzles/skewb/solver/search.js +3 -3
  133. package/dist/puzzles/skewb/solver/tables.d.ts +6 -0
  134. package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -1
  135. package/dist/puzzles/skewb/solver/tables.js +17 -0
  136. package/dist/puzzles/sq1/solver/browser-loader.d.ts.map +1 -1
  137. package/dist/puzzles/sq1/solver/browser-loader.js +58 -26
  138. package/dist-cjs/core/api.js +10 -2
  139. package/dist-cjs/puzzles/cube2/solver/browser-loader.js +42 -14
  140. package/dist-cjs/puzzles/cube2/solver/generated-assets.js +8 -3
  141. package/dist-cjs/puzzles/cube2/solver/search.js +2 -2
  142. package/dist-cjs/puzzles/cube2/solver/tables.js +17 -3
  143. package/dist-cjs/puzzles/cube3/moves.js +104 -18
  144. package/dist-cjs/puzzles/cube3/scrambler.js +1 -1
  145. package/dist-cjs/puzzles/cube3/solver/browser-loader.js +48 -22
  146. package/dist-cjs/puzzles/cube3/solver/generated-assets.js +9 -3
  147. package/dist-cjs/puzzles/cube3/solver/index.js +8 -1
  148. package/dist-cjs/puzzles/cube3/solver/node-loader.js +9 -2
  149. package/dist-cjs/puzzles/cube3/solver/phase1.js +157 -15
  150. package/dist-cjs/puzzles/cube3/solver/phase2.js +276 -65
  151. package/dist-cjs/puzzles/cube3/solver/symmetry.js +80 -0
  152. package/dist-cjs/puzzles/cube3/solver/tables.js +14 -12
  153. package/dist-cjs/puzzles/cube3/validation.js +15 -7
  154. package/dist-cjs/puzzles/cube4/browser-worker-pool.js +211 -0
  155. package/dist-cjs/puzzles/cube4/index.js +23 -1
  156. package/dist-cjs/puzzles/cube4/moves.js +213 -11
  157. package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +227 -32
  158. package/dist-cjs/puzzles/cube4/reduction/center2.js +155 -10
  159. package/dist-cjs/puzzles/cube4/reduction/edge3.js +24 -3
  160. package/dist-cjs/puzzles/cube4/reduction/finish3.js +398 -0
  161. package/dist-cjs/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
  162. package/dist-cjs/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
  163. package/dist-cjs/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
  164. package/dist-cjs/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
  165. package/dist-cjs/puzzles/cube4/reduction/generated/hc-manifest.json +44 -0
  166. package/dist-cjs/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
  167. package/dist-cjs/puzzles/cube4/reduction/hc-pdb.js +232 -0
  168. package/dist-cjs/puzzles/cube4/reduction/node-loader.js +200 -25
  169. package/dist-cjs/puzzles/cube4/reduction/pair-transversal.js +112 -0
  170. package/dist-cjs/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
  171. package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +460 -33
  172. package/dist-cjs/puzzles/cube4/reduction/rotation-fix.js +216 -0
  173. package/dist-cjs/puzzles/cube4/reduction/solve.js +1203 -172
  174. package/dist-cjs/puzzles/cube4/scramble-cache.js +156 -0
  175. package/dist-cjs/puzzles/cube4/scrambler.js +416 -174
  176. package/dist-cjs/puzzles/cube4/state.js +4 -1
  177. package/dist-cjs/puzzles/cube4/worker-pool.js +447 -0
  178. package/dist-cjs/puzzles/cube4/worker-protocol.js +62 -0
  179. package/dist-cjs/puzzles/pyraminx/solver/browser-loader.js +44 -15
  180. package/dist-cjs/puzzles/pyraminx/solver/generated-assets.js +8 -3
  181. package/dist-cjs/puzzles/pyraminx/solver/search.js +2 -5
  182. package/dist-cjs/puzzles/pyraminx/solver/tables.js +18 -1
  183. package/dist-cjs/puzzles/shared/compressed-fetch.js +229 -31
  184. package/dist-cjs/puzzles/shared/load-once.js +14 -0
  185. package/dist-cjs/puzzles/skewb/solver/browser-loader.js +44 -16
  186. package/dist-cjs/puzzles/skewb/solver/generated-assets.js +8 -3
  187. package/dist-cjs/puzzles/skewb/solver/search.js +2 -2
  188. package/dist-cjs/puzzles/skewb/solver/tables.js +18 -1
  189. package/dist-cjs/puzzles/sq1/solver/browser-loader.js +58 -26
  190. package/docs/development.md +19 -7
  191. package/package.json +17 -7
@@ -1,111 +1,89 @@
1
1
  /**
2
2
  * The 4x4 scramble generator: sample a uniform random state, solve it with
3
- * the reduction solver (phases 1-4), and invert the solution. The scramble
4
- * always reproduces the sampled target state and facelets exactly.
3
+ * the reduction solver (phases 1-4), and invert the solution. The emitted
4
+ * scramble must reproduce the sampled target state EXACTLY — enforced by
5
+ * `assertCube4ScrambleReproducesTarget` on every generated result (unless
6
+ * `skipValidation` is set, which is only for the independent verification
7
+ * harness in `scripts/verify-scrambles.ts`).
5
8
  *
6
- * If the solver fails after 5 attempts it falls back to cubing.js
7
- * `randomScrambleForEvent("444")` (also random-state). If that also fails,
8
- * a WCA-compatible random-moves sequence (40 moves) is returned.
9
+ * On a target mismatch the error is retryable, so the existing retry loop
10
+ * re-runs the solve over the SAME sampled target (deep attempt) and throws
11
+ * once attempts are exhausted. Re-sampling a different target to "make it
12
+ * fit" and degrading to random moves are forbidden: failing loudly (WCA 4g)
13
+ * is strictly preferable to emitting a scramble that lies about its seed.
14
+ *
15
+ * ── Determinism contract: what defines the output ──────────────────────
16
+ * `generateCube4Scramble` / `generateCube4Attempt` are pure functions of
17
+ * (seed, complete options): the emitted `moves` do not depend on wall-clock
18
+ * time, CPU load, warm-up, batch order, or process identity. This holds
19
+ * because every budget that can change the search outcome is a
20
+ * deterministic work counter (nodes visited), never a wall deadline:
21
+ * - reduction phases: node budgets inside `reduction/solve.ts`;
22
+ * - the 3x3 finish: node caps below (`finish.phase2MaxNodes`,
23
+ * `finish.improveMaxNodes`, per-candidate cap), which replace the old
24
+ * 150/50 ms wall budgets that made the same seed yield different move
25
+ * counts under CPU contention.
26
+ * Options are PART of the key: `restarts`/`bestOf`, `threeAxes`, `deep`,
27
+ * the beam knobs and `finish.*` legitimately change the result for the same
28
+ * seed — the output is reproducible within each configuration (documented
29
+ * behavior, not nondeterminism).
30
+ *
31
+ * Residual wall-clock backstop (hang guard only; never observed to bind —
32
+ * max measured solve 36.5 s): the 90 s
33
+ * `Promise.race` below. It cannot preempt the synchronous search — it
34
+ * can only fire at an event-loop tick — so if one ever fired, that attempt
35
+ * fails loudly (as a retryable error) instead of hanging. Making that bound
36
+ * exact deadline support would require cooperative search checkpoints.
9
37
  */
10
- import { formatCube4Algorithm, applyCube4Algorithm } from "./moves.js";
38
+ import { formatCube4Algorithm, applyCube4Algorithm, invertCube4Algorithm } from "./moves.js";
11
39
  import { generateRandomCube4State } from "./random-state.js";
12
40
  import { createSolvedCube4State, cube4StatesEqual } from "./state.js";
13
41
  import { formatCube4Facelets, toCube4Facelets } from "./facelets.js";
14
- import { solveCube4State } from "./reduction/solve.js";
15
- import { solveCube3State } from "../cube3/solver/index.js";
16
- import { createRandomSource } from "../../random/index.js";
17
- // ────────────────────────────────────────────────────────────
18
- // cubing.js fallback (random-state, WCA-compliant)
42
+ import { solveCube4State, applyCubeRotation, Cube4FinishCapError, Cube4Phase2CapError, Cube4Phase3CapError } from "./reduction/solve.js";
43
+ import { solveCube3State, Cube3Phase1SearchError, Cube3Phase2SearchError } from "../cube3/solver/index.js";
44
+ import { loadCube3Phase1Tables, loadCube3Phase2Tables } from "../cube3/solver/tables.js";
45
+ import { solveFinish3, Finish3SearchError } from "./reduction/finish3.js";
46
+ import { placeRotationFix } from "./reduction/rotation-fix.js";
19
47
  // ────────────────────────────────────────────────────────────
20
48
  /**
21
- * Try to generate a 4x4 scramble using cubing.js `randomScrambleForEvent`.
22
- * Uses a dynamic import so browser bundles can tree-shake it when unused.
23
- * Returns null if cubing.js is unavailable.
49
+ * Deterministic work caps for the 3x3 finish — nodes visited, never
50
+ * wall-clock. Calibrated against the former wall budgets (phase-2 50 ms,
51
+ * improvement 150 ms at a measured ~1.75k nodes/ms warm) so typical
52
+ * latency and finish length are unchanged, while the stopping point
53
+ * becomes independent of CPU load: same (seed, options) ⇒ same moves.
54
+ * A failing first finish call retries once with every cap doubled
55
+ * (equivalent of the former 150→300 / 50→100 ms retry).
24
56
  */
25
- async function generateCube4WithCubingJs() {
26
- try {
27
- const { randomScrambleForEvent } = await import(
28
- /* webpackIgnore: true */
29
- /* @vite-ignore */
30
- "cubing/scramble");
31
- const alg = await randomScrambleForEvent("444");
32
- const scramble = alg.toString();
33
- const moves = scramble.trim().split(/\s+/);
34
- return {
35
- eventId: "444",
36
- scramble,
37
- moves,
38
- quality: "random-state",
39
- generator: "cubing-js",
40
- metadata: {
41
- generator: "cubing-js",
42
- distribution: "random-state",
43
- note: "Generated by cubing.js randomScrambleForEvent as fallback."
44
- }
45
- };
46
- }
47
- catch {
48
- return null;
49
- }
50
- }
51
- // ────────────────────────────────────────────────────────────
52
- // Last-resort: random-moves (no external dep, always works)
53
- // ────────────────────────────────────────────────────────────
54
- const CUBE4_FACES = ["U", "R", "F", "D", "L", "B"];
55
- // Opposite face indices (U↔D, R↔L, F↔B)
56
- const CUBE4_OPPOSITE = { U: "D", D: "U", R: "L", L: "R", F: "B", B: "F" };
57
- const CUBE4_SUFFIXES = ["", "2", "'"];
58
- const CUBE4_WIDE_FACES = ["Uw", "Rw", "Fw", "Dw", "Lw", "Bw"];
59
- // Opposite wide-move faces
60
- const CUBE4_WIDE_OPPOSITE = {
61
- Uw: "Dw", Dw: "Uw", Rw: "Lw", Lw: "Rw", Fw: "Bw", Bw: "Fw"
62
- };
63
- // All available move bases (outer + wide). Outer and wide turns are treated
64
- // independently for the same-face/opposite-face check using their base letter.
65
- const ALL_MOVE_BASES = [...CUBE4_FACES, ...CUBE4_WIDE_FACES];
66
- function generateCube4RandomMoves(options = {}) {
67
- const rng = createRandomSource(options.seed);
68
- const moveCount = 40;
69
- const moves = [];
70
- let lastFace = null;
71
- let secondLastFace = null;
72
- const getBaseLetter = (face) => face.length > 1 ? face[0] : face;
73
- for (let i = 0; i < moveCount; i++) {
74
- let face;
75
- // Reject same face as last or opposite face when the last two moves
76
- // share the same axis (to avoid redundant move pairs like R L R).
77
- do {
78
- face = ALL_MOVE_BASES[rng.nextInt(ALL_MOVE_BASES.length)];
79
- } while (face === lastFace ||
80
- (lastFace !== null &&
81
- (CUBE4_OPPOSITE[face] === lastFace || CUBE4_WIDE_OPPOSITE[face] === lastFace) &&
82
- secondLastFace !== null &&
83
- getBaseLetter(secondLastFace) === getBaseLetter(face)));
84
- const suffix = CUBE4_SUFFIXES[rng.nextInt(3)];
85
- moves.push(`${face}${suffix}`);
86
- secondLastFace = lastFace;
87
- lastFace = face;
88
- }
89
- const scramble = moves.join(" ");
90
- return {
91
- eventId: "444",
92
- scramble,
93
- moves,
94
- quality: "random-moves",
95
- generator: "cube4-random-moves-fallback",
96
- metadata: {
97
- generator: "cube4-random-moves-fallback",
98
- distribution: "random-moves",
99
- note: "Reduction solver and cubing.js both failed; using random-moves.",
100
- ...options.metadata
101
- }
102
- };
57
+ const FINISH_PHASE2_MAX_NODES = 90_000;
58
+ const FINISH_IMPROVE_MAX_NODES = 260_000;
59
+ const FINISH_RETRY_SCALE = 2;
60
+ // Continuous finish defaults (the production finish). Measured on cc:0..15:
61
+ // 6 views × 200k nodes = 44.88 moves @ 1257 ms vs legacy 46.06 @ 1288 ms
62
+ // (50k: 45.50 @ 1102; 100k: 45.31; 300k: 44.88 @ 1371 — no gain past 200k).
63
+ const FINISH3_MAX_NODES = 200_000;
64
+ const FINISH3_HARD_MAX_NODES = 20_000_000;
65
+ // Screening budget for the two-stage finish (every candidate reduction gets
66
+ // this; only the best `finishScreenTop` get FINISH3_MAX_NODES).
67
+ const FINISH3_SCREEN_NODES = 30_000;
68
+ const FINISH3_VIEWS = 6;
69
+ const FINISH3_MAX_PHASE2_LENGTH = 12;
70
+ let cube4TablesPromise;
71
+ /**
72
+ * Loads the 4x4 reduction tables with a promise-deduplicating cache: the
73
+ * cache stores the in-flight PROMISE (not a value), so concurrent first
74
+ * calls share one load instead of racing; a rejected load is purged so the
75
+ * next call can retry instead of caching the failure forever.
76
+ */
77
+ function loadCube4Tables(baseUrl) {
78
+ if (cube4TablesPromise !== undefined)
79
+ return cube4TablesPromise;
80
+ cube4TablesPromise = loadCube4TablesOnce(baseUrl).catch((error) => {
81
+ cube4TablesPromise = undefined;
82
+ throw error;
83
+ });
84
+ return cube4TablesPromise;
103
85
  }
104
- // ────────────────────────────────────────────────────────────
105
- let cachedTables;
106
- async function loadCube4Tables(baseUrl) {
107
- if (cachedTables)
108
- return cachedTables;
86
+ async function loadCube4TablesOnce(baseUrl) {
109
87
  const isBrowser = typeof window !== "undefined" ||
110
88
  typeof document !== "undefined" ||
111
89
  (typeof self !== "undefined" && typeof self.importScripts === "function") ||
@@ -118,20 +96,15 @@ async function loadCube4Tables(baseUrl) {
118
96
  "./reduction/browser-loader.js");
119
97
  const cached = loader.getCachedCube4Tables();
120
98
  if (cached) {
121
- cachedTables = cached;
122
99
  return cached;
123
100
  }
124
- const tables = await loader.loadCube4ReductionTablesBrowser(baseUrl);
125
- cachedTables = tables;
126
- return tables;
101
+ return await loader.loadCube4ReductionTablesBrowser(baseUrl);
127
102
  }
128
103
  const loader = await import(
129
104
  /* webpackIgnore: true */
130
105
  /* @vite-ignore */
131
106
  "./reduction/node-loader.js");
132
- const tables = await loader.loadCube4ReductionTables();
133
- cachedTables = tables;
134
- return tables;
107
+ return await loader.loadCube4ReductionTables();
135
108
  }
136
109
  export const cube4ScrambleGenerator = {
137
110
  info: {
@@ -140,98 +113,363 @@ export const cube4ScrambleGenerator = {
140
113
  puzzle: "cube-4x4",
141
114
  quality: "random-state",
142
115
  generator: "cube4-reduction",
143
- description: "Random-state 4x4 scramble via the reduction solver (centers + pairing + 3x3 finish). Falls back to random-moves if the solver fails."
116
+ description: "Random-state 4x4 scramble via the reduction solver (centers + pairing + 3x3 finish)."
144
117
  },
145
118
  generate(options = {}) {
146
119
  return generateCube4Scramble(options);
147
120
  }
148
121
  };
122
+ /**
123
+ * Thrown by the target gate when the emitted scramble does not reproduce
124
+ * the independently sampled state. Retryable: the retry loop re-solves the
125
+ * SAME sampled target (deep attempt) — never re-samples.
126
+ */
127
+ export class Cube4TargetMismatchError extends Error {
128
+ code = "cube4-target-mismatch";
129
+ constructor(message) {
130
+ super(message);
131
+ this.name = "Cube4TargetMismatchError";
132
+ }
133
+ }
134
+ /** Thrown by the 90 s hang guard (wall-clock, fires only at a tick). */
135
+ export class Cube4WallTimeoutError extends Error {
136
+ code = "cube4-wall-timeout";
137
+ constructor(message) {
138
+ super(message);
139
+ this.name = "Cube4WallTimeoutError";
140
+ }
141
+ }
142
+ /**
143
+ * Message patterns for errors thrown by `reduction/solve.ts`, which cannot
144
+ * be classified by type/code without editing that file (reported: it should
145
+ * grow error codes). Every other retryable path is typed below.
146
+ */
147
+ const SOLVER_RETRYABLE_MESSAGE_PATTERNS = [
148
+ "reduction failed", // no candidates / node budget exhausted in a reduction phase
149
+ "no valid phase 2" // empty phase-2 candidate set
150
+ ];
151
+ /**
152
+ * Retry classification. Typed first (error class / `code`), message
153
+ * patterns last and only for the reduction solver's own errors. Semantics:
154
+ * a retry re-runs the solve over the SAME sampled target with deeper work
155
+ * caps; it never changes the seed's target state.
156
+ */
157
+ function isFinish3Cap(error) {
158
+ return error instanceof Error &&
159
+ error.code === "cube4-finish3-cap" &&
160
+ (error instanceof Finish3SearchError || error.name === "Finish3SearchError");
161
+ }
162
+ function isRetryableCube4Error(error) {
163
+ if (!(error instanceof Error))
164
+ return false;
165
+ const code = error.code;
166
+ if (code === "cube4-target-mismatch" || code === "cube4-wall-timeout")
167
+ return true;
168
+ if (isFinish3Cap(error))
169
+ return true;
170
+ if (error instanceof Cube4Phase2CapError ||
171
+ error instanceof Cube4Phase3CapError ||
172
+ error instanceof Cube4FinishCapError)
173
+ return true;
174
+ // Budget/depth failures of the 3x3 finish phase 2: a deeper attempt may
175
+ // succeed (historical behavior: any Cube3Phase2* error was retryable).
176
+ if (error instanceof Cube3Phase2SearchError || error.name === "Cube3Phase2SearchError") {
177
+ return true;
178
+ }
179
+ // Phase-1 failures are deterministic depth failures (phase 1 runs
180
+ // uncapped in the 444 finish): retrying the same target cannot help.
181
+ if (error instanceof Cube3Phase1SearchError || error.name === "Cube3Phase1SearchError") {
182
+ return false;
183
+ }
184
+ return SOLVER_RETRYABLE_MESSAGE_PATTERNS.some((pattern) => error.message.includes(pattern));
185
+ }
186
+ /**
187
+ * The real random-state contract for 444 (non-tautological): replaying the
188
+ * emitted scramble on the solved cube must reproduce the exact state that
189
+ * was sampled for the seed — the reference the sampler produced, never a
190
+ * state derived from the scramble itself. Throws when it does not.
191
+ *
192
+ * Used by the production validation path in `solveCube4Attempt` and
193
+ * exported so tests can prove the gate rejects corrupted scrambles.
194
+ */
195
+ export function assertCube4ScrambleReproducesTarget(scrambleMoves, targetState, seed) {
196
+ const replayed = applyCube4Algorithm(createSolvedCube4State(), scrambleMoves);
197
+ if (!cube4StatesEqual(replayed, targetState)) {
198
+ throw new Cube4TargetMismatchError("[cube4-scrambler] scramble does not reproduce the sampled target state" +
199
+ (seed !== undefined ? ` (seed=${seed})` : "") +
200
+ ": applying the emitted scramble to the solved cube must yield the state " +
201
+ "sampled for this seed. Refusing to emit it (no re-sampling, no random-move fallback).");
202
+ }
203
+ }
149
204
  export async function generateCube4Scramble(options = {}) {
150
205
  let tables;
206
+ // Optional pair tables are unnecessary for the 444 finish. In browsers,
207
+ // probing for absent tables on every generation adds a network dependency
208
+ // before the search even starts (outside its timeout).
209
+ const pairTables = undefined;
151
210
  try {
152
- tables = await loadCube4Tables();
211
+ const continuousFinish = options.finish?.mode !== "legacy";
212
+ const [loadedTables, cube3P1Tables, cube3P2Tables] = await Promise.all([
213
+ loadCube4Tables(),
214
+ loadCube3Phase1Tables(),
215
+ continuousFinish ? loadCube3Phase2Tables() : Promise.resolve(undefined)
216
+ ]);
217
+ tables = loadedTables;
218
+ tables.cube3Phase1 = cube3P1Tables;
219
+ if (cube3P2Tables !== undefined)
220
+ tables.cube3Phase2 = cube3P2Tables;
153
221
  }
154
222
  catch (error) {
155
- console.warn("[cube4-scrambler] Failed to load reduction tables; using fallback.", error);
156
- const cubingJsResult = await generateCube4WithCubingJs();
157
- if (cubingJsResult)
158
- return cubingJsResult;
159
- return generateCube4RandomMoves(options);
223
+ throw new Error("[cube4-scrambler] Failed to load reduction tables; run pnpm cube4:center1:tables etc. " +
224
+ `(${error instanceof Error ? error.message : String(error)})`);
160
225
  }
226
+ const totalRestarts = Math.max(1, Math.floor(options.restarts ?? options.bestOf ?? 1) || 1);
227
+ const targetState = generateRandomCube4State(options);
161
228
  let lastError;
162
- // Retry with different states if the solver fails to find a physical
163
- // reduction (some random states are outside the solver's effective
164
- // horizon with the current candidate/length caps).
165
- for (let attempt = 0; attempt < 5; attempt++) {
166
- let attemptOptions;
167
- if (attempt === 0) {
168
- attemptOptions = options;
229
+ try {
230
+ return await solveCube4Attempt(tables, pairTables, {
231
+ ...options,
232
+ restarts: totalRestarts
233
+ }, targetState);
234
+ }
235
+ catch (error) {
236
+ if (!isRetryableCube4Error(error))
237
+ throw error;
238
+ lastError = error;
239
+ }
240
+ if (!options.deep) {
241
+ try {
242
+ return await solveCube4Attempt(tables, pairTables, {
243
+ ...options,
244
+ restarts: totalRestarts,
245
+ deep: true
246
+ }, targetState);
169
247
  }
170
- else {
171
- attemptOptions = { ...options };
172
- if (options.seed !== undefined) {
173
- attemptOptions.seed = `${options.seed}:retry${attempt}`;
174
- }
175
- else {
176
- delete attemptOptions.seed;
177
- }
248
+ catch (error) {
249
+ if (isRetryableCube4Error(error))
250
+ lastError = error;
251
+ else
252
+ throw error;
178
253
  }
179
- const attemptState = generateRandomCube4State(attemptOptions);
254
+ }
255
+ throw lastError instanceof Error
256
+ ? lastError
257
+ : new Error(`[cube4-scrambler] Reduction solver failed for restarts=${totalRestarts}: ${String(lastError)}`);
258
+ }
259
+ /**
260
+ * Single-attempt 444 solve: samples ONE state (from `options.seed`) and
261
+ * solves it with the reduction solver. Throws on failure — including a
262
+ * target mismatch ("scramble does not reproduce the sampled target state")
263
+ * — no resampling; hosts may retry with the same seed, which re-uses the
264
+ * same target.
265
+ * Fan-out primitive for hosts: run with seeds `seed`, `seed:bestN`, ...
266
+ * across workers and keep the shortest valid result.
267
+ */
268
+ export async function generateCube4Attempt(tables, pairTables, options = {}) {
269
+ return solveCube4Attempt(tables, pairTables, options, generateRandomCube4State(options));
270
+ }
271
+ async function solveCube4Attempt(tables, pairTables, options, attemptState) {
272
+ const axes = options.threeAxes ? [[], ["z"], ["y'"]] : [[]];
273
+ let bestScrambleMoves = null;
274
+ let bestSolutionMoves = null;
275
+ let bestPhases;
276
+ let bestNodes;
277
+ let bestTimings;
278
+ let bestReductionLen;
279
+ let lastError;
280
+ const tAttemptStart = performance.now();
281
+ const finishOpts = options.finish ?? {};
282
+ for (const rot of axes) {
180
283
  try {
181
- const solvePromise = solveCube4State(attemptState, tables, (cube3State) => solveCube3State(cube3State));
182
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("4x4 solve: timeout")), 20000));
183
- const { scramble: scrambleMoves } = await Promise.race([solvePromise, timeoutPromise]);
184
- if (!options.skipValidation) {
185
- const scrambledState = applyCube4Algorithm(createSolvedCube4State(), scrambleMoves);
186
- if (!cube4StatesEqual(scrambledState, attemptState)) {
187
- throw new Error("Generated 4x4 scramble does not reproduce the target random state.");
284
+ const sRot = rot.length === 0 ? attemptState : applyCubeRotation(attemptState, rot);
285
+ // The reduction search is bounded by deterministic work caps (see
286
+ // solve.ts), so these timeouts are only wall-clock backstops for
287
+ // pathological cases.
288
+ const solvePromise = solveCube4State(sRot, tables, async (cube3State, effort) => {
289
+ // Legacy batch two-phase finish. Budgets are deterministic node
290
+ // caps (see FINISH_* constants); `capScale` doubles every cap on
291
+ // the one flaky-finish retry.
292
+ const solveLegacyFinish = async (capScale) => {
293
+ const solution = await solveCube3State(cube3State, {
294
+ orientations: finishOpts.orientations ?? 3,
295
+ phase1ExtraDepth: finishOpts.phase1ExtraDepth ?? 1,
296
+ phase1SolutionLimit: finishOpts.phase1SolutionLimit ?? 24,
297
+ // 18 (not 20): the improve loop only runs when the first
298
+ // answer exceeds the target, so 20 skipped it for 19-20 move
299
+ // answers. Measured: -0.13 moves at ~+3% time.
300
+ targetLength: finishOpts.targetLength ?? 18,
301
+ improveMaxNodes: Math.round((finishOpts.improveMaxNodes ?? FINISH_IMPROVE_MAX_NODES) * capScale),
302
+ ...(finishOpts.improvePhase2MaxNodes !== undefined
303
+ ? {
304
+ improvePhase2MaxNodes: Math.round(finishOpts.improvePhase2MaxNodes * capScale)
305
+ }
306
+ : {}),
307
+ // The 54MB pair tables are skipped for the 444 finish: pair
308
+ // pruning changes nothing measurable here (identical lengths
309
+ // on a 44-state corpus, zero extra finish retries), and
310
+ // loading them dominates cold-start I/O. Hosts that already
311
+ // hold pairTables can still pass them via generateCube4Attempt.
312
+ phase2: {
313
+ pairTables,
314
+ skipPairTables: pairTables === undefined,
315
+ maxNodes: Math.round((finishOpts.phase2MaxNodes ?? FINISH_PHASE2_MAX_NODES) * capScale)
316
+ }
317
+ });
318
+ return { moves: solution.moves };
319
+ };
320
+ if (finishOpts.mode !== "legacy") {
321
+ // Callers of generateCube4Attempt pass their own table bag,
322
+ // which may predate the continuous finish: load what is missing.
323
+ tables.cube3Phase1 ??= await loadCube3Phase1Tables();
324
+ tables.cube3Phase2 ??= await loadCube3Phase2Tables();
325
+ const views = finishOpts.continuousViews ?? FINISH3_VIEWS;
326
+ const runContinuous = (capScale) => {
327
+ if (tables.cube3Phase1 === undefined || tables.cube3Phase2 === undefined) {
328
+ throw new Error("[cube4-scrambler] continuous finish requires cube3 phase-1 and phase-2 tables");
329
+ }
330
+ return solveFinish3(cube3State, { phase1: tables.cube3Phase1, phase2: tables.cube3Phase2 }, {
331
+ targetLength: finishOpts.targetLength ?? 18,
332
+ maxNodes: Math.round((effort === "screen"
333
+ ? finishOpts.continuousScreenNodes ?? FINISH3_SCREEN_NODES
334
+ : finishOpts.continuousMaxNodes ?? FINISH3_MAX_NODES) * capScale),
335
+ hardMaxNodes: Math.round((finishOpts.continuousHardMaxNodes ?? FINISH3_HARD_MAX_NODES) * capScale),
336
+ maxPhase2Length: finishOpts.continuousMaxPhase2Length ?? FINISH3_MAX_PHASE2_LENGTH,
337
+ views
338
+ });
339
+ };
340
+ try {
341
+ return runContinuous(1);
342
+ }
343
+ catch (error) {
344
+ if (!isFinish3Cap(error))
345
+ throw error;
346
+ }
347
+ try {
348
+ return runContinuous(FINISH_RETRY_SCALE);
349
+ }
350
+ catch (error) {
351
+ if (!isFinish3Cap(error))
352
+ throw error;
353
+ return solveLegacyFinish(1);
354
+ }
355
+ }
356
+ // The first legacy call seeds everything, so a flaky finish gets
357
+ // one retry with doubled caps before the whole finish is declared
358
+ // failed (formerly 150→300 / 50→100 wall budgets).
359
+ try {
360
+ return await solveLegacyFinish(1);
361
+ }
362
+ catch {
363
+ return solveLegacyFinish(FINISH_RETRY_SCALE);
188
364
  }
365
+ }, {
366
+ deep: options.deep === true,
367
+ deepPolish: options.deepPolish ?? options.threeAxes,
368
+ ...(options.finishTries !== undefined ? { finishTries: options.finishTries } : {}),
369
+ ...(options.centersBeam !== undefined ? { centersBeam: options.centersBeam } : {}),
370
+ ...(options.edgesPerCenter !== undefined ? { edgesPerCenter: options.edgesPerCenter } : {}),
371
+ ...(options.phase1ExtraDepths !== undefined ? { phase1ExtraDepths: options.phase1ExtraDepths } : {}),
372
+ ...(options.phase1Cap !== undefined ? { phase1Cap: options.phase1Cap } : {}),
373
+ ...(options.center2Wide !== undefined ? { center2Wide: options.center2Wide } : {}),
374
+ ...(options.restarts !== undefined ? { restarts: options.restarts } : {}),
375
+ finishScreenTop: finishOpts.mode === "legacy" ? 0 : finishOpts.screenTop ?? 0,
376
+ ...(options.maxFixMoves !== undefined ? { maxFixMoves: options.maxFixMoves } : {}),
377
+ ...(options.prepCap !== undefined ? { prepCap: options.prepCap } : {}),
378
+ ...(options.p1CandCap !== undefined ? { p1CandCap: options.p1CandCap } : {}),
379
+ ...(options.stagnationCut !== undefined ? { stagnationCut: options.stagnationCut } : {}),
380
+ maxReductionLen: bestReductionLen
381
+ });
382
+ let timeoutHandle;
383
+ // Honest scope: this race cannot interrupt the synchronous CPU-bound
384
+ // search — a setTimeout callback only runs at an event-loop tick, so
385
+ // the guard can fire only if the async solve chain yields for 90 s
386
+ // total. It exists solely so a pathological hang fails loudly
387
+ // (retryable Cube4WallTimeoutError) instead of wedging the host; it
388
+ // has never bound (max measured solve 36.5 s).
389
+ const timeoutPromise = new Promise((_, reject) => {
390
+ timeoutHandle = setTimeout(() => reject(new Cube4WallTimeoutError("4x4 solve: timeout")), 90000);
391
+ });
392
+ let solveOutcome;
393
+ try {
394
+ solveOutcome = await Promise.race([solvePromise, timeoutPromise]);
189
395
  }
190
- const facelets = toCube4Facelets(attemptState);
191
- const result = {
192
- eventId: "444",
193
- scramble: formatCube4Algorithm(scrambleMoves),
194
- moves: scrambleMoves,
195
- quality: "random-state",
196
- generator: "cube4-reduction",
197
- metadata: {
198
- generator: "cube4-reduction",
199
- state: attemptState,
200
- facelets,
201
- faceletString: formatCube4Facelets(facelets),
202
- distribution: "random-state",
203
- ...options.metadata
396
+ finally {
397
+ // The search is work-capped and always settles first: clearing the
398
+ // backstop avoids leaking a live 90s timer per axis (which kept the
399
+ // event loop — and CLI/test processes — alive needlessly).
400
+ clearTimeout(timeoutHandle);
401
+ }
402
+ const { scramble: scrambleMoves, moves: solutionMoves, phases, timings, nodes } = solveOutcome;
403
+ const fullSolution = rot.length === 0
404
+ ? solutionMoves
405
+ : placeRotationFix([], rot, solutionMoves);
406
+ const fullScramble = rot.length === 0
407
+ ? scrambleMoves
408
+ : invertCube4Algorithm(fullSolution);
409
+ if (!bestScrambleMoves || fullScramble.length < bestScrambleMoves.length) {
410
+ bestScrambleMoves = fullScramble;
411
+ bestSolutionMoves = fullSolution;
412
+ bestPhases = phases;
413
+ bestTimings = timings;
414
+ bestNodes = nodes;
415
+ if (phases) {
416
+ bestReductionLen = phases.length12 + phases.length3;
204
417
  }
205
- };
206
- if (options.seed !== undefined) {
207
- result.seed = options.seed;
208
418
  }
209
- return result;
210
419
  }
211
- catch (error) {
212
- lastError = error;
213
- // Retry on deterministic reduction / 3x3 failures (some random states are
214
- // outside the solver's horizon with the current caps).
215
- const msg = error instanceof Error ? error.message : String(error);
216
- const isPhase2Error = typeof error?.name === "string" &&
217
- error.name.includes("Cube3Phase2");
218
- if (error instanceof Error &&
219
- (msg.includes("reduction failed") ||
220
- msg.includes("failed to convert") ||
221
- msg.includes("no valid phase 2") ||
222
- msg.includes("timeout") ||
223
- isPhase2Error ||
224
- msg.includes("Phase 2 solution not found"))) {
225
- continue;
226
- }
227
- throw error;
420
+ catch (err) {
421
+ lastError = err;
228
422
  }
229
423
  }
230
- // All solver attempts exhausted — try cubing.js first, then random-moves.
231
- console.warn("[cube4-scrambler] Reduction solver failed after 5 attempts; trying cubing.js fallback.", lastError);
232
- const cubingJsResult = await generateCube4WithCubingJs();
233
- if (cubingJsResult)
234
- return cubingJsResult;
235
- console.warn("[cube4-scrambler] cubing.js fallback also failed; using random-moves.");
236
- return generateCube4RandomMoves(options);
424
+ if (!bestScrambleMoves || !bestSolutionMoves) {
425
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
426
+ }
427
+ const scrambleMoves = bestScrambleMoves;
428
+ const scrambledState = applyCube4Algorithm(createSolvedCube4State(), scrambleMoves);
429
+ if (!options.skipValidation) {
430
+ // Non-tautological gate: compare against `attemptState` — the state
431
+ // sampled for this seed (the same reference the sampler produced),
432
+ // NOT a state derived from the scramble. The former check
433
+ // (`apply(scramble, invert(scramble)) === solved`) passed by
434
+ // construction and hid frame bugs. A mismatch throws an error the
435
+ // retry loop classifies as retryable: `generateCube4Scramble` then
436
+ // re-runs the solve over this SAME `attemptState` (deep) and finally
437
+ // throws — it never re-samples another target.
438
+ assertCube4ScrambleReproducesTarget(scrambleMoves, attemptState, options.seed);
439
+ }
440
+ const facelets = toCube4Facelets(scrambledState);
441
+ const result = {
442
+ eventId: "444",
443
+ scramble: formatCube4Algorithm(scrambleMoves),
444
+ moves: scrambleMoves,
445
+ quality: "random-state",
446
+ generator: "cube4-reduction",
447
+ metadata: {
448
+ generator: "cube4-reduction",
449
+ // The sampled target — the independent reference the seed stands for
450
+ // (consistent with the 222/333/pymx/skwb/fto scramblers). When
451
+ // validation ran, this is exactly `apply(solved, moves)`; with
452
+ // `skipValidation` it stays the sampled truth so external checks can
453
+ // compare the emitted scramble against it instead of a derived state.
454
+ state: attemptState,
455
+ facelets,
456
+ faceletString: formatCube4Facelets(facelets),
457
+ distribution: "random-state",
458
+ // The scramble's inverse: by construction it solves the state the
459
+ // scramble actually produces, and (validation on) that state is
460
+ // `attemptState`.
461
+ solutionMoves: invertCube4Algorithm(scrambleMoves),
462
+ // Winning-reduction breakdown (unsimplified lengths) + wall-clock
463
+ // split, for tuning and A/B comparisons.
464
+ phases: bestPhases,
465
+ phaseTimings: bestTimings,
466
+ nodes: bestNodes,
467
+ attemptMs: performance.now() - tAttemptStart,
468
+ ...options.metadata
469
+ }
470
+ };
471
+ if (options.seed !== undefined) {
472
+ result.seed = options.seed;
473
+ }
474
+ return result;
237
475
  }
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/state.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAM7C,wBAAgB,sBAAsB,IAAI,UAAU,CAanD;AAMD,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,GAAG,UAAU,CAOzD;AAMD,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAOtE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAE7D;AAMD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAS5D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAUzD"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/state.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAM7C,wBAAgB,sBAAsB,IAAI,UAAU,CAanD;AAMD,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,GAAG,UAAU,CAOzD;AAMD,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAOtE;AAMD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAE7D;AAMD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAS5D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAUzD"}