@cubingcenter/scrambler 0.6.0 → 0.6.2

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 (50) hide show
  1. package/dist/core/types.d.ts +1 -1
  2. package/dist/core/types.d.ts.map +1 -1
  3. package/dist/puzzles/cube3/solver/browser-loader.d.ts.map +1 -1
  4. package/dist/puzzles/cube3/solver/browser-loader.js +26 -1
  5. package/dist/puzzles/cube3/solver/generated/manifest.json +14 -0
  6. package/dist/puzzles/cube3/solver/generated/phase1-udco.bin +1 -0
  7. package/dist/puzzles/cube3/solver/generated/phase1-udeo.bin +1 -0
  8. package/dist/puzzles/cube3/solver/generated-assets.d.ts.map +1 -1
  9. package/dist/puzzles/cube3/solver/generated-assets.js +2 -1
  10. package/dist/puzzles/cube3/solver/node-loader.d.ts.map +1 -1
  11. package/dist/puzzles/cube3/solver/node-loader.js +22 -0
  12. package/dist/puzzles/cube3/solver/pair-browser-loader.d.ts +11 -0
  13. package/dist/puzzles/cube3/solver/pair-browser-loader.d.ts.map +1 -0
  14. package/dist/puzzles/cube3/solver/pair-browser-loader.js +93 -0
  15. package/dist/puzzles/cube3/solver/pair-node-loader.d.ts +6 -0
  16. package/dist/puzzles/cube3/solver/pair-node-loader.d.ts.map +1 -0
  17. package/dist/puzzles/cube3/solver/pair-node-loader.js +79 -0
  18. package/dist/puzzles/cube3/solver/pair-tables-types.d.ts +37 -0
  19. package/dist/puzzles/cube3/solver/pair-tables-types.d.ts.map +1 -0
  20. package/dist/puzzles/cube3/solver/pair-tables-types.js +29 -0
  21. package/dist/puzzles/cube3/solver/pair-tables.d.ts +29 -0
  22. package/dist/puzzles/cube3/solver/pair-tables.d.ts.map +1 -0
  23. package/dist/puzzles/cube3/solver/pair-tables.js +69 -0
  24. package/dist/puzzles/cube3/solver/phase1.d.ts +3 -1
  25. package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
  26. package/dist/puzzles/cube3/solver/phase1.js +32 -2
  27. package/dist/puzzles/cube3/solver/phase2.d.ts +3 -0
  28. package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
  29. package/dist/puzzles/cube3/solver/phase2.js +63 -5
  30. package/dist/puzzles/cube3/solver/tables.d.ts +16 -0
  31. package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
  32. package/dist/puzzles/cube3/solver/tables.js +4 -2
  33. package/dist/puzzles/cube4/scrambler.d.ts +4 -1
  34. package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
  35. package/dist/puzzles/cube4/scrambler.js +101 -14
  36. package/dist-cjs/puzzles/cube3/solver/browser-loader.js +26 -1
  37. package/dist-cjs/puzzles/cube3/solver/generated/manifest.json +14 -0
  38. package/dist-cjs/puzzles/cube3/solver/generated/phase1-udco.bin +1 -0
  39. package/dist-cjs/puzzles/cube3/solver/generated/phase1-udeo.bin +1 -0
  40. package/dist-cjs/puzzles/cube3/solver/generated-assets.js +2 -1
  41. package/dist-cjs/puzzles/cube3/solver/node-loader.js +22 -0
  42. package/dist-cjs/puzzles/cube3/solver/pair-browser-loader.js +96 -0
  43. package/dist-cjs/puzzles/cube3/solver/pair-node-loader.js +85 -0
  44. package/dist-cjs/puzzles/cube3/solver/pair-tables-types.js +33 -0
  45. package/dist-cjs/puzzles/cube3/solver/pair-tables.js +107 -0
  46. package/dist-cjs/puzzles/cube3/solver/phase1.js +32 -2
  47. package/dist-cjs/puzzles/cube3/solver/phase2.js +63 -5
  48. package/dist-cjs/puzzles/cube3/solver/tables.js +4 -2
  49. package/dist-cjs/puzzles/cube4/scrambler.js +101 -15
  50. package/package.json +4 -1
@@ -4,6 +4,7 @@ import { encodePermutation, encodeSlicePermutation, encodeUdEdgePermutation } fr
4
4
  import { isCube3Phase1Solved, solveCube3Phase1 } from "./phase1.js";
