@cubingcenter/scrambler 0.4.1 → 0.5.1

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 (83) hide show
  1. package/README.md +21 -0
  2. package/dist/core/api.d.ts.map +1 -1
  3. package/dist/core/api.js +6 -1
  4. package/dist/puzzles/pyraminx/facelets.d.ts.map +1 -1
  5. package/dist/puzzles/pyraminx/facelets.js +43 -17
  6. package/dist/puzzles/pyraminx/random-state.d.ts.map +1 -1
  7. package/dist/puzzles/pyraminx/random-state.js +63 -29
  8. package/dist/puzzles/pyraminx/scrambler.d.ts.map +1 -1
  9. package/dist/puzzles/pyraminx/scrambler.js +35 -26
  10. package/dist/puzzles/pyraminx/solver/search.d.ts +3 -0
  11. package/dist/puzzles/pyraminx/solver/search.d.ts.map +1 -1
  12. package/dist/puzzles/pyraminx/solver/search.js +60 -5
  13. package/dist/puzzles/pyraminx/solver/tables.d.ts +1 -0
  14. package/dist/puzzles/pyraminx/solver/tables.d.ts.map +1 -1
  15. package/dist/puzzles/pyraminx/solver/tables.js +3 -0
  16. package/dist/puzzles/skewb/facelets.d.ts.map +1 -1
  17. package/dist/puzzles/skewb/facelets.js +46 -23
  18. package/dist/puzzles/skewb/index.d.ts +2 -1
  19. package/dist/puzzles/skewb/index.d.ts.map +1 -1
  20. package/dist/puzzles/skewb/index.js +2 -1
  21. package/dist/puzzles/skewb/random-state.d.ts +1 -1
  22. package/dist/puzzles/skewb/random-state.d.ts.map +1 -1
  23. package/dist/puzzles/skewb/random-state.js +25 -18
  24. package/dist/puzzles/skewb/scrambler.d.ts.map +1 -1
  25. package/dist/puzzles/skewb/scrambler.js +27 -16
  26. package/dist/puzzles/skewb/solver/browser-loader.d.ts +20 -0
  27. package/dist/puzzles/skewb/solver/browser-loader.d.ts.map +1 -0
  28. package/dist/puzzles/skewb/solver/browser-loader.js +86 -0
  29. package/dist/puzzles/skewb/solver/generated/centers-table.bin +0 -0
  30. package/dist/puzzles/skewb/solver/generated/helpers.bin +0 -0
  31. package/dist/puzzles/skewb/solver/generated/manifest.json +31 -0
  32. package/dist/puzzles/skewb/solver/generated/orientation-table.bin +0 -0
  33. package/dist/puzzles/skewb/solver/generated/permutation-table.bin +0 -0
  34. package/dist/puzzles/skewb/solver/generated/policy.bin +0 -0
  35. package/dist/puzzles/skewb/solver/generated-assets.d.ts +10 -0
  36. package/dist/puzzles/skewb/solver/generated-assets.d.ts.map +1 -0
  37. package/dist/puzzles/skewb/solver/generated-assets.js +24 -0
  38. package/dist/puzzles/skewb/solver/index.d.ts +5 -0
  39. package/dist/puzzles/skewb/solver/index.d.ts.map +1 -0
  40. package/dist/puzzles/skewb/solver/index.js +4 -0
  41. package/dist/puzzles/skewb/solver/node-loader.d.ts +10 -0
  42. package/dist/puzzles/skewb/solver/node-loader.d.ts.map +1 -0
  43. package/dist/puzzles/skewb/solver/node-loader.js +91 -0
  44. package/dist/puzzles/skewb/solver/search.d.ts +14 -0
  45. package/dist/puzzles/skewb/solver/search.d.ts.map +1 -0
  46. package/dist/puzzles/skewb/solver/search.js +120 -0
  47. package/dist/puzzles/skewb/solver/table-metadata.d.ts +27 -0
  48. package/dist/puzzles/skewb/solver/table-metadata.d.ts.map +1 -0
  49. package/dist/puzzles/skewb/solver/table-metadata.js +26 -0
  50. package/dist/puzzles/skewb/solver/tables.d.ts +69 -0
  51. package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -0
  52. package/dist/puzzles/skewb/solver/tables.js +511 -0
  53. package/dist/random/prng.d.ts.map +1 -1
  54. package/dist/random/prng.js +25 -2
  55. package/dist-cjs/core/api.js +6 -1
  56. package/dist-cjs/puzzles/pyraminx/facelets.js +43 -17
  57. package/dist-cjs/puzzles/pyraminx/random-state.js +63 -29
  58. package/dist-cjs/puzzles/pyraminx/scrambler.js +34 -25
  59. package/dist-cjs/puzzles/pyraminx/solver/search.js +62 -4
  60. package/dist-cjs/puzzles/pyraminx/solver/tables.js +4 -0
  61. package/dist-cjs/puzzles/skewb/facelets.js +46 -23
  62. package/dist-cjs/puzzles/skewb/index.js +49 -11
  63. package/dist-cjs/puzzles/skewb/random-state.js +25 -18
  64. package/dist-cjs/puzzles/skewb/scrambler.js +26 -15
  65. package/dist-cjs/puzzles/skewb/solver/browser-loader.js +91 -0
  66. package/dist-cjs/puzzles/skewb/solver/generated/centers-table.bin +0 -0
  67. package/dist-cjs/puzzles/skewb/solver/generated/helpers.bin +0 -0
  68. package/dist-cjs/puzzles/skewb/solver/generated/manifest.json +31 -0
  69. package/dist-cjs/puzzles/skewb/solver/generated/orientation-table.bin +0 -0
  70. package/dist-cjs/puzzles/skewb/solver/generated/permutation-table.bin +0 -0
  71. package/dist-cjs/puzzles/skewb/solver/generated/policy.bin +0 -0
  72. package/dist-cjs/puzzles/skewb/solver/generated-assets.js +60 -0
  73. package/dist-cjs/puzzles/skewb/solver/index.js +23 -0
  74. package/dist-cjs/puzzles/skewb/solver/node-loader.js +97 -0
  75. package/dist-cjs/puzzles/skewb/solver/search.js +130 -0
  76. package/dist-cjs/puzzles/skewb/solver/table-metadata.js +29 -0
  77. package/dist-cjs/puzzles/skewb/solver/tables.js +543 -0
  78. package/dist-cjs/random/prng.js +25 -2
  79. package/package.json +6 -3
  80. package/dist/puzzles/skewb/solver.d.ts +0 -11
  81. package/dist/puzzles/skewb/solver.d.ts.map +0 -1
  82. package/dist/puzzles/skewb/solver.js +0 -149
  83. package/dist-cjs/puzzles/skewb/solver.js +0 -160
