@cubingcenter/scrambler 0.5.1 → 0.5.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.
@@ -1 +1 @@
1
- {"version":3,"file":"facelets.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube2/facelets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAgB,MAAM,YAAY,CAAC;AA4BrF,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAehE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAEnE;AAED,wBAAgB,iBAAiB,IAAI,SAAS,SAAS,EAAE,CAExD"}
1
+ {"version":3,"file":"facelets.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube2/facelets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAgB,MAAM,YAAY,CAAC;AAgErF,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAqBhE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAEnE;AAED,wBAAgB,iBAAiB,IAAI,SAAS,SAAS,EAAE,CAExD"}
@@ -1,4 +1,6 @@
1
1
  const FACE_ORDER = ["U", "R", "F", "D", "L", "B"];
2
+ const STICKERS = ["U", "R", "F", "D", "L", "B"];
3
+ const STICKER_INDEX = { U: 0, R: 1, F: 2, D: 3, L: 4, B: 5 };
2
4
  const CORNER_PIECE_STICKERS = [
3
5
  ["U", "R", "F"],
4
6
  ["U", "F", "L"],
@@ -19,12 +21,48 @@ const CORNER_POSITION_SLOTS = [
19
21
  [["D", 2], ["B", 3], ["L", 2]],
20
22
  [["D", 3], ["R", 3], ["B", 2]]
21
23
  ];
22
- export function toCube2Facelets(state) {
23
- const facelets = createEmptyFacelets();
24
+ const CORNER_CELLS = buildCells();
25
+ function buildCells() {
26
+ const cells = [];
24
27
  for (let position = 0; position < CORNER_POSITION_SLOTS.length; position += 1) {
25
- const piece = state.corners.permutation[position];
26
- const orientation = state.corners.orientation[position];
27
- placeOrientedStickers(facelets, CORNER_POSITION_SLOTS[position], CORNER_PIECE_STICKERS[piece], orientation);
28
+ const byPiece = [];
29
+ for (let piece = 0; piece < CORNER_PIECE_STICKERS.length; piece += 1) {
30
+ const byOrientation = [];
31
+ for (let orientation = 0; orientation < 3; orientation += 1) {
32
+ const stickers = CORNER_PIECE_STICKERS[piece];
33
+ const slotList = CORNER_POSITION_SLOTS[position];
34
+ const cell = [];
35
+ for (let index = 0; index < slotList.length; index += 1) {
36
+ const [face, stickerIndex] = slotList[index];
37
+ cell.push(FACE_ORDER.indexOf(face));
38
+ cell.push(stickerIndex);
39
+ cell.push(STICKER_INDEX[stickers[(index - orientation + stickers.length) % stickers.length]]);
40
+ }
41
+ byOrientation.push(cell);
42
+ }
43
+ byPiece.push(byOrientation);
44
+ }
45
+ cells.push(byPiece);
46
+ }
47
+ return cells;
48
+ }
49
+ export function toCube2Facelets(state) {
50
+ const facelets = {
51
+ U: Array(4),
52
+ R: Array(4),
53
+ F: Array(4),
54
+ D: Array(4),
55
+ L: Array(4),
56
+ B: Array(4)
57
+ };
58
+ const faces = [facelets.U, facelets.R, facelets.F, facelets.D, facelets.L, facelets.B];
59
+ const permutation = state.corners.permutation;
60
+ const orientation = state.corners.orientation;
61
+ for (let position = 0; position < CORNER_CELLS.length; position += 1) {
62
+ const cells = CORNER_CELLS[position][permutation[position]][orientation[position]];
63
+ for (let cellIndex = 0; cellIndex < cells.length; cellIndex += 3) {
64
+ faces[cells[cellIndex]][cells[cellIndex + 1]] = STICKERS[cells[cellIndex + 2]];
65
+ }
28
66
  }
29
67
  return facelets;
30
68
  }
@@ -34,20 +72,3 @@ export function formatCube2Facelets(facelets) {
34
72
  export function getCube2FaceOrder() {
35
73
  return FACE_ORDER;
36
74
  }
37
- function createEmptyFacelets() {
38
- return {
39
- U: Array(4).fill("U"),
40
- R: Array(4).fill("R"),
41
- F: Array(4).fill("F"),
42
- D: Array(4).fill("D"),
43
- L: Array(4).fill("L"),
44
- B: Array(4).fill("B")
45
- };
46
- }
47
- function placeOrientedStickers(facelets, slots, stickers, orientation) {
48
- for (let index = 0; index < slots.length; index += 1) {
49
- const [face, stickerIndex] = slots[index];
50
- const sticker = stickers[(index - orientation + stickers.length) % stickers.length];
51
- facelets[face][stickerIndex] = sticker;
52
- }
53
- }
@@ -1 +1 @@
1
- {"version":3,"file":"random-state.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube2/random-state.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGtE,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,uBAA4B,GAAG,UAAU,CAa1F"}
1
+ {"version":3,"file":"random-state.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube2/random-state.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAItE,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,uBAA4B,GAAG,UAAU,CAsB1F"}
@@ -1,51 +1,61 @@
1
1
  import { createRandomSource } from "../../random/index.js";
2
- import { CORNER_COUNT } from "./types.js";
3
- import { isAllowedRandomCube2State } from "./validation.js";
2
+ import { isTrivialSamplingCube2State } from "./validation.js";
3
+ import { encodePermutation, encodeOrientation } from "./solver/tables.js";
4
4
  export function generateRandomCube2State(options = {}) {
5
5
  const random = createRandomSource(options.seed);
6
6
  const rejectTrivial = options.rejectTrivial ?? true;
7
7
  for (let attempt = 0; attempt < 100; attempt += 1) {
8
+ // The state is legal by construction (DBL corner fixed, even
9
+ // permutation, orientation sum divisible by 3, values in range), so the
10
+ // only meaningful rejection is the trivial set: solved + the 18
11
+ // single-move states (scrambles shorter than 2 moves). Coordinates are
12
+ // encoded once for both checks.
8
13
  const state = createLegalRandomState(random);
9
- if (!rejectTrivial || isAllowedRandomCube2State(state)) {
14
+ if (!rejectTrivial) {
15
+ return state;
16
+ }
17
+ const p = encodePermutation(state.corners.permutation);
18
+ const o = encodeOrientation(state.corners.orientation);
19
+ if (!isTrivialSamplingCube2State(p, o)) {
10
20
  return state;
11
21
  }
12
22
  }
13
23
  throw new Error("Unable to generate a non-trivial random 2x2 state.");
14
24
  }
15
25
  function createLegalRandomState(random) {
16
- const perm7 = shuffledRange(CORNER_COUNT - 1, random);
17
- const mappedPerm = perm7.map((val) => (val >= 6 ? val + 1 : val));
18
- mappedPerm.splice(6, 0, 6);
19
- const orient7 = randomOrientations(CORNER_COUNT - 1, 3, random);
20
- orient7.splice(6, 0, 0);
21
- return {
22
- corners: {
23
- permutation: mappedPerm,
24
- orientation: orient7
25
- }
26
- };
27
- }
28
- function shuffledRange(length, random) {
29
- const values = Array.from({ length }, (_, index) => index);
30
- for (let index = values.length - 1; index > 0; index -= 1) {
26
+ // 7 free corners (piece 6 = DBL stays fixed at position 6)
27
+ const perm7 = [0, 1, 2, 3, 4, 5, 6];
28
+ for (let index = 6; index > 0; index -= 1) {
31
29
  const swapIndex = random.nextInt(index + 1);
32
- swap(values, index, swapIndex);
30
+ if (swapIndex < index) {
31
+ const temporary = perm7[index];
32
+ perm7[index] = perm7[swapIndex];
33
+ perm7[swapIndex] = temporary;
34
+ }
33
35
  }
34
- return values;
35
- }
36
- function randomOrientations(length, modulo, random) {
37
- const orientations = Array(length).fill(0);
36
+ // Map the shuffle domain {0..6} to {0..5,7} and insert 6 at position 6
37
+ // (DBL fixed). Single allocation, no intermediate arrays.
38
+ const last = perm7[6] >= 6 ? perm7[6] + 1 : perm7[6];
39
+ const permutation = new Array(8);
40
+ for (let index = 0; index < 6; index += 1) {
41
+ const value = perm7[index];
42
+ permutation[index] = value >= 6 ? value + 1 : value;
43
+ }
44
+ permutation[6] = 6;
45
+ permutation[7] = last;
46
+ // 7 free orientations with sum divisible by 3; DBL stays oriented
47
+ const orientation = [0, 0, 0, 0, 0, 0, 0, 0];
38
48
  let total = 0;
39
- for (let index = 0; index < length - 1; index += 1) {
40
- const orientation = random.nextInt(modulo);
41
- orientations[index] = orientation;
42
- total += orientation;
49
+ for (let index = 0; index < 6; index += 1) {
50
+ const value = random.nextInt(3);
51
+ orientation[index] = value;
52
+ total += value;
43
53
  }
44
- orientations[length - 1] = (modulo - (total % modulo)) % modulo;
45
- return orientations;
46
- }
47
- function swap(values, left, right) {
48
- const temporary = values[left];
49
- values[left] = values[right];
50
- values[right] = temporary;
54
+ orientation[7] = (3 - (total % 3)) % 3;
55
+ return {
56
+ corners: {
57
+ permutation,
58
+ orientation
59
+ }
60
+ };
51
61
  }
@@ -1 +1 @@
1
- {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube2/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAS9F,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,CAwCzB"}
1
+ {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube2/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAiB9F,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,CAqDzB"}
@@ -1,7 +1,10 @@
1
- import { formatCube2Algorithm, invertCube2Algorithm } from "./moves.js";
1
+ import { formatCube2Algorithm } from "./moves.js";
2
2
  import { generateRandomCube2State } from "./random-state.js";
3
3
  import { formatCube2Facelets, toCube2Facelets } from "./facelets.js";
4
- import { solveCube2State, verifyCube2Scramble, verifyCube2Solution } from "./solver/index.js";
4
+ import { areCube2TablesLoaded, solveCube2StateIndices, solveCube2StateIndicesSync, verifyCube2SolutionIndices, BFS_MOVES } from "./solver/index.js";
5
+ // BFS_MOVES = [U, U2, U', R, R2, R', F, F2, F'] — the inverse of index m is
6
+ // CUBE2_MOVE_INVERSE[m] (U2/R2/F2 are self-inverse, so m ^ 1 does not apply).
7
+ const CUBE2_MOVE_INVERSE = [2, 1, 0, 5, 4, 3, 8, 7, 6];
5
8
  export const cube2ScrambleGenerator = {
6
9
  info: {
7
10
  eventId: "222",
@@ -17,20 +20,31 @@ export const cube2ScrambleGenerator = {
17
20
  };
18
21
  export async function generateCube2Scramble(options = {}) {
19
22
  const state = generateRandomCube2State(options);
20
- const solution = await solveCube2State(state);
21
- const scrambleMoves = invertCube2Algorithm(solution.moves);
23
+ const solution = areCube2TablesLoaded()
24
+ ? solveCube2StateIndicesSync(state)
25
+ : await solveCube2StateIndices(state);
26
+ const depth = solution.length;
27
+ // Scramble = inverse of the solution, reversed.
28
+ const scrambleMoves = new Array(depth);
29
+ for (let index = 0; index < depth; index += 1) {
30
+ scrambleMoves[index] = BFS_MOVES[CUBE2_MOVE_INVERSE[solution[depth - 1 - index]]];
31
+ }
22
32
  if (!options.skipValidation) {
23
- if (!verifyCube2Scramble(state, scrambleMoves)) {
33
+ // The scramble is the exact inverse of the solution, so verifying that
34
+ // the solution solves the target state is equivalent to verifying that
35
+ // the scramble reproduces it — a single numeric walk suffices.
36
+ if (!verifyCube2SolutionIndices(state, solution)) {
24
37
  throw new Error("Generated 2x2 scramble does not reproduce the target random state.");
25
38
  }
26
- if (!verifyCube2Solution(state, solution.moves)) {
27
- throw new Error("Generated 2x2 solution does not solve the target random state.");
28
- }
29
39
  if (scrambleMoves.length < 2) {
30
40
  throw new Error("Generated 2x2 scramble is too short.");
31
41
  }
32
42
  }
33
43
  const facelets = toCube2Facelets(state);
44
+ const solutionMoves = new Array(depth);
45
+ for (let index = 0; index < depth; index += 1) {
46
+ solutionMoves[index] = BFS_MOVES[solution[index]];
47
+ }
34
48
  const result = {
35
49
  eventId: "222",
36
50
  scramble: formatCube2Algorithm(scrambleMoves),
@@ -41,8 +55,8 @@ export async function generateCube2Scramble(options = {}) {
41
55
  state,
42
56
  facelets,
43
57
  faceletString: formatCube2Facelets(facelets),
44
- solutionMoves: solution.moves,
45
- solutionDepth: solution.depth
58
+ solutionMoves,
59
+ solutionDepth: depth
46
60
  }
47
61
  };
48
62
  if (options.seed !== undefined) {
@@ -1,3 +1,4 @@
1
- export { solveCube2State, verifyCube2Scramble, verifyCube2Solution } from "./search.js";
1
+ export { solveCube2State, solveCube2StateIndices, solveCube2StateIndicesSync, verifyCube2Scramble, verifyCube2Solution, verifyCube2SolutionIndices } from "./search.js";
2
2
  export type { Cube2Solution } from "./search.js";
3
+ export { areCube2TablesLoaded, BFS_MOVES, DISTANCES, ensureTablesLoaded, encodePermutation, encodeOrientation, getDistance, getPermutationTransition, getOrientationTransition, MOVE_COUNT, ORIENTATION_COUNT, ORIENTATION_TABLE, PERMUTATION_TABLE } from "./tables.js";
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACxF,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,0BAA0B,EAC1B,mBAAmB,EACnB,mBAAmB,EACnB,0BAA0B,EAC3B,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EACL,oBAAoB,EACpB,SAAS,EACT,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,wBAAwB,EACxB,wBAAwB,EACxB,UAAU,EACV,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,aAAa,CAAC"}
@@ -1 +1,2 @@
1
- export { solveCube2State, verifyCube2Scramble, verifyCube2Solution } from "./search.js";
1
+ export { solveCube2State, solveCube2StateIndices, solveCube2StateIndicesSync, verifyCube2Scramble, verifyCube2Solution, verifyCube2SolutionIndices } from "./search.js";
2
+ export { areCube2TablesLoaded, BFS_MOVES, DISTANCES, ensureTablesLoaded, encodePermutation, encodeOrientation, getDistance, getPermutationTransition, getOrientationTransition, MOVE_COUNT, ORIENTATION_COUNT, ORIENTATION_TABLE, PERMUTATION_TABLE } from "./tables.js";
@@ -5,6 +5,9 @@ export interface Cube2Solution {
5
5
  state: Cube2State;
6
6
  }
7
7
  export declare function solveCube2State(state: Cube2State): Promise<Cube2Solution>;
8
+ export declare function solveCube2StateIndices(state: Cube2State): Promise<number[]>;
9
+ export declare function solveCube2StateIndicesSync(state: Cube2State): number[];
8
10
  export declare function verifyCube2Scramble(state: Cube2State, scrambleMoves: readonly Cube2Move[]): boolean;
9
11
  export declare function verifyCube2Solution(state: Cube2State, solutionMoves: readonly Cube2Move[]): boolean;
12
+ export declare function verifyCube2SolutionIndices(state: Cube2State, solution: readonly number[]): boolean;
10
13
  //# sourceMappingURL=search.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAczD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CA6D/E;AAoBD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAUnG;AAGD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAOnG"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAczD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CAQ/E;AAKD,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGjF;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,CAKtE;AA6ED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAUnG;AAGD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAOnG;AAMD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAclG"}
@@ -2,6 +2,27 @@ import { createSolvedCube2State } from "../state.js";
2
2
  import { encodeOrientation, encodePermutation, ensureTablesLoaded, PERMUTATION_TABLE, ORIENTATION_TABLE, DISTANCES, ORIENTATION_COUNT, BFS_MOVES, MOVE_COUNT } from "./tables.js";
3
3
  export async function solveCube2State(state) {
4
4
  await ensureTablesLoaded();
5
+ const indices = solveWithTablesIndices(state);
6
+ return {
7
+ moves: indices.map((index) => BFS_MOVES[index]),
8
+ depth: indices.length,
9
+ state: createSolvedCube2State()
10
+ };
11
+ }
12
+ // Index-based solve for the hot scramble path: returns the solution as move
13
+ // indices (0..8) in walk order. The inverse of index m is CUBE2_MOVE_INVERSE[m]
14
+ // (U2/R2/F2 are self-inverse, so m ^ 1 does not apply here).
15
+ export async function solveCube2StateIndices(state) {
16
+ await ensureTablesLoaded();
17
+ return solveWithTablesIndices(state);
18
+ }
19
+ export function solveCube2StateIndicesSync(state) {
20
+ if (!DISTANCES) {
21
+ throw new Error("Cube2 solver tables not loaded. Call ensureTablesLoaded() first.");
22
+ }
23
+ return solveWithTablesIndices(state);
24
+ }
25
+ function solveWithTablesIndices(state) {
5
26
  if (state.corners.permutation[6] !== 6 || state.corners.orientation[6] !== 0) {
6
27
  throw new Error("The 2x2 solver requires the DBL corner (index 6) to be solved " +
7
28
  "as the frame of reference. Please normalize the state before solving.");
@@ -16,7 +37,7 @@ export async function solveCube2State(state) {
16
37
  const initialCombined = permIndex * ORIENTATION_COUNT + orientIndex;
17
38
  let distance = (distances[initialCombined >> 1] >> ((initialCombined & 1) << 2)) & 0x0f;
18
39
  if (distance === 0) {
19
- return { moves, depth: 0, state: createSolvedCube2State() };
40
+ return moves;
20
41
  }
21
42
  while (distance > 0) {
22
43
  let foundNext = false;
@@ -30,7 +51,7 @@ export async function solveCube2State(state) {
30
51
  const combined = nextPerm * ORIENTATION_COUNT + nextOrient;
31
52
  const nextDist = (distances[combined >> 1] >> ((combined & 1) << 2)) & 0x0f;
32
53
  if (nextDist === nextDistance) {
33
- moves.push(BFS_MOVES[m]);
54
+ moves.push(m);
34
55
  permIndex = nextPerm;
35
56
  orientIndex = nextOrient;
36
57
  distance = nextDistance;
@@ -42,11 +63,7 @@ export async function solveCube2State(state) {
42
63
  throw new Error("BFS distance table corrupted or unreachable state.");
43
64
  }
44
65
  }
45
- return {
46
- moves,
47
- depth: moves.length,
48
- state: createSolvedCube2State()
49
- };
66
+ return moves;
50
67
  }
51
68
  const MOVE_INDEX = {};
52
69
  for (let i = 0; i < BFS_MOVES.length; i++) {
@@ -74,3 +91,21 @@ export function verifyCube2Solution(state, solutionMoves) {
74
91
  const { permIndex, orientIndex } = walkCube2Coordinates(encodePermutation(state.corners.permutation), encodeOrientation(state.corners.orientation), solutionMoves);
75
92
  return permIndex === 0 && orientIndex === 0;
76
93
  }
94
+ // Numeric verification for the hot scramble path: walks the solution (move
95
+ // indices, walk order) from the target state to solved. The scramble is the
96
+ // exact inverse of the solution, so this is equivalent to verifying that the
97
+ // scramble reproduces the target state — one walk, no string parsing.
98
+ export function verifyCube2SolutionIndices(state, solution) {
99
+ const permTable = PERMUTATION_TABLE;
100
+ const orientTable = ORIENTATION_TABLE;
101
+ if (!permTable || !orientTable) {
102
+ throw new Error("Cube2 solver tables not loaded. Call ensureTablesLoaded() first.");
103
+ }
104
+ let permIndex = encodePermutation(state.corners.permutation);
105
+ let orientIndex = encodeOrientation(state.corners.orientation);
106
+ for (const m of solution) {
107
+ permIndex = permTable[permIndex * MOVE_COUNT + m];
108
+ orientIndex = orientTable[orientIndex * MOVE_COUNT + m];
109
+ }
110
+ return permIndex === 0 && orientIndex === 0;
111
+ }
@@ -7,6 +7,7 @@ export declare let PERMUTATION_TABLE: Uint16Array | null;
7
7
  export declare let ORIENTATION_TABLE: Uint16Array | null;
8
8
  export declare let DISTANCES: Uint8Array | null;
9
9
  export declare function ensureTablesLoaded(): Promise<void>;
10
+ export declare function areCube2TablesLoaded(): boolean;
10
11
  export declare function getDistance(permIndex: number, orientIndex: number): number;
11
12
  export declare function getPermutationTransition(move: number, permIndex: number): number;
12
13
  export declare function getOrientationTransition(move: number, orientIndex: number): number;
@@ -1 +1 @@
1
- {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/tables.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,UAAU,IAAI,CAAC;AAM5B,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAexE;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAWxE;AAeD,eAAO,IAAI,iBAAiB,EAAE,WAAW,GAAG,IAAW,CAAC;AACxD,eAAO,IAAI,iBAAiB,EAAE,WAAW,GAAG,IAAW,CAAC;AACxD,eAAO,IAAI,SAAS,EAAE,UAAU,GAAG,IAAW,CAAC;AAE/C,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAUxD;AAMD,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAO1E;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAKhF;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAKlF;AAED,eAAO,MAAM,SAAS,EAAE,SAAS,MAAM,EAItC,CAAC"}
1
+ {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/tables.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,UAAU,IAAI,CAAC;AAM5B,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAexE;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAWxE;AAeD,eAAO,IAAI,iBAAiB,EAAE,WAAW,GAAG,IAAW,CAAC;AACxD,eAAO,IAAI,iBAAiB,EAAE,WAAW,GAAG,IAAW,CAAC;AACxD,eAAO,IAAI,SAAS,EAAE,UAAU,GAAG,IAAW,CAAC;AAE/C,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAUxD;AAED,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAMD,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAO1E;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAKhF;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAKlF;AAED,eAAO,MAAM,SAAS,EAAE,SAAS,MAAM,EAItC,CAAC"}
@@ -52,6 +52,9 @@ export async function ensureTablesLoaded() {
52
52
  DISTANCES = tables.distances;
53
53
  tablesLoaded = true;
54
54
  }
55
+ export function areCube2TablesLoaded() {
56
+ return tablesLoaded;
57
+ }
55
58
  // ---------------------------------------------------------------------------
56
59
  // Public API
57
60
  // ---------------------------------------------------------------------------
@@ -1,6 +1,7 @@
1
1
  import type { Cube2State, Cube2ValidationResult } from "./types.js";
2
2
  export declare function validateCube2State(state: Cube2State): Cube2ValidationResult;
3
3
  export declare function isOneMoveFromSolvedCube2State(state: Cube2State): boolean;
4
+ export declare function isTrivialSamplingCube2State(p: number, o: number): boolean;
4
5
  export declare function isAllowedRandomCube2State(state: Cube2State): boolean;
5
6
  export declare function permutationParity(permutation: readonly number[]): 0 | 1;
6
7
  //# sourceMappingURL=validation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube2/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAgBpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,qBAAqB,CAkB3E;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAIxE;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAIpE;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAYvE"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube2/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAgBpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,qBAAqB,CAkB3E;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAIxE;AAKD,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAEzE;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAIpE;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAYvE"}
@@ -31,7 +31,13 @@ export function validateCube2State(state) {
31
31
  export function isOneMoveFromSolvedCube2State(state) {
32
32
  const p = encodePermutation(state.corners.permutation);
33
33
  const o = encodeOrientation(state.corners.orientation);
34
- return ONE_MOVE_FROM_SOLVED_ENCODED.some((entry) => entry.p === p && entry.o === o);
34
+ return isTrivialSamplingCube2State(p, o);
35
+ }
36
+ // Cheap trivial check for random-state sampling: the caller has already
37
+ // encoded the coordinates once. True for the solved state and the 18
38
+ // single-move states (which would produce scrambles shorter than 2 moves).
39
+ export function isTrivialSamplingCube2State(p, o) {
40
+ return (p === 0 && o === 0) || ONE_MOVE_FROM_SOLVED_ENCODED.some((entry) => entry.p === p && entry.o === o);
35
41
  }
36
42
  export function isAllowedRandomCube2State(state) {
37
43
  return validateCube2State(state).valid &&
@@ -4,6 +4,8 @@ exports.toCube2Facelets = toCube2Facelets;
4
4
  exports.formatCube2Facelets = formatCube2Facelets;
5
5
  exports.getCube2FaceOrder = getCube2FaceOrder;
6
6
  const FACE_ORDER = ["U", "R", "F", "D", "L", "B"];
7
+ const STICKERS = ["U", "R", "F", "D", "L", "B"];
8
+ const STICKER_INDEX = { U: 0, R: 1, F: 2, D: 3, L: 4, B: 5 };
7
9
  const CORNER_PIECE_STICKERS = [
8
10
  ["U", "R", "F"],
9
11
  ["U", "F", "L"],
@@ -24,12 +26,48 @@ const CORNER_POSITION_SLOTS = [
24
26
  [["D", 2], ["B", 3], ["L", 2]],
25
27
  [["D", 3], ["R", 3], ["B", 2]]
26
28
  ];
27
- function toCube2Facelets(state) {
28
- const facelets = createEmptyFacelets();
29
+ const CORNER_CELLS = buildCells();
30
+ function buildCells() {
31
+ const cells = [];
29
32
  for (let position = 0; position < CORNER_POSITION_SLOTS.length; position += 1) {
30
- const piece = state.corners.permutation[position];
31
- const orientation = state.corners.orientation[position];
32
- placeOrientedStickers(facelets, CORNER_POSITION_SLOTS[position], CORNER_PIECE_STICKERS[piece], orientation);
33
+ const byPiece = [];
34
+ for (let piece = 0; piece < CORNER_PIECE_STICKERS.length; piece += 1) {
35
+ const byOrientation = [];
36
+ for (let orientation = 0; orientation < 3; orientation += 1) {
37
+ const stickers = CORNER_PIECE_STICKERS[piece];
38
+ const slotList = CORNER_POSITION_SLOTS[position];
39
+ const cell = [];
40
+ for (let index = 0; index < slotList.length; index += 1) {
41
+ const [face, stickerIndex] = slotList[index];
42
+ cell.push(FACE_ORDER.indexOf(face));
43
+ cell.push(stickerIndex);
44
+ cell.push(STICKER_INDEX[stickers[(index - orientation + stickers.length) % stickers.length]]);
45
+ }
46
+ byOrientation.push(cell);
47
+ }
48
+ byPiece.push(byOrientation);
49
+ }
50
+ cells.push(byPiece);
51
+ }
52
+ return cells;
53
+ }
54
+ function toCube2Facelets(state) {
55
+ const facelets = {
56
+ U: Array(4),
57
+ R: Array(4),
58
+ F: Array(4),
59
+ D: Array(4),
60
+ L: Array(4),
61
+ B: Array(4)
62
+ };
63
+ const faces = [facelets.U, facelets.R, facelets.F, facelets.D, facelets.L, facelets.B];
64
+ const permutation = state.corners.permutation;
65
+ const orientation = state.corners.orientation;
66
+ for (let position = 0; position < CORNER_CELLS.length; position += 1) {
67
+ const cells = CORNER_CELLS[position][permutation[position]][orientation[position]];
68
+ for (let cellIndex = 0; cellIndex < cells.length; cellIndex += 3) {
69
+ faces[cells[cellIndex]][cells[cellIndex + 1]] = STICKERS[cells[cellIndex + 2]];
70
+ }
33
71
  }
34
72
  return facelets;
35
73
  }
@@ -39,20 +77,3 @@ function formatCube2Facelets(facelets) {
39
77
  function getCube2FaceOrder() {
40
78
  return FACE_ORDER;
41
79
  }
42
- function createEmptyFacelets() {
43
- return {
44
- U: Array(4).fill("U"),
45
- R: Array(4).fill("R"),
46
- F: Array(4).fill("F"),
47
- D: Array(4).fill("D"),
48
- L: Array(4).fill("L"),
49
- B: Array(4).fill("B")
50
- };
51
- }
52
- function placeOrientedStickers(facelets, slots, stickers, orientation) {
53
- for (let index = 0; index < slots.length; index += 1) {
54
- const [face, stickerIndex] = slots[index];
55
- const sticker = stickers[(index - orientation + stickers.length) % stickers.length];
56
- facelets[face][stickerIndex] = sticker;
57
- }
58
- }
@@ -2,53 +2,63 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateRandomCube2State = generateRandomCube2State;
4
4
  const index_js_1 = require("../../random/index.js");
5
- const types_js_1 = require("./types.js");
6
5
  const validation_js_1 = require("./validation.js");
6
+ const tables_js_1 = require("./solver/tables.js");
7
7
  function generateRandomCube2State(options = {}) {
8
8
  const random = (0, index_js_1.createRandomSource)(options.seed);
9
9
  const rejectTrivial = options.rejectTrivial ?? true;
10
10
  for (let attempt = 0; attempt < 100; attempt += 1) {
11
+ // The state is legal by construction (DBL corner fixed, even
12
+ // permutation, orientation sum divisible by 3, values in range), so the
13
+ // only meaningful rejection is the trivial set: solved + the 18
14
+ // single-move states (scrambles shorter than 2 moves). Coordinates are
15
+ // encoded once for both checks.
11
16
  const state = createLegalRandomState(random);
12
- if (!rejectTrivial || (0, validation_js_1.isAllowedRandomCube2State)(state)) {
17
+ if (!rejectTrivial) {
18
+ return state;
19
+ }
20
+ const p = (0, tables_js_1.encodePermutation)(state.corners.permutation);
21
+ const o = (0, tables_js_1.encodeOrientation)(state.corners.orientation);
22
+ if (!(0, validation_js_1.isTrivialSamplingCube2State)(p, o)) {
13
23
  return state;
14
24
  }
15
25
  }
16
26
  throw new Error("Unable to generate a non-trivial random 2x2 state.");
17
27
  }
18
28
  function createLegalRandomState(random) {
19
- const perm7 = shuffledRange(types_js_1.CORNER_COUNT - 1, random);
20
- const mappedPerm = perm7.map((val) => (val >= 6 ? val + 1 : val));
21
- mappedPerm.splice(6, 0, 6);
22
- const orient7 = randomOrientations(types_js_1.CORNER_COUNT - 1, 3, random);
23
- orient7.splice(6, 0, 0);
24
- return {
25
- corners: {
26
- permutation: mappedPerm,
27
- orientation: orient7
28
- }
29
- };
30
- }
31
- function shuffledRange(length, random) {
32
- const values = Array.from({ length }, (_, index) => index);
33
- for (let index = values.length - 1; index > 0; index -= 1) {
29
+ // 7 free corners (piece 6 = DBL stays fixed at position 6)
30
+ const perm7 = [0, 1, 2, 3, 4, 5, 6];
31
+ for (let index = 6; index > 0; index -= 1) {
34
32
  const swapIndex = random.nextInt(index + 1);
35
- swap(values, index, swapIndex);
33
+ if (swapIndex < index) {
34
+ const temporary = perm7[index];
35
+ perm7[index] = perm7[swapIndex];
36
+ perm7[swapIndex] = temporary;
37
+ }
36
38
  }
37
- return values;
38
- }
39
- function randomOrientations(length, modulo, random) {
40
- const orientations = Array(length).fill(0);
39
+ // Map the shuffle domain {0..6} to {0..5,7} and insert 6 at position 6
40
+ // (DBL fixed). Single allocation, no intermediate arrays.
41
+ const last = perm7[6] >= 6 ? perm7[6] + 1 : perm7[6];
42
+ const permutation = new Array(8);
43
+ for (let index = 0; index < 6; index += 1) {
44
+ const value = perm7[index];
45
+ permutation[index] = value >= 6 ? value + 1 : value;
46
+ }
47
+ permutation[6] = 6;
48
+ permutation[7] = last;
49
+ // 7 free orientations with sum divisible by 3; DBL stays oriented
50
+ const orientation = [0, 0, 0, 0, 0, 0, 0, 0];
41
51
  let total = 0;
42
- for (let index = 0; index < length - 1; index += 1) {
43
- const orientation = random.nextInt(modulo);
44
- orientations[index] = orientation;
45
- total += orientation;
52
+ for (let index = 0; index < 6; index += 1) {
53
+ const value = random.nextInt(3);
54
+ orientation[index] = value;
55
+ total += value;
46
56
  }
47
- orientations[length - 1] = (modulo - (total % modulo)) % modulo;
48
- return orientations;
49
- }
50
- function swap(values, left, right) {
51
- const temporary = values[left];
52
- values[left] = values[right];
53
- values[right] = temporary;
57
+ orientation[7] = (3 - (total % 3)) % 3;
58
+ return {
59
+ corners: {
60
+ permutation,
61
+ orientation
62
+ }
63
+ };
54
64
  }
@@ -6,6 +6,9 @@ const moves_js_1 = require("./moves.js");
6
6
  const random_state_js_1 = require("./random-state.js");
7
7
  const facelets_js_1 = require("./facelets.js");
8
8
  const index_js_1 = require("./solver/index.js");
9
+ // BFS_MOVES = [U, U2, U', R, R2, R', F, F2, F'] — the inverse of index m is
10
+ // CUBE2_MOVE_INVERSE[m] (U2/R2/F2 are self-inverse, so m ^ 1 does not apply).
11
+ const CUBE2_MOVE_INVERSE = [2, 1, 0, 5, 4, 3, 8, 7, 6];
9
12
  exports.cube2ScrambleGenerator = {
10
13
  info: {
11
14
  eventId: "222",
@@ -21,20 +24,31 @@ exports.cube2ScrambleGenerator = {
21
24
  };
22
25
  async function generateCube2Scramble(options = {}) {
23
26
  const state = (0, random_state_js_1.generateRandomCube2State)(options);
24
- const solution = await (0, index_js_1.solveCube2State)(state);
25
- const scrambleMoves = (0, moves_js_1.invertCube2Algorithm)(solution.moves);
27
+ const solution = (0, index_js_1.areCube2TablesLoaded)()
28
+ ? (0, index_js_1.solveCube2StateIndicesSync)(state)
29
+ : await (0, index_js_1.solveCube2StateIndices)(state);
30
+ const depth = solution.length;
31
+ // Scramble = inverse of the solution, reversed.
32
+ const scrambleMoves = new Array(depth);
33
+ for (let index = 0; index < depth; index += 1) {
34
+ scrambleMoves[index] = index_js_1.BFS_MOVES[CUBE2_MOVE_INVERSE[solution[depth - 1 - index]]];
35
+ }
26
36
  if (!options.skipValidation) {
27
- if (!(0, index_js_1.verifyCube2Scramble)(state, scrambleMoves)) {
37
+ // The scramble is the exact inverse of the solution, so verifying that
38
+ // the solution solves the target state is equivalent to verifying that
39
+ // the scramble reproduces it — a single numeric walk suffices.
40
+ if (!(0, index_js_1.verifyCube2SolutionIndices)(state, solution)) {
28
41
  throw new Error("Generated 2x2 scramble does not reproduce the target random state.");
29
42
  }
30
- if (!(0, index_js_1.verifyCube2Solution)(state, solution.moves)) {
31
- throw new Error("Generated 2x2 solution does not solve the target random state.");
32
- }
33
43
  if (scrambleMoves.length < 2) {
34
44
  throw new Error("Generated 2x2 scramble is too short.");
35
45
  }
36
46
  }
37
47
  const facelets = (0, facelets_js_1.toCube2Facelets)(state);
48
+ const solutionMoves = new Array(depth);
49
+ for (let index = 0; index < depth; index += 1) {
50
+ solutionMoves[index] = index_js_1.BFS_MOVES[solution[index]];
51
+ }
38
52
  const result = {
39
53
  eventId: "222",
40
54
  scramble: (0, moves_js_1.formatCube2Algorithm)(scrambleMoves),
@@ -45,8 +59,8 @@ async function generateCube2Scramble(options = {}) {
45
59
  state,
46
60
  facelets,
47
61
  faceletString: (0, facelets_js_1.formatCube2Facelets)(facelets),
48
- solutionMoves: solution.moves,
49
- solutionDepth: solution.depth
62
+ solutionMoves,
63
+ solutionDepth: depth
50
64
  }
51
65
  };
52
66
  if (options.seed !== undefined) {
@@ -1,7 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.verifyCube2Solution = exports.verifyCube2Scramble = exports.solveCube2State = void 0;
3
+ exports.PERMUTATION_TABLE = exports.ORIENTATION_TABLE = exports.ORIENTATION_COUNT = exports.MOVE_COUNT = exports.getOrientationTransition = exports.getPermutationTransition = exports.getDistance = exports.encodeOrientation = exports.encodePermutation = exports.ensureTablesLoaded = exports.DISTANCES = exports.BFS_MOVES = exports.areCube2TablesLoaded = exports.verifyCube2SolutionIndices = exports.verifyCube2Solution = exports.verifyCube2Scramble = exports.solveCube2StateIndicesSync = exports.solveCube2StateIndices = exports.solveCube2State = void 0;
4
4
  var search_js_1 = require("./search.js");
5
5
  Object.defineProperty(exports, "solveCube2State", { enumerable: true, get: function () { return search_js_1.solveCube2State; } });
6
+ Object.defineProperty(exports, "solveCube2StateIndices", { enumerable: true, get: function () { return search_js_1.solveCube2StateIndices; } });
7
+ Object.defineProperty(exports, "solveCube2StateIndicesSync", { enumerable: true, get: function () { return search_js_1.solveCube2StateIndicesSync; } });
6
8
  Object.defineProperty(exports, "verifyCube2Scramble", { enumerable: true, get: function () { return search_js_1.verifyCube2Scramble; } });
7
9
  Object.defineProperty(exports, "verifyCube2Solution", { enumerable: true, get: function () { return search_js_1.verifyCube2Solution; } });
10
+ Object.defineProperty(exports, "verifyCube2SolutionIndices", { enumerable: true, get: function () { return search_js_1.verifyCube2SolutionIndices; } });
11
+ var tables_js_1 = require("./tables.js");
12
+ Object.defineProperty(exports, "areCube2TablesLoaded", { enumerable: true, get: function () { return tables_js_1.areCube2TablesLoaded; } });
13
+ Object.defineProperty(exports, "BFS_MOVES", { enumerable: true, get: function () { return tables_js_1.BFS_MOVES; } });
14
+ Object.defineProperty(exports, "DISTANCES", { enumerable: true, get: function () { return tables_js_1.DISTANCES; } });
15
+ Object.defineProperty(exports, "ensureTablesLoaded", { enumerable: true, get: function () { return tables_js_1.ensureTablesLoaded; } });
16
+ Object.defineProperty(exports, "encodePermutation", { enumerable: true, get: function () { return tables_js_1.encodePermutation; } });
17
+ Object.defineProperty(exports, "encodeOrientation", { enumerable: true, get: function () { return tables_js_1.encodeOrientation; } });
18
+ Object.defineProperty(exports, "getDistance", { enumerable: true, get: function () { return tables_js_1.getDistance; } });
19
+ Object.defineProperty(exports, "getPermutationTransition", { enumerable: true, get: function () { return tables_js_1.getPermutationTransition; } });
20
+ Object.defineProperty(exports, "getOrientationTransition", { enumerable: true, get: function () { return tables_js_1.getOrientationTransition; } });
21
+ Object.defineProperty(exports, "MOVE_COUNT", { enumerable: true, get: function () { return tables_js_1.MOVE_COUNT; } });
22
+ Object.defineProperty(exports, "ORIENTATION_COUNT", { enumerable: true, get: function () { return tables_js_1.ORIENTATION_COUNT; } });
23
+ Object.defineProperty(exports, "ORIENTATION_TABLE", { enumerable: true, get: function () { return tables_js_1.ORIENTATION_TABLE; } });
24
+ Object.defineProperty(exports, "PERMUTATION_TABLE", { enumerable: true, get: function () { return tables_js_1.PERMUTATION_TABLE; } });
@@ -1,12 +1,36 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.solveCube2State = solveCube2State;
4
+ exports.solveCube2StateIndices = solveCube2StateIndices;
5
+ exports.solveCube2StateIndicesSync = solveCube2StateIndicesSync;
4
6
  exports.verifyCube2Scramble = verifyCube2Scramble;
5
7
  exports.verifyCube2Solution = verifyCube2Solution;
8
+ exports.verifyCube2SolutionIndices = verifyCube2SolutionIndices;
6
9
  const state_js_1 = require("../state.js");
7
10
  const tables_js_1 = require("./tables.js");
8
11
  async function solveCube2State(state) {
9
12
  await (0, tables_js_1.ensureTablesLoaded)();
13
+ const indices = solveWithTablesIndices(state);
14
+ return {
15
+ moves: indices.map((index) => tables_js_1.BFS_MOVES[index]),
16
+ depth: indices.length,
17
+ state: (0, state_js_1.createSolvedCube2State)()
18
+ };
19
+ }
20
+ // Index-based solve for the hot scramble path: returns the solution as move
21
+ // indices (0..8) in walk order. The inverse of index m is CUBE2_MOVE_INVERSE[m]
22
+ // (U2/R2/F2 are self-inverse, so m ^ 1 does not apply here).
23
+ async function solveCube2StateIndices(state) {
24
+ await (0, tables_js_1.ensureTablesLoaded)();
25
+ return solveWithTablesIndices(state);
26
+ }
27
+ function solveCube2StateIndicesSync(state) {
28
+ if (!tables_js_1.DISTANCES) {
29
+ throw new Error("Cube2 solver tables not loaded. Call ensureTablesLoaded() first.");
30
+ }
31
+ return solveWithTablesIndices(state);
32
+ }
33
+ function solveWithTablesIndices(state) {
10
34
  if (state.corners.permutation[6] !== 6 || state.corners.orientation[6] !== 0) {
11
35
  throw new Error("The 2x2 solver requires the DBL corner (index 6) to be solved " +
12
36
  "as the frame of reference. Please normalize the state before solving.");
@@ -21,7 +45,7 @@ async function solveCube2State(state) {
21
45
  const initialCombined = permIndex * tables_js_1.ORIENTATION_COUNT + orientIndex;
22
46
  let distance = (distances[initialCombined >> 1] >> ((initialCombined & 1) << 2)) & 0x0f;
23
47
  if (distance === 0) {
24
- return { moves, depth: 0, state: (0, state_js_1.createSolvedCube2State)() };
48
+ return moves;
25
49
  }
26
50
  while (distance > 0) {
27
51
  let foundNext = false;
@@ -35,7 +59,7 @@ async function solveCube2State(state) {
35
59
  const combined = nextPerm * tables_js_1.ORIENTATION_COUNT + nextOrient;
36
60
  const nextDist = (distances[combined >> 1] >> ((combined & 1) << 2)) & 0x0f;
37
61
  if (nextDist === nextDistance) {
38
- moves.push(tables_js_1.BFS_MOVES[m]);
62
+ moves.push(m);
39
63
  permIndex = nextPerm;
40
64
  orientIndex = nextOrient;
41
65
  distance = nextDistance;
@@ -47,11 +71,7 @@ async function solveCube2State(state) {
47
71
  throw new Error("BFS distance table corrupted or unreachable state.");
48
72
  }
49
73
  }
50
- return {
51
- moves,
52
- depth: moves.length,
53
- state: (0, state_js_1.createSolvedCube2State)()
54
- };
74
+ return moves;
55
75
  }
56
76
  const MOVE_INDEX = {};
57
77
  for (let i = 0; i < tables_js_1.BFS_MOVES.length; i++) {
@@ -79,3 +99,21 @@ function verifyCube2Solution(state, solutionMoves) {
79
99
  const { permIndex, orientIndex } = walkCube2Coordinates((0, tables_js_1.encodePermutation)(state.corners.permutation), (0, tables_js_1.encodeOrientation)(state.corners.orientation), solutionMoves);
80
100
  return permIndex === 0 && orientIndex === 0;
81
101
  }
102
+ // Numeric verification for the hot scramble path: walks the solution (move
103
+ // indices, walk order) from the target state to solved. The scramble is the
104
+ // exact inverse of the solution, so this is equivalent to verifying that the
105
+ // scramble reproduces the target state — one walk, no string parsing.
106
+ function verifyCube2SolutionIndices(state, solution) {
107
+ const permTable = tables_js_1.PERMUTATION_TABLE;
108
+ const orientTable = tables_js_1.ORIENTATION_TABLE;
109
+ if (!permTable || !orientTable) {
110
+ throw new Error("Cube2 solver tables not loaded. Call ensureTablesLoaded() first.");
111
+ }
112
+ let permIndex = (0, tables_js_1.encodePermutation)(state.corners.permutation);
113
+ let orientIndex = (0, tables_js_1.encodeOrientation)(state.corners.orientation);
114
+ for (const m of solution) {
115
+ permIndex = permTable[permIndex * tables_js_1.MOVE_COUNT + m];
116
+ orientIndex = orientTable[orientIndex * tables_js_1.MOVE_COUNT + m];
117
+ }
118
+ return permIndex === 0 && orientIndex === 0;
119
+ }
@@ -4,6 +4,7 @@ exports.BFS_MOVES = exports.DISTANCES = exports.ORIENTATION_TABLE = exports.PERM
4
4
  exports.encodePermutation = encodePermutation;
5
5
  exports.encodeOrientation = encodeOrientation;
6
6
  exports.ensureTablesLoaded = ensureTablesLoaded;
7
+ exports.areCube2TablesLoaded = areCube2TablesLoaded;
7
8
  exports.getDistance = getDistance;
8
9
  exports.getPermutationTransition = getPermutationTransition;
9
10
  exports.getOrientationTransition = getOrientationTransition;
@@ -61,6 +62,9 @@ async function ensureTablesLoaded() {
61
62
  exports.DISTANCES = tables.distances;
62
63
  tablesLoaded = true;
63
64
  }
65
+ function areCube2TablesLoaded() {
66
+ return tablesLoaded;
67
+ }
64
68
  // ---------------------------------------------------------------------------
65
69
  // Public API
66
70
  // ---------------------------------------------------------------------------
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateCube2State = validateCube2State;
4
4
  exports.isOneMoveFromSolvedCube2State = isOneMoveFromSolvedCube2State;
5
+ exports.isTrivialSamplingCube2State = isTrivialSamplingCube2State;
5
6
  exports.isAllowedRandomCube2State = isAllowedRandomCube2State;
6
7
  exports.permutationParity = permutationParity;
7
8
  const types_js_1 = require("./types.js");
@@ -37,7 +38,13 @@ function validateCube2State(state) {
37
38
  function isOneMoveFromSolvedCube2State(state) {
38
39
  const p = (0, tables_js_1.encodePermutation)(state.corners.permutation);
39
40
  const o = (0, tables_js_1.encodeOrientation)(state.corners.orientation);
40
- return ONE_MOVE_FROM_SOLVED_ENCODED.some((entry) => entry.p === p && entry.o === o);
41
+ return isTrivialSamplingCube2State(p, o);
42
+ }
43
+ // Cheap trivial check for random-state sampling: the caller has already
44
+ // encoded the coordinates once. True for the solved state and the 18
45
+ // single-move states (which would produce scrambles shorter than 2 moves).
46
+ function isTrivialSamplingCube2State(p, o) {
47
+ return (p === 0 && o === 0) || ONE_MOVE_FROM_SOLVED_ENCODED.some((entry) => entry.p === p && entry.o === o);
41
48
  }
42
49
  function isAllowedRandomCube2State(state) {
43
50
  return validateCube2State(state).valid &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cubingcenter/scrambler",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Extensible scramble generation toolkit for CubingCenter projects.",
5
5
  "type": "module",
6
6
  "license": "MIT",