5
5
  import { loadCube3Phase1Tables, loadCube3Phase2Tables, CUBE3_PHASE1_MOVES, CUBE3_PHASE2_MOVES } from "./tables.js";
6
6
  import { getMoveFace, getMoveFaceByIndex, shouldSkipCube3SearchMove } from "./search-utils.js";
7
+ import { loadCube3PairTables, pairBound } from "./pair-tables.js";
7
8
  export class Cube3Phase2SearchError extends Error {
8
9
  visitedNodes;
9
10
  constructor(message, visitedNodes) {
@@ -40,11 +41,13 @@ export async function solveCube3Phase2(state, options = {}) {
40
41
  throw new Cube3Phase2SearchError("3x3 Phase 2 requires a state already in G1.", 0);
41
42
  }
42
43
  const tables = options.tables ?? await loadCube3Phase2Tables();
44
+ const pairTables = options.pairTables ?? await loadCube3PairTables();
43
45
  const maxDepth = options.maxDepth ?? 18;
44
46
  const start = encodeCube3Phase2Coordinates(state);
45
47
  const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
46
48
  const context = {
47
49
  tables,
50
+ pairTables,
48
51
  deadline,
49
52
  visitedNodes: 0,
50
53
  timedOut: false
@@ -56,7 +59,13 @@ export async function solveCube3Phase2(state, options = {}) {
56
59
  const combinedEPIndex = start.udEdgePermutation * spRange + start.slicePermutation;
57
60
  const cpDist = (cpspTable[combinedCPIndex >> 1] >> ((combinedCPIndex & 1) << 2)) & 0x0f;
58
61
  const epDist = (epspTable[combinedEPIndex >> 1] >> ((combinedEPIndex & 1) << 2)) & 0x0f;
59
- const minDepth = cpDist > epDist ? cpDist : epDist;
62
+ let minDepth = cpDist > epDist ? cpDist : epDist;
63
+ if (pairTables) {
64
+ const pair = pairBound(pairTables, start.cornerPermutation, start.udEdgePermutation);
65
+ if (pair > minDepth) {
66
+ minDepth = pair;
67
+ }
68
+ }
60
69
  const initialPreviousFace = options.previousFace ?? -1;
61
70
  const initialPreviousPreviousFace = options.previousPreviousFace ?? -1;
62
71
  for (let depth = minDepth; depth <= maxDepth; depth += 1) {
@@ -151,6 +160,38 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
151
160
  const cpOffset = cornerPermutation * moveCount;
152
161
  const epOffset = udEdgePermutation * moveCount;
153
162
  const spOffset = slicePermutation * moveCount;
163
+ // Without the pair tables (the default npm package), keep the exact
164
+ // reference traversal — zero overhead. With them (turbo tier), collect the
165
+ // children with the pair-distance bound and explore in ascending bound
166
+ // order (stable: ties keep the reference move order): same depth-minimal
167
+ // solutions, far fewer nodes.
168
+ if (context.pairTables === undefined) {
169
+ for (let i = 0; i < validMoves.length; i += 1) {
170
+ const moveIndex = validMoves[i];
171
+ const nextCornerPermutation = cpTable[cpOffset + moveIndex];
172
+ const nextUdEdgePermutation = epTable[epOffset + moveIndex];
173
+ const nextSlicePermutation = spTable[spOffset + moveIndex];
174
+ const combinedCPIndex = nextCornerPermutation * spRange + nextSlicePermutation;
175
+ const combinedEPIndex = nextUdEdgePermutation * spRange + nextSlicePermutation;
176
+ const cpDist = (cpspTable[combinedCPIndex >> 1] >> ((combinedCPIndex & 1) << 2)) & 0x0f;
177
+ const epDist = (epspTable[combinedEPIndex >> 1] >> ((combinedEPIndex & 1) << 2)) & 0x0f;
178
+ const nextLowerBound = cpDist > epDist ? cpDist : epDist;
179
+ if (nextLowerBound > remainingDepth - 1) {
180
+ continue;
181
+ }
182
+ path.push(context.tables.phase2Moves[moveIndex]);
183
+ if (searchPhase2(nextCornerPermutation, nextUdEdgePermutation, nextSlicePermutation, PHASE2_MOVE_FACES[moveIndex], previousFace, remainingDepth - 1, path, context)) {
184
+ return true;
185
+ }
186
+ path.pop();
187
+ if (context.timedOut) {
188
+ return false;
189
+ }
190
+ }
191
+ return false;
192
+ }
193
+ const pairTables = context.pairTables;
194
+ const children = [];
154
195
  for (let i = 0; i < validMoves.length; i += 1) {
155
196
  const moveIndex = validMoves[i];
156
197
  const nextCornerPermutation = cpTable[cpOffset + moveIndex];
@@ -160,13 +201,29 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
160
201
  const combinedEPIndex = nextUdEdgePermutation * spRange + nextSlicePermutation;
161
202
  const cpDist = (cpspTable[combinedCPIndex >> 1] >> ((combinedCPIndex & 1) << 2)) & 0x0f;
162
203
  const epDist = (epspTable[combinedEPIndex >> 1] >> ((combinedEPIndex & 1) << 2)) & 0x0f;
163
- const nextLowerBound = cpDist > epDist ? cpDist : epDist;
204
+ let nextLowerBound = cpDist > epDist ? cpDist : epDist;
164
205
  if (nextLowerBound > remainingDepth - 1) {
165
206
  continue;
166
207
  }
167
- const face = PHASE2_MOVE_FACES[moveIndex];
168
- path.push(context.tables.phase2Moves[moveIndex]);
169
- if (searchPhase2(nextCornerPermutation, nextUdEdgePermutation, nextSlicePermutation, face, previousFace, remainingDepth - 1, path, context)) {
208
+ const pair = pairBound(pairTables, nextCornerPermutation, nextUdEdgePermutation);
209
+ if (pair > nextLowerBound) {
210
+ nextLowerBound = pair;
211
+ }
212
+ if (nextLowerBound > remainingDepth - 1) {
213
+ continue;
214
+ }
215
+ children.push({
216
+ moveIndex,
217
+ lowerBound: nextLowerBound,
218
+ cornerPermutation: nextCornerPermutation,
219
+ udEdgePermutation: nextUdEdgePermutation,
220
+ slicePermutation: nextSlicePermutation
221
+ });
222
+ }
223
+ children.sort((left, right) => left.lowerBound - right.lowerBound);
224
+ for (const child of children) {
225
+ path.push(context.tables.phase2Moves[child.moveIndex]);
226
+ if (searchPhase2(child.cornerPermutation, child.udEdgePermutation, child.slicePermutation, PHASE2_MOVE_FACES[child.moveIndex], previousFace, remainingDepth - 1, path, context)) {
170
227
  return true;
171
228
  }
172
229
  path.pop();
@@ -217,6 +274,7 @@ function searchPhase1Interleaved(cornerOrientation, edgeOrientation, udSlice, pr
217
274
  const maxPhase2Depth = 6;
218
275
  const phase2Context = {
219
276
  tables: context.phase2Tables,
277
+ pairTables: undefined,
220
278
  deadline: context.deadline,
221
279
  visitedNodes: 0,
222
280
  timedOut: false
@@ -40,6 +40,20 @@ export interface Cube3SolverTables {
40
40
  cornerPermutationSlicePermutation: Cube3PruningTable;
41
41
  udEdgePermutationSlicePermutation: Cube3PruningTable;
42
42
  };
43
+ jointTables: Cube3JointTables | undefined;
44
+ }
45
+ /**
46
+ * Phase-1 joint tables (ud x co / ud x eo): exact distances to the ud-slice
47
+ * solved together with the corner/edge orientations. Tighten the phase-1
48
+ * bound from max(coeo, ud) to max(coeo, ud, udco, udeo) — a ~7x speedup
49
+ * (the starting minDepth becomes exact, eliminating failing depth
50
+ * iterations). Part of the standard tables (~1 MB).
51
+ */
52
+ export interface Cube3JointTables {
53
+ /** 495 x 2187, 4-bit packed: index = ud * 2187 + cornerOrientation. */
54
+ jointUdCo: Uint8Array;
55
+ /** 495 x 2048, 4-bit packed: index = ud * 2048 + edgeOrientation. */
56
+ jointUdEo: Uint8Array;
43
57
  }
44
58
  export interface Cube3Phase1Tables {
45
59
  phase1Moves: readonly Cube3Move[];
@@ -47,6 +61,8 @@ export interface Cube3Phase1Tables {
47
61
  pruningTables: Pick<Cube3SolverTables["pruningTables"], "cornerOrientation" | "edgeOrientation" | "udSlice"> & {
48
62
  cornerOrientationEdgeOrientation: Cube3PruningTable;
49
63
  };
64
+ /** Present when the manifest ships the joint tables (default). */
65
+ jointTables: Cube3JointTables | undefined;
50
66
  }
51
67
  export interface Cube3Phase2Tables {
52
68
  phase2Moves: readonly Cube3Move[];
@@ -1 +1 @@
1
- {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/tables.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/tables.ts"],"names":[],"mappings":"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"}
@@ -46,7 +46,8 @@ export function generateCube3Phase1Tables() {
46
46
  edgeOrientation: generateCube3PruningTable(edgeOrientation),
47
47
  udSlice: generateCube3PruningTable(udSlice),
48
48
  cornerOrientationEdgeOrientation: generateCube3CombinedPruningTable(cornerOrientation, edgeOrientation)
49
- }
49
+ },
50
+ jointTables: undefined
50
51
  };
51
52
  }
52
53
  export function generateCube3SolverTables() {
@@ -73,7 +74,8 @@ export function generateCube3SolverTables() {
73
74
  slicePermutation: phase2Tables.pruningTables.slicePermutation,
74
75
  cornerPermutationSlicePermutation: phase2Tables.pruningTables.cornerPermutationSlicePermutation,
75
76
  udEdgePermutationSlicePermutation: phase2Tables.pruningTables.udEdgePermutationSlicePermutation
76
- }
77
+ },
78
+ jointTables: undefined
77
79
  };
78
80
  }
79
81
  export function generateCube3Phase2Tables() {
@@ -2,9 +2,12 @@
2
2
  * The 4x4 scramble generator: sample a uniform random state, solve it with
3
3
  * the reduction solver (phases 1-4), and invert the solution. The scramble
4
4
  * always reproduces the sampled target state and facelets exactly.
5
+ *
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.
5
9
  */
6
10
  import type { ScrambleGenerator, ScrambleOptions, ScrambleResult } from "../../core/index.js";
7
- export declare function isCube4TablesLoaded(): boolean;
8
11
  export declare const cube4ScrambleGenerator: ScrambleGenerator;
9
12
  export declare function generateCube4Scramble(options?: ScrambleOptions & {
10
13
  skipValidation?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/scrambler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA4C9F,wBAAgB,mBAAmB,IAAI,OAAO,CAU7C;AAED,eAAO,MAAM,sBAAsB,EAAE,iBAYpC,CAAC;AAEF,wBAAsB,qBAAqB,CACzC,OAAO,GAAE,eAAe,GAAG;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3D,OAAO,CAAC,cAAc,CAAC,CAkFzB"}
1
+ {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/scrambler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAmJ9F,eAAO,MAAM,sBAAsB,EAAE,iBAYpC,CAAC;AAEF,wBAAsB,qBAAqB,CACzC,OAAO,GAAE,eAAe,GAAG;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3D,OAAO,CAAC,cAAc,CAAC,CAyFzB"}
@@ -2,6 +2,10 @@
2
2
  * The 4x4 scramble generator: sample a uniform random state, solve it with
3
3
  * the reduction solver (phases 1-4), and invert the solution. The scramble
4
4
  * always reproduces the sampled target state and facelets exactly.
5
+ *
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.
5
9
  */
6
10
  import { formatCube4Algorithm, applyCube4Algorithm } from "./moves.js";
7
11
  import { generateRandomCube4State } from "./random-state.js";
@@ -9,6 +13,95 @@ import { createSolvedCube4State, cube4StatesEqual } from "./state.js";
9
13
  import { formatCube4Facelets, toCube4Facelets } from "./facelets.js";
10
14
  import { solveCube4State } from "./reduction/solve.js";
11
15
  import { solveCube3State } from "../cube3/solver/index.js";
16
+ import { createRandomSource } from "../../random/index.js";
17
+ // ────────────────────────────────────────────────────────────
18
+ // cubing.js fallback (random-state, WCA-compliant)
19
+ // ────────────────────────────────────────────────────────────
20
+ /**
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.
24
+ */
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
+ };
103
+ }
104
+ // ────────────────────────────────────────────────────────────
12
105
  let cachedTables;
13
106
  async function loadCube4Tables(baseUrl) {
14
107
  if (cachedTables)
@@ -40,18 +133,6 @@ async function loadCube4Tables(baseUrl) {
40
133
  cachedTables = tables;
41
134
  return tables;
42
135
  }
43
- export function isCube4TablesLoaded() {
44
- // For the scrambler retry logic, we need to know if tables are already
45
- // loaded to avoid re-loading on every attempt.
46
- try {
47
- // @ts-ignore
48
- const loader = globalThis;
49
- return false;
50
- }
51
- catch {
52
- return false;
53
- }
54
- }
55
136
  export const cube4ScrambleGenerator = {
56
137
  info: {
57
138
  eventId: "444",
@@ -59,7 +140,7 @@ export const cube4ScrambleGenerator = {
59
140
  puzzle: "cube-4x4",
60
141
  quality: "random-state",
61
142
  generator: "cube4-reduction",
62
- description: "Random-state 4x4 scramble via the reduction solver (centers + pairing + 3x3 finish)."
143
+ description: "Random-state 4x4 scramble via the reduction solver (centers + pairing + 3x3 finish). Falls back to random-moves if the solver fails."
63
144
  },
64
145
  generate(options = {}) {
65
146
  return generateCube4Scramble(options);
@@ -136,5 +217,11 @@ export async function generateCube4Scramble(options = {}) {
136
217
  throw error;
137
218
  }
138
219
  }
139
- throw lastError;
220
+ // All solver attempts exhausted — try cubing.js first, then random-moves.
221
+ console.warn("[cube4-scrambler] Reduction solver failed after 5 attempts; trying cubing.js fallback.", lastError);
222
+ const cubingJsResult = await generateCube4WithCubingJs();
223
+ if (cubingJsResult)
224
+ return cubingJsResult;
225
+ console.warn("[cube4-scrambler] cubing.js fallback also failed; using random-moves.");
226
+ return generateCube4RandomMoves(options);
140
227
  }
@@ -53,10 +53,35 @@ async function loadBrowserTables(baseUrl) {
53
53
  slicePermutation: await fetchPruningTable(resolvedBase, manifest.pruningTables.slicePermutation),
54
54
  cornerPermutationSlicePermutation: await fetchPruningTable(resolvedBase, manifest.pruningTables.cornerPermutationSlicePermutation),
55
55
  udEdgePermutationSlicePermutation: await fetchPruningTable(resolvedBase, manifest.pruningTables.udEdgePermutationSlicePermutation)
56
- }
56
+ },
57
+ jointTables: await fetchJointTables(resolvedBase, manifest.jointTables)
57
58
  };
58
59
  return browserTables;
59
60
  }
61
+ async function fetchJointTables(baseUrl, entries) {
62
+ if (!entries?.udCo || !entries.udEo) {
63
+ return undefined;
64
+ }
65
+ const fetchJoint = async (entry) => {
66
+ const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
67
+ cacheName: CACHE_NAME,
68
+ compressed: entry.compressed !== false
69
+ });
70
+ const expectedBytes = Math.ceil((entry.rangeA * entry.rangeB) / 2);
71
+ if (buffer.byteLength !== expectedBytes) {
72
+ throw new Error(`Invalid byte size for ${entry.file}.`);
73
+ }
74
+ const values = new Uint8Array(buffer);
75
+ if (checksum(values) !== entry.checksum) {
76
+ throw new Error(`Checksum mismatch for ${entry.file}.`);
77
+ }
78
+ return values;
79
+ };
80
+ return {
81
+ jointUdCo: await fetchJoint(entries.udCo),
82
+ jointUdEo: await fetchJoint(entries.udEo)
83
+ };
84
+ }
60
85
  async function fetchManifest(baseUrl) {
61
86
  const url = `${baseUrl}/puzzles/cube3/solver/generated/manifest.json`;
62
87
  const response = await fetch(url);
@@ -400,5 +400,19 @@
400
400
  "checksum": 2840452350,
401
401
  "maxDepth": 12
402
402
  }
403
+ },
404
+ "jointTables": {
405
+ "udCo": {
406
+ "file": "phase1-udco.bin",
407
+ "rangeA": 495,
408
+ "rangeB": 2187,
409
+ "checksum": 1999233281
410
+ },
411
+ "udEo": {
412
+ "file": "phase1-udeo.bin",
413
+ "rangeA": 495,
414
+ "rangeB": 2048,
415
+ "checksum": 4017545214
416
+ }
403
417
  }
404
418
  }