@@ -5,7 +5,8 @@ export { cloneSkewbState, createSolvedSkewbState, isSolvedSkewbState, skewbState
5
5
  export { permutationParity, validateSkewbState, isAllowedRandomSkewbState, isOneMoveFromSolvedSkewbState } from "./validation.js";
6
6
  export { generateRandomSkewbState } from "./random-state.js";
7
7
  export { createSolvedSkewbFacelets, formatSkewbFacelets, getSkewbFaceOrder, toSkewbFacelets } from "./facelets.js";
8
- export { solveSkewb, getStateIndex, getStateFromIndex } from "./solver.js";
8
+ export { solveSkewb, solveSkewbWithInfo, solveSkewbSync, verifySkewbScramble, verifySkewbSolution, getStateIndex, getStateFromIndex, } from "./solver/index.js";
9
+ export * as solver from "./solver/index.js";
9
10
  export { generateSkewbScramble, skewbScrambleGenerator } from "./scrambler.js";
10
11
  export { generateScramble, generateScrambles, getEventInfo, getSupportedEvents, initializeScrambler } from "../../core/index.js";
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACtD,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC/G,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AAClI,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACnH,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAE/E,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACtD,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC/G,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAC;AAClI,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACnH,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAE/E,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC"}
@@ -7,6 +7,7 @@ export { cloneSkewbState, createSolvedSkewbState, isSolvedSkewbState, skewbState
7
7
  export { permutationParity, validateSkewbState, isAllowedRandomSkewbState, isOneMoveFromSolvedSkewbState } from "./validation.js";
8
8
  export { generateRandomSkewbState } from "./random-state.js";
9
9
  export { createSolvedSkewbFacelets, formatSkewbFacelets, getSkewbFaceOrder, toSkewbFacelets } from "./facelets.js";
10
- export { solveSkewb, getStateIndex, getStateFromIndex } from "./solver.js";
10
+ export { solveSkewb, solveSkewbWithInfo, solveSkewbSync, verifySkewbScramble, verifySkewbSolution, getStateIndex, getStateFromIndex, } from "./solver/index.js";
11
+ export * as solver from "./solver/index.js";
11
12
  export { generateSkewbScramble, skewbScrambleGenerator } from "./scrambler.js";
12
13
  export { generateScramble, generateScrambles, getEventInfo, getSupportedEvents, initializeScrambler } from "../../core/index.js";
@@ -1,3 +1,3 @@
1
1
  import type { SkewbRandomStateOptions, SkewbState } from "./types.js";
2
- export declare function generateRandomSkewbState(options?: SkewbRandomStateOptions): SkewbState;
2
+ export declare function generateRandomSkewbState(options?: SkewbRandomStateOptions): Promise<SkewbState>;
3
3
  //# sourceMappingURL=random-state.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"random-state.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/random-state.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAMtE,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,uBAA4B,GAAG,UAAU,CAS1F"}
1
+ {"version":3,"file":"random-state.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/random-state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAatE,wBAAsB,wBAAwB,CAC5C,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,UAAU,CAAC,CAiBrB"}
@@ -1,27 +1,34 @@
1
1
  import { createRandomSource } from "../../random/index.js";
2
- import { isAllowedRandomSkewbState } from "./validation.js";
3
- import { applySkewbMove } from "./moves.js";
4
- import { createSolvedSkewbState } from "./state.js";
5
- import { SKEWB_MOVES } from "./types.js";
6
- export function generateRandomSkewbState(options = {}) {
2
+ import { ensureTablesLoaded, getCentersTransition, getOrientationTransition, getPermutationTransition, getStateFromRank, MOVE_COUNT, rankFromComponents, SOLVED_RANK, STATE_COUNT, } from "./solver/index.js";
3
+ export async function generateRandomSkewbState(options = {}) {
4
+ await ensureTablesLoaded();
7
5
  const random = createRandomSource(options.seed);
8
6
  const rejectTrivial = options.rejectTrivial ?? true;
7
+ const trivialRanks = rejectTrivial ? getTrivialRanks() : null;
8
+ // Exact uniform sampling: the compact rank is a bijection over the
9
+ // 3,149,280 reachable states, so a uniform rank gives every state exactly
10
+ // the same probability (unlike a finite random walk, which only approaches
11
+ // uniformity). Trivial states (solved / one move from solved) are rejected
12
+ // and resampled, which keeps the conditional distribution uniform.
9
13
  for (let attempt = 0; attempt < 100; attempt++) {
10
- const state = createLegalRandomState(random);
11
- if (!rejectTrivial || isAllowedRandomSkewbState(state))
12
- return state;
14
+ const rank = random.nextInt(STATE_COUNT);
15
+ if (trivialRanks && trivialRanks.has(rank))
16
+ continue;
17
+ return getStateFromRank(rank);
13
18
  }
14
19
  throw new Error("Unable to generate a non-trivial random Skewb state.");
15
20
  }
16
- function createLegalRandomState(random) {
17
- // Generate a random reachable Skewb state via random walk from solved.
18
- // Skewb has 3,149,280 reachable states with WCA moves (fixed corner).
19
- // A walk of 30+ moves is sufficient to approach uniform distribution.
20
- let state = createSolvedSkewbState();
21
- const walkLength = 30 + random.nextInt(5); // 30-34 moves
22
- for (let i = 0; i < walkLength; i++) {
23
- const move = random.choice([...SKEWB_MOVES]);
24
- state = applySkewbMove(state, move);
21
+ let trivialRanks = null;
22
+ function getTrivialRanks() {
23
+ if (!trivialRanks) {
24
+ const ranks = new Set([SOLVED_RANK]);
25
+ for (let m = 0; m < MOVE_COUNT; m++) {
26
+ const r7 = getPermutationTransition(0, m);
27
+ const rO = getOrientationTransition(0, m);
28
+ const rC = getCentersTransition(0, m);
29
+ ranks.add(rankFromComponents(r7, rO, rC));
30
+ }
31
+ trivialRanks = ranks;
25
32
  }
26
- return state;
33
+ return trivialRanks;
27
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAO9F,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,CAiCzB"}
1
+ {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/skewb/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAa9F,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,CAgDzB"}
@@ -1,7 +1,6 @@
1
1
  import { generateRandomSkewbState } from "./random-state.js";
2
- import { createSolvedSkewbState, isSolvedSkewbState, skewbStatesEqual } from "./state.js";
3
- import { applySkewbAlgorithm, formatSkewbAlgorithm, invertSkewbAlgorithm } from "./moves.js";
4
- import { solveSkewb } from "./solver.js";
2
+ import { formatSkewbAlgorithm } from "./moves.js";
3
+ import { areSkewbTablesLoaded, solveSkewbIndices, solveSkewbIndicesSync, verifySkewbScrambleIndices, SKEWB_MOVES } from "./solver/index.js";
5
4
  import { formatSkewbFacelets, toSkewbFacelets } from "./facelets.js";
6
5
  export const skewbScrambleGenerator = {
7
6
  info: {
@@ -9,41 +8,53 @@ export const skewbScrambleGenerator = {
9
8
  name: "Skewb",
10
9
  puzzle: "skewb",
11
10
  quality: "random-state",
12
- generator: "skewb-bfs",
13
- description: "Random-state Skewb scramble generated by an optimal bidirectional BFS solver.",
11
+ generator: "skewb-optimal",
12
+ description: "Random-state Skewb scramble generated by an optimal solver with precalculated tables.",
14
13
  },
15
14
  generate(options = {}) {
16
15
  return generateSkewbScramble(options);
17
16
  },
18
17
  };
19
18
  export async function generateSkewbScramble(options = {}) {
20
- const state = generateRandomSkewbState(options);
21
- const solution = solveSkewb(state);
22
- const scrambleMoves = invertSkewbAlgorithm(solution);
19
+ const state = await generateRandomSkewbState(options);
20
+ // generateRandomSkewbState guarantees the tables are loaded, so use the
21
+ // sync solve on every call after the first (no extra microtask).
22
+ const solution = areSkewbTablesLoaded()
23
+ ? solveSkewbIndicesSync(state)
24
+ : await solveSkewbIndices(state);
25
+ const depth = solution.length;
26
+ // Scramble = inverse of the solution, reversed. For index m the inverse is
27
+ // m ^ 1 (moves are paired R/R', U/U', L/L', B/B').
28
+ const scrambleMoves = new Array(depth);
29
+ for (let index = 0; index < depth; index += 1) {
30
+ scrambleMoves[index] = SKEWB_MOVES[solution[depth - 1 - index] ^ 1];
31
+ }
23
32
  if (!options.skipValidation) {
24
- const scrambledState = applySkewbAlgorithm(createSolvedSkewbState(), scrambleMoves);
25
- if (!skewbStatesEqual(scrambledState, state)) {
33
+ // If the scramble reproduces the target state, its inverse (the
34
+ // solution) solves it by construction, so a single walk suffices.
35
+ if (!verifySkewbScrambleIndices(state, solution)) {
26
36
  throw new Error("Generated Skewb scramble does not reproduce the target random state.");
27
37
  }
28
- if (!isSolvedSkewbState(applySkewbAlgorithm(state, solution))) {
29
- throw new Error("Generated Skewb solution does not solve the target random state.");
30
- }
31
38
  if (scrambleMoves.length < 2)
32
39
  throw new Error("Generated Skewb scramble is too short.");
33
40
  }
34
41
  const facelets = toSkewbFacelets(state);
42
+ const solutionMoves = new Array(depth);
43
+ for (let index = 0; index < depth; index += 1) {
44
+ solutionMoves[index] = SKEWB_MOVES[solution[index]];
45
+ }
35
46
  const result = {
36
47
  eventId: "skwb",
37
48
  scramble: formatSkewbAlgorithm(scrambleMoves),
38
49
  moves: scrambleMoves,
39
50
  quality: "random-state",
40
- generator: "skewb-bfs",
51
+ generator: "skewb-optimal",
41
52
  metadata: {
42
53
  state,
43
54
  facelets,
44
55
  faceletString: formatSkewbFacelets(facelets),
45
- solutionMoves: solution,
46
- solutionDepth: solution.length,
56
+ solutionMoves,
57
+ solutionDepth: depth,
47
58
  },
48
59
  };
49
60
  if (options.seed !== undefined)
@@ -0,0 +1,20 @@
1
+ export declare function getCachedBrowserTables(): {
2
+ permutationTable: Uint16Array;
3
+ orientationTable: Uint16Array;
4
+ centersTable: Uint16Array;
5
+ policyTable: Uint8Array;
6
+ evenCenterRank: Uint16Array;
7
+ orientClass: Uint8Array;
8
+ r7Sets: Uint16Array;
9
+ } | undefined;
10
+ export declare function clearCachedBrowserTables(): void;
11
+ export declare function loadBrowserTables(baseUrl?: string): Promise<{
12
+ permutationTable: Uint16Array;
13
+ orientationTable: Uint16Array;
14
+ centersTable: Uint16Array;
15
+ policyTable: Uint8Array;
16
+ evenCenterRank: Uint16Array;
17
+ orientClass: Uint8Array;
18
+ r7Sets: Uint16Array;
19
+ }>;
20
+ //# sourceMappingURL=browser-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/browser-loader.ts"],"names":[],"mappings":"AAeA,wBAAgB,sBAAsB;sBATlB,WAAW;sBACX,WAAW;kBACf,WAAW;iBACZ,UAAU;oBACP,WAAW;iBACd,UAAU;YACf,WAAW;cAKpB;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAED,wBAAsB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACjE,gBAAgB,EAAE,WAAW,CAAC;IAC9B,gBAAgB,EAAE,WAAW,CAAC;IAC9B,YAAY,EAAE,WAAW,CAAC;IAC1B,WAAW,EAAE,UAAU,CAAC;IACxB,cAAc,EAAE,WAAW,CAAC;IAC5B,WAAW,EAAE,UAAU,CAAC;IACxB,MAAM,EAAE,WAAW,CAAC;CACrB,CAAC,CAwCD"}
@@ -0,0 +1,86 @@
1
+ import { SKEWB_SOLVER_TABLE_METADATA } from "./table-metadata.js";
2
+ import { fetchTableBuffer } from "../../shared/compressed-fetch.js";
3
+ const TABLES_DIR = "puzzles/skewb/solver/generated";
4
+ let cachedBrowserTables;
5
+ export function getCachedBrowserTables() {
6
+ return cachedBrowserTables;
7
+ }
8
+ export function clearCachedBrowserTables() {
9
+ cachedBrowserTables = undefined;
10
+ }
11
+ export async function loadBrowserTables(baseUrl) {
12
+ if (cachedBrowserTables) {
13
+ return cachedBrowserTables;
14
+ }
15
+ const resolved = resolveBaseUrl(baseUrl);
16
+ const manifest = await fetchManifest(resolved);
17
+ const [permTable, orientTable, centersTable, helpers, policy] = await Promise.all([
18
+ fetchUint16Table(resolved, manifest.files.permutationTable, SKEWB_SOLVER_TABLE_METADATA.permutationTable.entries),
19
+ fetchUint16Table(resolved, manifest.files.orientationTable, SKEWB_SOLVER_TABLE_METADATA.orientationTable.entries),
20
+ fetchUint16Table(resolved, manifest.files.centersTable, SKEWB_SOLVER_TABLE_METADATA.centersTable.entries),
21
+ fetchUint8Table(resolved, manifest.files.helpers, SKEWB_SOLVER_TABLE_METADATA.helpers.entries),
22
+ fetchUint8Table(resolved, manifest.files.policyTable, SKEWB_SOLVER_TABLE_METADATA.policyTable.entries)
23
+ ]);
24
+ const helpersOffset = SKEWB_SOLVER_TABLE_METADATA.helpers;
25
+ const evenCenterRank = new Uint16Array(helpersOffset.evenCenterRankBytes / 2);
26
+ const rankView = new DataView(helpers.buffer, helpers.byteOffset);
27
+ for (let i = 0; i < evenCenterRank.length; i++) {
28
+ evenCenterRank[i] = rankView.getUint16(i * 2, true);
29
+ }
30
+ const orientClass = helpers.subarray(helpersOffset.evenCenterRankBytes, helpersOffset.evenCenterRankBytes + helpersOffset.orientClassBytes);
31
+ const r7Sets = new Uint16Array(helpersOffset.r7SetsEntries);
32
+ const view = new DataView(helpers.buffer, helpers.byteOffset + helpersOffset.evenCenterRankBytes + helpersOffset.orientClassBytes);
33
+ for (let i = 0; i < r7Sets.length; i++) {
34
+ r7Sets[i] = view.getUint16(i * 2, true);
35
+ }
36
+ cachedBrowserTables = {
37
+ permutationTable: permTable,
38
+ orientationTable: orientTable,
39
+ centersTable,
40
+ policyTable: policy,
41
+ evenCenterRank,
42
+ orientClass,
43
+ r7Sets
44
+ };
45
+ return cachedBrowserTables;
46
+ }
47
+ function resolveBaseUrl(provided) {
48
+ if (provided) {
49
+ return provided.replace(/\/$/, "");
50
+ }
51
+ if (typeof document !== "undefined") {
52
+ const script = document.currentScript;
53
+ if (script?.src) {
54
+ const base = new URL(".", script.src).href;
55
+ return base.replace(/\/$/, "");
56
+ }
57
+ }
58
+ return ".";
59
+ }
60
+ async function fetchManifest(baseUrl) {
61
+ const url = `${baseUrl}/puzzles/skewb/solver/generated/manifest.json`;
62
+ const response = await fetch(url);
63
+ if (!response.ok) {
64
+ throw new Error(`Failed to load skewb solver table manifest from ${url}: ${response.status} ${response.statusText}`);
65
+ }
66
+ return await response.json();
67
+ }
68
+ async function fetchUint16Table(baseUrl, entry, expectedEntries) {
69
+ const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, { compressed: entry.compressed !== false });
70
+ if (buffer.byteLength !== expectedEntries * 2) {
71
+ throw new Error(`Invalid byte size for ${entry.file}`);
72
+ }
73
+ const values = new Uint16Array(expectedEntries);
74
+ const view = new DataView(buffer);
75
+ for (let i = 0; i < values.length; i++) {
76
+ values[i] = view.getUint16(i * 2, true);
77
+ }
78
+ return values;
79
+ }
80
+ async function fetchUint8Table(baseUrl, entry, expectedEntries) {
81
+ const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, { compressed: entry.compressed !== false });
82
+ if (buffer.byteLength !== expectedEntries) {
83
+ throw new Error(`Invalid byte size for ${entry.file}`);
84
+ }
85
+ return new Uint8Array(buffer);
86
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "version": 1,
3
+ "files": {
4
+ "permutationTable": {
5
+ "file": "permutation-table.bin",
6
+ "entries": 40320,
7
+ "checksum": 3073532981
8
+ },
9
+ "orientationTable": {
10
+ "file": "orientation-table.bin",
11
+ "entries": 17496,
12
+ "checksum": 2128852053
13
+ },
14
+ "centersTable": {
15
+ "file": "centers-table.bin",
16
+ "entries": 5760,
17
+ "checksum": 360885765
18
+ },
19
+ "helpers": {
20
+ "file": "helpers.bin",
21
+ "entries": 29547,
22
+ "checksum": 4284555735
23
+ },
24
+ "policyTable": {
25
+ "file": "policy.bin",
26
+ "entries": 1180980,
27
+ "checksum": 1294485891,
28
+ "maxDepth": 11
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,10 @@
1
+ export declare function loadGeneratedSkewbSolverTables(): Promise<{
2
+ permutationTable: Uint16Array;
3
+ orientationTable: Uint16Array;
4
+ centersTable: Uint16Array;
5
+ policyTable: Uint8Array;
6
+ evenCenterRank: Uint16Array;
7
+ orientClass: Uint8Array;
8
+ r7Sets: Uint16Array;
9
+ }>;
10
+ //# sourceMappingURL=generated-assets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated-assets.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/generated-assets.ts"],"names":[],"mappings":"AAgBA,wBAAsB,8BAA8B,IAAI,OAAO,CAAC;IAC9D,gBAAgB,EAAE,WAAW,CAAC;IAC9B,gBAAgB,EAAE,WAAW,CAAC;IAC9B,YAAY,EAAE,WAAW,CAAC;IAC1B,WAAW,EAAE,UAAU,CAAC;IACxB,cAAc,EAAE,WAAW,CAAC;IAC5B,WAAW,EAAE,UAAU,CAAC;IACxB,MAAM,EAAE,WAAW,CAAC;CACrB,CAAC,CAYD"}
@@ -0,0 +1,24 @@
1
+ import { getCachedBrowserTables } from "./browser-loader.js";
2
+ let cachedTables;
3
+ function isBrowser() {
4
+ return typeof self !== "undefined";
5
+ }
6
+ export async function loadGeneratedSkewbSolverTables() {
7
+ if (isBrowser()) {
8
+ const browserTables = getCachedBrowserTables();
9
+ if (browserTables)
10
+ return browserTables;
11
+ throw new Error("Skewb solver tables are not loaded. " +
12
+ "In browser environments, call `initializeScrambler(baseUrl)` " +
13
+ "before generating scrambles.");
14
+ }
15
+ cachedTables ??= await loadNodeTables();
16
+ return cachedTables;
17
+ }
18
+ async function loadNodeTables() {
19
+ const loader = await import(
20
+ /* webpackIgnore: true */
21
+ /* @vite-ignore */
22
+ "./node-loader.js");
23
+ return loader.readGeneratedSkewbSolverTables();
24
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./tables.js";
2
+ export * from "./search.js";
3
+ export { SKEWB_SOLVER_TABLE_METADATA } from "./table-metadata.js";
4
+ export { loadGeneratedSkewbSolverTables } from "./generated-assets.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,8BAA8B,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from "./tables.js";
2
+ export * from "./search.js";
3
+ export { SKEWB_SOLVER_TABLE_METADATA } from "./table-metadata.js";
4
+ export { loadGeneratedSkewbSolverTables } from "./generated-assets.js";
@@ -0,0 +1,10 @@
1
+ export declare function readGeneratedSkewbSolverTables(): {
2
+ permutationTable: Uint16Array;
3
+ orientationTable: Uint16Array;
4
+ centersTable: Uint16Array;
5
+ policyTable: Uint8Array;
6
+ evenCenterRank: Uint16Array;
7
+ orientClass: Uint8Array;
8
+ r7Sets: Uint16Array;
9
+ };
10
+ //# sourceMappingURL=node-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/node-loader.ts"],"names":[],"mappings":"AAkBA,wBAAgB,8BAA8B,IAAI;IAChD,gBAAgB,EAAE,WAAW,CAAC;IAC9B,gBAAgB,EAAE,WAAW,CAAC;IAC9B,YAAY,EAAE,WAAW,CAAC;IAC1B,WAAW,EAAE,UAAU,CAAC;IACxB,cAAc,EAAE,WAAW,CAAC;IAC5B,WAAW,EAAE,UAAU,CAAC;IACxB,MAAM,EAAE,WAAW,CAAC;CACrB,CA6DA"}
@@ -0,0 +1,91 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import { SKEWB_SOLVER_TABLE_METADATA } from "./table-metadata.js";
5
+ const GENERATED_DIRECTORY = "puzzles/skewb/solver/generated";
6
+ export function readGeneratedSkewbSolverTables() {
7
+ const assetDirectory = resolveGeneratedAssetDirectory();
8
+ const manifest = JSON.parse(readFileSync(path.join(assetDirectory, "manifest.json"), "utf8"));
9
+ if (manifest.version !== 1) {
10
+ throw new Error(`Unsupported skewb solver table manifest version: ${manifest.version}`);
11
+ }
12
+ const permutationTable = readUint16Table(assetDirectory, manifest.files.permutationTable, SKEWB_SOLVER_TABLE_METADATA.permutationTable.checksum);
13
+ const orientationTable = readUint16Table(assetDirectory, manifest.files.orientationTable, SKEWB_SOLVER_TABLE_METADATA.orientationTable.checksum);
14
+ const centersTable = readUint16Table(assetDirectory, manifest.files.centersTable, SKEWB_SOLVER_TABLE_METADATA.centersTable.checksum);
15
+ const helpersBuffer = readFileSync(path.join(assetDirectory, manifest.files.helpers.file));
16
+ if (helpersBuffer.length !== manifest.files.helpers.entries) {
17
+ throw new Error(`Invalid byte size for ${manifest.files.helpers.file}`);
18
+ }
19
+ assertChecksum(manifest.files.helpers.file, checksum(new Uint8Array(helpersBuffer)), SKEWB_SOLVER_TABLE_METADATA.helpers.checksum);
20
+ const evenCenterRank = new Uint16Array(SKEWB_SOLVER_TABLE_METADATA.helpers.evenCenterRankBytes / 2);
21
+ for (let i = 0; i < evenCenterRank.length; i++) {
22
+ evenCenterRank[i] = helpersBuffer.readUInt16LE(i * 2);
23
+ }
24
+ const orientClass = new Uint8Array(helpersBuffer.subarray(SKEWB_SOLVER_TABLE_METADATA.helpers.evenCenterRankBytes, SKEWB_SOLVER_TABLE_METADATA.helpers.evenCenterRankBytes + SKEWB_SOLVER_TABLE_METADATA.helpers.orientClassBytes));
25
+ const r7SetsBuffer = helpersBuffer.subarray(SKEWB_SOLVER_TABLE_METADATA.helpers.evenCenterRankBytes + SKEWB_SOLVER_TABLE_METADATA.helpers.orientClassBytes);
26
+ const r7Sets = new Uint16Array(SKEWB_SOLVER_TABLE_METADATA.helpers.r7SetsEntries);
27
+ for (let i = 0; i < r7Sets.length; i++) {
28
+ r7Sets[i] = r7SetsBuffer.readUInt16LE(i * 2);
29
+ }
30
+ const policyBuffer = readFileSync(path.join(assetDirectory, manifest.files.policyTable.file));
31
+ if (policyBuffer.length !== manifest.files.policyTable.entries) {
32
+ throw new Error(`Invalid byte size for ${manifest.files.policyTable.file}`);
33
+ }
34
+ const policyTable = new Uint8Array(policyBuffer);
35
+ assertChecksum(manifest.files.policyTable.file, checksum(policyTable), SKEWB_SOLVER_TABLE_METADATA.policyTable.checksum);
36
+ return { permutationTable, orientationTable, centersTable, policyTable, evenCenterRank, orientClass, r7Sets };
37
+ }
38
+ function readUint16Table(assetDirectory, entry, expectedChecksum) {
39
+ const buffer = readFileSync(path.join(assetDirectory, entry.file));
40
+ if (buffer.length !== entry.entries * 2) {
41
+ throw new Error(`Invalid byte size for ${entry.file}`);
42
+ }
43
+ const values = new Uint16Array(entry.entries);
44
+ for (let index = 0; index < values.length; index += 1) {
45
+ values[index] = buffer.readUInt16LE(index * 2);
46
+ }
47
+ assertChecksum(entry.file, checksum(values), expectedChecksum);
48
+ return values;
49
+ }
50
+ function resolveGeneratedAssetDirectory() {
51
+ const envDirectory = process.env.CUBINGCENTER_SCRAMBLER_TABLES_DIR;
52
+ if (envDirectory && existsSync(path.join(envDirectory, "manifest.json"))) {
53
+ return envDirectory;
54
+ }
55
+ for (const candidate of getAssetDirectoryCandidates()) {
56
+ if (existsSync(path.join(candidate, "manifest.json"))) {
57
+ return candidate;
58
+ }
59
+ }
60
+ throw new Error("Unable to find generated skewb solver tables. Run pnpm skewb:tables.");
61
+ }
62
+ function getAssetDirectoryCandidates() {
63
+ const cwd = process.cwd();
64
+ const candidates = [
65
+ path.join(cwd, "src", GENERATED_DIRECTORY),
66
+ path.join(cwd, "dist", GENERATED_DIRECTORY),
67
+ path.join(cwd, "dist-cjs", GENERATED_DIRECTORY)
68
+ ];
69
+ try {
70
+ const requireFromCwd = createRequire(path.join(cwd, "package.json"));
71
+ const packageEntry = requireFromCwd.resolve("@cubingcenter/scrambler");
72
+ candidates.push(path.join(path.dirname(packageEntry), GENERATED_DIRECTORY));
73
+ }
74
+ catch {
75
+ // Source-tree usage is covered by the cwd candidates.
76
+ }
77
+ return candidates;
78
+ }
79
+ function assertChecksum(file, actual, expected) {
80
+ if (actual !== expected) {
81
+ throw new Error(`Checksum mismatch for ${file}: expected ${expected}, received ${actual}.`);
82
+ }
83
+ }
84
+ function checksum(values) {
85
+ let hash = 0x811c9dc5;
86
+ for (let index = 0; index < values.length; index += 1) {
87
+ hash ^= values[index];
88
+ hash = Math.imul(hash, 0x01000193);
89
+ }
90
+ return hash >>> 0;
91
+ }
@@ -0,0 +1,14 @@
1
+ import type { SkewbMove, SkewbState } from "../types.js";
2
+ export interface SkewbSolution {
3
+ moves: SkewbMove[];
4
+ depth: number;
5
+ }
6
+ export declare function solveSkewb(state: SkewbState): Promise<SkewbMove[]>;
7
+ export declare function solveSkewbWithInfo(state: SkewbState): Promise<SkewbSolution>;
8
+ export declare function solveSkewbIndices(state: SkewbState): Promise<number[]>;
9
+ export declare function solveSkewbIndicesSync(state: SkewbState): number[];
10
+ export declare function solveSkewbSync(state: SkewbState): SkewbMove[];
11
+ export declare function verifySkewbScramble(state: SkewbState, scrambleMoves: readonly SkewbMove[]): boolean;
12
+ export declare function verifySkewbScrambleIndices(state: SkewbState, solution: readonly number[]): boolean;
13
+ export declare function verifySkewbSolution(state: SkewbState, solutionMoves: readonly SkewbMove[]): boolean;
14
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAiBzD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAGxE;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CAGlF;AAKD,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAG5E;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,CAKjE;AAGD,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,EAAE,CAK7D;AA4ED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAOnG;AAID,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAwBlG;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAUnG"}
@@ -0,0 +1,120 @@
1
+ import { ensureTablesLoaded, getCenterIndex, getOrientationIndex, getPerm7Index, getPolicyMoveFromPacked, MOVE_COUNT, PERMUTATION_TABLE, ORIENTATION_TABLE, CENTERS_TABLE, POLICY_TABLE, rankFromComponents, SKEWB_MOVES, SOLVED_RANK } from "./tables.js";
2
+ export async function solveSkewb(state) {
3
+ await ensureTablesLoaded();
4
+ return solveWithTables(state).moves;
5
+ }
6
+ export async function solveSkewbWithInfo(state) {
7
+ await ensureTablesLoaded();
8
+ return solveWithTables(state);
9
+ }
10
+ // Index-based solve for the hot scramble path: returns the solution as move
11
+ // indices (0..7) in walk order. The inverse of index m is m ^ 1 (moves are
12
+ // paired R/R', U/U', L/L', B/B').
13
+ export async function solveSkewbIndices(state) {
14
+ await ensureTablesLoaded();
15
+ return solveWithTablesIndices(state);
16
+ }
17
+ export function solveSkewbIndicesSync(state) {
18
+ if (!POLICY_TABLE) {
19
+ throw new Error("Skewb solver tables not loaded. Call ensureTablesLoaded() first.");
20
+ }
21
+ return solveWithTablesIndices(state);
22
+ }
23
+ // Synchronous variant for scripts that have already ensured tables are loaded
24
+ export function solveSkewbSync(state) {
25
+ if (!POLICY_TABLE) {
26
+ throw new Error("Skewb solver tables not loaded. Call ensureTablesLoaded() first.");
27
+ }
28
+ return solveWithTables(state).moves;
29
+ }
30
+ function solveWithTables(state) {
31
+ const indices = solveWithTablesIndices(state);
32
+ const moves = indices.map((index) => SKEWB_MOVES[index]);
33
+ return { moves, depth: moves.length };
34
+ }
35
+ function solveWithTablesIndices(state) {
36
+ const policy = POLICY_TABLE;
37
+ const permTable = PERMUTATION_TABLE;
38
+ const orientTable = ORIENTATION_TABLE;
39
+ const centersTable = CENTERS_TABLE;
40
+ if (state.corners.permutation[0] !== 0) {
41
+ throw new Error("The Skewb solver requires the UFR corner (index 0) to be fixed " +
42
+ "as the frame of reference. Please normalize the state before solving.");
43
+ }
44
+ let r7 = getPerm7Index(state.corners.permutation);
45
+ let rO = getOrientationIndex(state.corners.orientation);
46
+ let rC = getCenterIndex(state.centers);
47
+ let rank = rankFromComponents(r7, rO, rC);
48
+ if (rank === SOLVED_RANK) {
49
+ return [];
50
+ }
51
+ const moves = [];
52
+ while (rank !== SOLVED_RANK) {
53
+ const m = getPolicyMoveFromPacked(policy, rank);
54
+ moves.push(m);
55
+ r7 = permTable[r7 * MOVE_COUNT + m];
56
+ rO = orientTable[rO * MOVE_COUNT + m];
57
+ rC = centersTable[rC * MOVE_COUNT + m];
58
+ rank = rankFromComponents(r7, rO, rC);
59
+ }
60
+ return moves;
61
+ }
62
+ // ---------------------------------------------------------------------------
63
+ // Coordinate-walking verification (no full state clones)
64
+ // ---------------------------------------------------------------------------
65
+ const MOVE_INDEX = {};
66
+ for (let i = 0; i < SKEWB_MOVES.length; i++) {
67
+ MOVE_INDEX[SKEWB_MOVES[i]] = i;
68
+ }
69
+ function walkSkewbCoordinates(start, moves) {
70
+ const permTable = PERMUTATION_TABLE;
71
+ const orientTable = ORIENTATION_TABLE;
72
+ const centersTable = CENTERS_TABLE;
73
+ if (!permTable || !orientTable || !centersTable) {
74
+ throw new Error("Skewb solver tables not loaded. Call ensureTablesLoaded() first.");
75
+ }
76
+ let { r7, rO, rC } = start;
77
+ for (const move of moves) {
78
+ const m = MOVE_INDEX[move];
79
+ r7 = permTable[r7 * MOVE_COUNT + m];
80
+ rO = orientTable[rO * MOVE_COUNT + m];
81
+ rC = centersTable[rC * MOVE_COUNT + m];
82
+ }
83
+ return { r7, rO, rC };
84
+ }
85
+ export function verifySkewbScramble(state, scrambleMoves) {
86
+ const final = walkSkewbCoordinates({ r7: 0, rO: 0, rC: 0 }, scrambleMoves);
87
+ return (final.r7 === getPerm7Index(state.corners.permutation) &&
88
+ final.rO === getOrientationIndex(state.corners.orientation) &&
89
+ final.rC === getCenterIndex(state.centers));
90
+ }
91
+ // Numeric verification for the hot scramble path: replays the scramble (the
92
+ // solution reversed with m ^ 1 inverses) without any string parsing.
93
+ export function verifySkewbScrambleIndices(state, solution) {
94
+ const permTable = PERMUTATION_TABLE;
95
+ const orientTable = ORIENTATION_TABLE;
96
+ const centersTable = CENTERS_TABLE;
97
+ if (!permTable || !orientTable || !centersTable) {
98
+ throw new Error("Skewb solver tables not loaded. Call ensureTablesLoaded() first.");
99
+ }
100
+ let r7 = 0;
101
+ let rO = 0;
102
+ let rC = 0;
103
+ for (let index = solution.length - 1; index >= 0; index -= 1) {
104
+ const m = solution[index] ^ 1;
105
+ r7 = permTable[r7 * MOVE_COUNT + m];
106
+ rO = orientTable[rO * MOVE_COUNT + m];
107
+ rC = centersTable[rC * MOVE_COUNT + m];
108
+ }
109
+ return (r7 === getPerm7Index(state.corners.permutation) &&
110
+ rO === getOrientationIndex(state.corners.orientation) &&
111
+ rC === getCenterIndex(state.centers));
112
+ }
113
+ export function verifySkewbSolution(state, solutionMoves) {
114
+ const final = walkSkewbCoordinates({
115
+ r7: getPerm7Index(state.corners.permutation),
116
+ rO: getOrientationIndex(state.corners.orientation),
117
+ rC: getCenterIndex(state.centers)
118
+ }, solutionMoves);
119
+ return final.r7 === 0 && final.rO === 0 && final.rC === 0;
120
+ }