@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
@@ -75,7 +75,8 @@ async function loadGeneratedCube3Phase1Tables() {
75
75
  edgeOrientation: tables.pruningTables.edgeOrientation,
76
76
  udSlice: tables.pruningTables.udSlice,
77
77
  cornerOrientationEdgeOrientation: tables.pruningTables.cornerOrientationEdgeOrientation
78
- }
78
+ },
79
+ jointTables: tables.jointTables
79
80
  };
80
81
  }
81
82
  async function loadGeneratedCube3Phase2Tables() {
@@ -32,7 +32,29 @@ function readGeneratedCube3SolverTables() {
32
32
  slicePermutation: readPruningTable(assetDirectory, manifest.pruningTables.slicePermutation),
33
33
  cornerPermutationSlicePermutation: readPruningTable(assetDirectory, manifest.pruningTables.cornerPermutationSlicePermutation),
34
34
  udEdgePermutationSlicePermutation: readPruningTable(assetDirectory, manifest.pruningTables.udEdgePermutationSlicePermutation)
35
+ },
36
+ jointTables: readJointTables(assetDirectory, manifest.jointTables)
37
+ };
38
+ }
39
+ function readJointTables(assetDirectory, entries) {
40
+ if (!entries?.udCo || !entries.udEo) {
41
+ return undefined;
42
+ }
43
+ const readJoint = (entry) => {
44
+ const buffer = (0, node_fs_1.readFileSync)(node_path_1.default.join(assetDirectory, entry.file));
45
+ const expectedBytes = Math.ceil((entry.rangeA * entry.rangeB) / 2);
46
+ if (buffer.length !== expectedBytes) {
47
+ throw new Error(`Invalid byte size for ${entry.file}.`);
35
48
  }
49
+ const values = new Uint8Array(buffer);
50
+ if (checksum(values) !== entry.checksum) {
51
+ throw new Error(`Checksum mismatch for ${entry.file}.`);
52
+ }
53
+ return values;
54
+ };
55
+ return {
56
+ jointUdCo: readJoint(entries.udCo),
57
+ jointUdEo: readJoint(entries.udEo)
36
58
  };
37
59
  }
38
60
  function readManifest(assetDirectory) {
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ /**
3
+ * Browser loader for the combined (cp x uep) pair pruning tables.
4
+ *
5
+ * Fetches the plain .bin files from the web assets base URL (the web sync
6
+ * script copies `generated/pair/` wholesale; compression variants are not
7
+ * used for the pair tables yet — the 55 MB dist table is the dominant
8
+ * download either way).
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.loadBrowserPairTables = loadBrowserPairTables;
12
+ const pair_tables_types_js_1 = require("./pair-tables-types.js");
13
+ const PAIR_DIR = "puzzles/cube3/solver/generated/pair";
14
+ function resolveBaseUrl(provided) {
15
+ if (provided) {
16
+ return provided.replace(/\/$/, "");
17
+ }
18
+ if (typeof document !== "undefined") {
19
+ const script = document.currentScript;
20
+ if (script?.src) {
21
+ const base = new URL(".", script.src).href;
22
+ return base.replace(/\/$/, "");
23
+ }
24
+ }
25
+ return ".";
26
+ }
27
+ async function loadBrowserPairTables(baseUrl) {
28
+ const resolvedBase = resolveBaseUrl(baseUrl);
29
+ const manifest = await fetchManifest(resolvedBase);
30
+ if (manifest === undefined) {
31
+ return undefined;
32
+ }
33
+ const fetchBin = async (file, expectedBytes, expectedChecksum) => {
34
+ const url = `${resolvedBase}/${PAIR_DIR}/${file}`;
35
+ const response = await fetch(url);
36
+ if (!response.ok) {
37
+ throw new Error(`Failed to load pair table ${url}: ${response.status} ${response.statusText}`);
38
+ }
39
+ const buffer = await response.arrayBuffer();
40
+ if (buffer.byteLength !== expectedBytes) {
41
+ throw new Error(`Invalid byte size for ${file}.`);
42
+ }
43
+ const values = new Uint8Array(buffer);
44
+ const actual = (0, pair_tables_types_js_1.checksumBytes)(values);
45
+ if (actual !== expectedChecksum) {
46
+ throw new Error(`Checksum mismatch for ${file}: expected ${expectedChecksum}, received ${actual}.`);
47
+ }
48
+ return values;
49
+ };
50
+ const fetchU16 = async (file, count, expectedChecksum) => {
51
+ const values = new Uint16Array(count);
52
+ const buffer = await fetchBin(file, count * 2, expectedChecksum);
53
+ for (let index = 0; index < count; index += 1) {
54
+ values[index] = buffer[index * 2] | (buffer[index * 2 + 1] << 8);
55
+ }
56
+ return values;
57
+ };
58
+ const fetchU32 = async (file, count, expectedChecksum) => {
59
+ const values = new Uint32Array(count);
60
+ const buffer = await fetchBin(file, count * 4, expectedChecksum);
61
+ for (let index = 0; index < count; index += 1) {
62
+ values[index] =
63
+ buffer[index * 4] |
64
+ (buffer[index * 4 + 1] << 8) |
65
+ (buffer[index * 4 + 2] << 16) |
66
+ (buffer[index * 4 + 3] << 24);
67
+ }
68
+ return values;
69
+ };
70
+ const canonCpMin = await fetchU16("phase2-pair-canoncpmin.bin", 40320, manifest.checksums.canonCpMin);
71
+ const canonTauStart = await fetchU32("phase2-pair-canonctaustart.bin", 40321, manifest.checksums.canonTauStart);
72
+ const canonTaus = await fetchBin("phase2-pair-canonctaus.bin", canonTauStart[40320], manifest.checksums.canonTaus);
73
+ const conjEp = await fetchU16("phase2-pair-conjep.bin", 16 * 40320, manifest.checksums.conjEp);
74
+ const dist = await fetchBin("phase2-pair-dist.bin", manifest.tableBytes, manifest.checksums.dist);
75
+ return {
76
+ denseMin: (0, pair_tables_types_js_1.buildDenseMin)(canonCpMin, manifest.nMinima),
77
+ canonCpMin,
78
+ canonTauStart,
79
+ canonTaus,
80
+ conjEp,
81
+ dist,
82
+ nMinima: manifest.nMinima,
83
+ maxDepth: manifest.maxDepth
84
+ };
85
+ }
86
+ async function fetchManifest(baseUrl) {
87
+ const url = `${baseUrl}/${PAIR_DIR}/phase2-pair-manifest.json`;
88
+ const response = await fetch(url);
89
+ if (response.status === 404) {
90
+ return undefined;
91
+ }
92
+ if (!response.ok) {
93
+ throw new Error(`Failed to load pair table manifest from ${url}: ${response.status} ${response.statusText}`);
94
+ }
95
+ return await response.json();
96
+ }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ /**
3
+ * Node loader for the combined (cp x uep) pair pruning tables.
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.loadNodePairTables = loadNodePairTables;
10
+ const node_fs_1 = require("node:fs");
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const pair_tables_types_js_1 = require("./pair-tables-types.js");
13
+ const PAIR_DIR = "puzzles/cube3/solver/generated/pair";
14
+ function loadNodePairTables() {
15
+ const dir = resolvePairDirectory();
16
+ if (dir === undefined) {
17
+ return undefined;
18
+ }
19
+ const manifest = JSON.parse((0, node_fs_1.readFileSync)(node_path_1.default.join(dir, "phase2-pair-manifest.json"), "utf8"));
20
+ const readBin = (file, expectedBytes, expectedChecksum) => {
21
+ const buffer = (0, node_fs_1.readFileSync)(node_path_1.default.join(dir, file));
22
+ if (buffer.length !== expectedBytes) {
23
+ throw new Error(`Invalid byte size for ${file}.`);
24
+ }
25
+ const values = new Uint8Array(buffer);
26
+ const actual = (0, pair_tables_types_js_1.checksumBytes)(values);
27
+ if (actual !== expectedChecksum) {
28
+ throw new Error(`Checksum mismatch for ${file}: expected ${expectedChecksum}, received ${actual}.`);
29
+ }
30
+ return values;
31
+ };
32
+ const readU16 = (file, count, expectedChecksum) => {
33
+ const values = new Uint16Array(count);
34
+ const buffer = readBin(file, count * 2, expectedChecksum);
35
+ for (let index = 0; index < count; index += 1) {
36
+ values[index] = buffer[index * 2] | (buffer[index * 2 + 1] << 8);
37
+ }
38
+ return values;
39
+ };
40
+ const readU32 = (file, count, expectedChecksum) => {
41
+ const values = new Uint32Array(count);
42
+ const buffer = readBin(file, count * 4, expectedChecksum);
43
+ for (let index = 0; index < count; index += 1) {
44
+ values[index] =
45
+ buffer[index * 4] |
46
+ (buffer[index * 4 + 1] << 8) |
47
+ (buffer[index * 4 + 2] << 16) |
48
+ (buffer[index * 4 + 3] << 24);
49
+ }
50
+ return values;
51
+ };
52
+ const canonCpMin = readU16("phase2-pair-canoncpmin.bin", 40320, manifest.checksums.canonCpMin);
53
+ const canonTauStart = readU32("phase2-pair-canonctaustart.bin", 40321, manifest.checksums.canonTauStart);
54
+ const canonTaus = readBin("phase2-pair-canonctaus.bin", canonTauStart[40320], manifest.checksums.canonTaus);
55
+ const conjEp = readU16("phase2-pair-conjep.bin", 16 * 40320, manifest.checksums.conjEp);
56
+ const dist = readBin("phase2-pair-dist.bin", manifest.tableBytes, manifest.checksums.dist);
57
+ return {
58
+ denseMin: (0, pair_tables_types_js_1.buildDenseMin)(canonCpMin, manifest.nMinima),
59
+ canonCpMin,
60
+ canonTauStart,
61
+ canonTaus,
62
+ conjEp,
63
+ dist,
64
+ nMinima: manifest.nMinima,
65
+ maxDepth: manifest.maxDepth
66
+ };
67
+ }
68
+ function resolvePairDirectory() {
69
+ const envDirectory = process.env.CUBINGCENTER_SCRAMBLER_TABLES_DIR;
70
+ if (envDirectory && (0, node_fs_1.existsSync)(node_path_1.default.join(envDirectory, "pair", "phase2-pair-manifest.json"))) {
71
+ return node_path_1.default.join(envDirectory, "pair");
72
+ }
73
+ const cwd = process.cwd();
74
+ const candidates = [
75
+ node_path_1.default.join(cwd, "src", PAIR_DIR),
76
+ node_path_1.default.join(cwd, "dist", PAIR_DIR),
77
+ node_path_1.default.join(cwd, "dist-cjs", PAIR_DIR)
78
+ ];
79
+ for (const candidate of candidates) {
80
+ if ((0, node_fs_1.existsSync)(node_path_1.default.join(candidate, "phase2-pair-manifest.json"))) {
81
+ return candidate;
82
+ }
83
+ }
84
+ return undefined;
85
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ /**
3
+ * Pair-table types shared by the node and browser loaders.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.checksumBytes = checksumBytes;
7
+ exports.buildDenseMin = buildDenseMin;
8
+ function checksumBytes(values) {
9
+ let hash = 0x811c9dc5;
10
+ for (let index = 0; index < values.length; index += 1) {
11
+ hash ^= values[index];
12
+ hash = Math.imul(hash, 0x01000193);
13
+ }
14
+ return hash >>> 0;
15
+ }
16
+ /**
17
+ * Builds the dense class map (first-appearance order, matching exp3-gen).
18
+ */
19
+ function buildDenseMin(canonCpMin, expectedCount) {
20
+ const denseMin = new Uint16Array(40320).fill(0xffff);
21
+ let next = 0;
22
+ for (let cp = 0; cp < 40320; cp += 1) {
23
+ const minCp = canonCpMin[cp];
24
+ if (denseMin[minCp] === 0xffff) {
25
+ denseMin[minCp] = next;
26
+ next += 1;
27
+ }
28
+ }
29
+ if (next !== expectedCount) {
30
+ throw new Error(`Dense class count mismatch: ${next} != ${expectedCount}.`);
31
+ }
32
+ return denseMin;
33
+ }
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ /**
3
+ * Combined (cp x uep) phase-2 pruning table (experiment:
4
+ * experiment/cube3-pair-pruning). The exact distance to (cp==0 && uep==0)
5
+ * with the slice permutation free, canonicalized under the slice-16
6
+ * conjugation group:
7
+ *
8
+ * index = denseMin[canonCpMin[cp]] * 40320 + uep*
9
+ * uep* = min over the cp-minimizer set (canonTaus) of conjEp[g][uep]
10
+ *
11
+ * The distance is ~3.5-4 moves tighter than max(cpsp, uepsp) (the plain
12
+ * phase-2 bound) and admissible for the joint phase-2 problem
13
+ * (d_pair <= d_joint), so it prunes the deep phase-2 searches
14
+ * exponentially. Measured in the native C++ repo (exp3): phase-2 drops
15
+ * from ~34k nodes to ~37 (990x) and the full scramble from ~5.7 ms to
16
+ * ~1.2 ms. Generated by the C++ tool `tools/exp3-gen`, ~1 min,
17
+ * deterministic, FNV-1a checksummed.
18
+ *
19
+ * The tables live in `puzzles/cube3/solver/generated/pair/` next to the
20
+ * standard solver tables.
21
+ */
22
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ var desc = Object.getOwnPropertyDescriptor(m, k);
25
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
26
+ desc = { enumerable: true, get: function() { return m[k]; } };
27
+ }
28
+ Object.defineProperty(o, k2, desc);
29
+ }) : (function(o, m, k, k2) {
30
+ if (k2 === undefined) k2 = k;
31
+ o[k2] = m[k];
32
+ }));
33
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
34
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
35
+ }) : function(o, v) {
36
+ o["default"] = v;
37
+ });
38
+ var __importStar = (this && this.__importStar) || (function () {
39
+ var ownKeys = function(o) {
40
+ ownKeys = Object.getOwnPropertyNames || function (o) {
41
+ var ar = [];
42
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
+ return ar;
44
+ };
45
+ return ownKeys(o);
46
+ };
47
+ return function (mod) {
48
+ if (mod && mod.__esModule) return mod;
49
+ var result = {};
50
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
+ __setModuleDefault(result, mod);
52
+ return result;
53
+ };
54
+ })();
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.getCachedPairTables = getCachedPairTables;
57
+ exports.loadCube3PairTables = loadCube3PairTables;
58
+ exports.pairBound = pairBound;
59
+ let cached;
60
+ function getCachedPairTables() {
61
+ return cached;
62
+ }
63
+ async function loadCube3PairTables() {
64
+ if (cached === undefined) {
65
+ cached = await loadPairTables();
66
+ }
67
+ return cached;
68
+ }
69
+ async function loadPairTables() {
70
+ try {
71
+ if (typeof self !== "undefined") {
72
+ const loader = await Promise.resolve().then(() => __importStar(require(
73
+ /* webpackIgnore: true */
74
+ /* @vite-ignore */
75
+ "./pair-browser-loader.js")));
76
+ return await loader.loadBrowserPairTables();
77
+ }
78
+ const loader = await Promise.resolve().then(() => __importStar(require(
79
+ /* webpackIgnore: true */
80
+ /* @vite-ignore */
81
+ "./pair-node-loader.js")));
82
+ return loader.loadNodePairTables();
83
+ }
84
+ catch {
85
+ return undefined;
86
+ }
87
+ }
88
+ /**
89
+ * The pair distance of (cp, uep) — the exact distance to (cp==0 && uep==0)
90
+ * with the slice free. Admissible as a phase-2 lower bound.
91
+ */
92
+ function pairBound(tables, cp, uep) {
93
+ const minCp = tables.canonCpMin[cp];
94
+ const start = tables.canonTauStart[cp];
95
+ const end = tables.canonTauStart[cp + 1];
96
+ let bestUep = 0x7fffffff;
97
+ for (let index = start; index < end; index += 1) {
98
+ const g = tables.canonTaus[index];
99
+ const e = tables.conjEp[g * 40320 + uep];
100
+ if (e < bestUep) {
101
+ bestUep = e;
102
+ }
103
+ }
104
+ const pairIndex = tables.denseMin[minCp] * 40320 + bestUep;
105
+ const nibble = tables.dist[pairIndex >> 1];
106
+ return (nibble >> ((pairIndex & 1) << 2)) & 0x0f;
107
+ }
@@ -32,11 +32,13 @@ const PHASE1_VALID_MOVES = Array.from({ length: 7 }, (_, prevPlusOne) => Array.f
32
32
  }));
33
33
  async function solveCube3Phase1(state, options = {}) {
34
34
  const tables = options.tables ?? await (0, tables_js_1.loadCube3Phase1Tables)();
35
+ const jointTables = options.jointTables ?? tables.jointTables;
35
36
  const maxDepth = options.maxDepth ?? 12;
36
37
  const start = encodeCube3Phase1Coordinates(state);
37
38
  const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
38
39
  const context = {
39
40
  tables,
41
+ jointTables,
40
42
  maxDepth,
41
43
  deadline,
42
44
  visitedNodes: 0,
@@ -56,7 +58,19 @@ async function solveCube3Phase1(state, options = {}) {
56
58
  const combinedIndex = (start.cornerOrientation << 11) + start.edgeOrientation;
57
59
  const coeo = (coeoTable[combinedIndex >> 1] >> ((combinedIndex & 1) << 2)) & 0x0f;
58
60
  const udSliceDist = (udPruningTable[start.udSlice >> 1] >> ((start.udSlice & 1) << 2)) & 0x0f;
59
- const minDepth = coeo > udSliceDist ? coeo : udSliceDist;
61
+ let minDepth = coeo > udSliceDist ? coeo : udSliceDist;
62
+ if (jointTables) {
63
+ const jc = (jointTables.jointUdCo[(start.udSlice * 2187 + start.cornerOrientation) >> 1] >>
64
+ (((start.udSlice * 2187 + start.cornerOrientation) & 1) << 2)) & 0x0f;
65
+ if (jc > minDepth) {
66
+ minDepth = jc;
67
+ }
68
+ const je = (jointTables.jointUdEo[(start.udSlice * 2048 + start.edgeOrientation) >> 1] >>
69
+ (((start.udSlice * 2048 + start.edgeOrientation) & 1) << 2)) & 0x0f;
70
+ if (je > minDepth) {
71
+ minDepth = je;
72
+ }
73
+ }
60
74
  for (let depth = minDepth; depth <= maxDepth; depth += 1) {
61
75
  const path = [];
62
76
  const solved = searchPhase1(start.cornerOrientation, start.edgeOrientation, start.udSlice, -1, -1, depth, path, context);
@@ -128,10 +142,26 @@ function searchPhase1(cornerOrientation, edgeOrientation, udSlice, previousFace,
128
142
  const combinedIndex = (nextCornerOrientation << 11) + nextEdgeOrientation;
129
143
  const coeo = (coeoTable[combinedIndex >> 1] >> ((combinedIndex & 1) << 2)) & 0x0f;
130
144
  const udSliceDist = (udPruningTable[nextUdSlice >> 1] >> ((nextUdSlice & 1) << 2)) & 0x0f;
131
- const nextLowerBound = coeo > udSliceDist ? coeo : udSliceDist;
145
+ let nextLowerBound = coeo > udSliceDist ? coeo : udSliceDist;
132
146
  if (nextLowerBound > remainingDepth - 1) {
133
147
  continue;
134
148
  }
149
+ const jointTables = context.jointTables;
150
+ if (jointTables) {
151
+ const udCoIndex = nextUdSlice * 2187 + nextCornerOrientation;
152
+ const jc = (jointTables.jointUdCo[udCoIndex >> 1] >> ((udCoIndex & 1) << 2)) & 0x0f;
153
+ if (jc > nextLowerBound) {
154
+ nextLowerBound = jc;
155
+ }
156
+ const udEoIndex = nextUdSlice * 2048 + nextEdgeOrientation;
157
+ const je = (jointTables.jointUdEo[udEoIndex >> 1] >> ((udEoIndex & 1) << 2)) & 0x0f;
158
+ if (je > nextLowerBound) {
159
+ nextLowerBound = je;
160
+ }
161
+ if (nextLowerBound > remainingDepth - 1) {
162
+ continue;
163
+ }
164
+ }
135
165
  const face = PHASE1_MOVE_FACES[moveIndex];
136
166
  path.push(context.tables.phase1Moves[moveIndex]);
137
167
  if (searchPhase1(nextCornerOrientation, nextEdgeOrientation, nextUdSlice, face, previousFace, remainingDepth - 1, path, context)) {
@@ -12,6 +12,7 @@ const coordinates_js_1 = require("./coordinates.js");
12
12
  const phase1_js_1 = require("./phase1.js");
13
13
  const tables_js_1 = require("./tables.js");
14
14
  const search_utils_js_1 = require("./search-utils.js");
15
+ const pair_tables_js_1 = require("./pair-tables.js");
15
16
  class Cube3Phase2SearchError extends Error {
16
17
  visitedNodes;
17
18
  constructor(message, visitedNodes) {
@@ -49,11 +50,13 @@ async function solveCube3Phase2(state, options = {}) {
49
50
  throw new Cube3Phase2SearchError("3x3 Phase 2 requires a state already in G1.", 0);
50
51
  }
51
52
  const tables = options.tables ?? await (0, tables_js_1.loadCube3Phase2Tables)();
53
+ const pairTables = options.pairTables ?? await (0, pair_tables_js_1.loadCube3PairTables)();
52
54
  const maxDepth = options.maxDepth ?? 18;
53
55
  const start = encodeCube3Phase2Coordinates(state);
54
56
  const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
55
57
  const context = {
56
58
  tables,
59
+ pairTables,
57
60
  deadline,
58
61
  visitedNodes: 0,
59
62
  timedOut: false
@@ -65,7 +68,13 @@ async function solveCube3Phase2(state, options = {}) {
65
68
  const combinedEPIndex = start.udEdgePermutation * spRange + start.slicePermutation;
66
69
  const cpDist = (cpspTable[combinedCPIndex >> 1] >> ((combinedCPIndex & 1) << 2)) & 0x0f;
67
70
  const epDist = (epspTable[combinedEPIndex >> 1] >> ((combinedEPIndex & 1) << 2)) & 0x0f;
68
- const minDepth = cpDist > epDist ? cpDist : epDist;
71
+ let minDepth = cpDist > epDist ? cpDist : epDist;
72
+ if (pairTables) {
73
+ const pair = (0, pair_tables_js_1.pairBound)(pairTables, start.cornerPermutation, start.udEdgePermutation);
74
+ if (pair > minDepth) {
75
+ minDepth = pair;
76
+ }
77
+ }
69
78
  const initialPreviousFace = options.previousFace ?? -1;
70
79
  const initialPreviousPreviousFace = options.previousPreviousFace ?? -1;
71
80
  for (let depth = minDepth; depth <= maxDepth; depth += 1) {
@@ -160,6 +169,38 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
160
169
  const cpOffset = cornerPermutation * moveCount;
161
170
  const epOffset = udEdgePermutation * moveCount;
162
171
  const spOffset = slicePermutation * moveCount;
172
+ // Without the pair tables (the default npm package), keep the exact
173
+ // reference traversal — zero overhead. With them (turbo tier), collect the
174
+ // children with the pair-distance bound and explore in ascending bound
175
+ // order (stable: ties keep the reference move order): same depth-minimal
176
+ // solutions, far fewer nodes.
177
+ if (context.pairTables === undefined) {
178
+ for (let i = 0; i < validMoves.length; i += 1) {
179
+ const moveIndex = validMoves[i];
180
+ const nextCornerPermutation = cpTable[cpOffset + moveIndex];
181
+ const nextUdEdgePermutation = epTable[epOffset + moveIndex];
182
+ const nextSlicePermutation = spTable[spOffset + moveIndex];
183
+ const combinedCPIndex = nextCornerPermutation * spRange + nextSlicePermutation;
184
+ const combinedEPIndex = nextUdEdgePermutation * spRange + nextSlicePermutation;
185
+ const cpDist = (cpspTable[combinedCPIndex >> 1] >> ((combinedCPIndex & 1) << 2)) & 0x0f;
186
+ const epDist = (epspTable[combinedEPIndex >> 1] >> ((combinedEPIndex & 1) << 2)) & 0x0f;
187
+ const nextLowerBound = cpDist > epDist ? cpDist : epDist;
188
+ if (nextLowerBound > remainingDepth - 1) {
189
+ continue;
190
+ }
191
+ path.push(context.tables.phase2Moves[moveIndex]);
192
+ if (searchPhase2(nextCornerPermutation, nextUdEdgePermutation, nextSlicePermutation, PHASE2_MOVE_FACES[moveIndex], previousFace, remainingDepth - 1, path, context)) {
193
+ return true;
194
+ }
195
+ path.pop();
196
+ if (context.timedOut) {
197
+ return false;
198
+ }
199
+ }
200
+ return false;
201
+ }
202
+ const pairTables = context.pairTables;
203
+ const children = [];
163
204
  for (let i = 0; i < validMoves.length; i += 1) {
164
205
  const moveIndex = validMoves[i];
165
206
  const nextCornerPermutation = cpTable[cpOffset + moveIndex];
@@ -169,13 +210,29 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
169
210
  const combinedEPIndex = nextUdEdgePermutation * spRange + nextSlicePermutation;
170
211
  const cpDist = (cpspTable[combinedCPIndex >> 1] >> ((combinedCPIndex & 1) << 2)) & 0x0f;
171
212
  const epDist = (epspTable[combinedEPIndex >> 1] >> ((combinedEPIndex & 1) << 2)) & 0x0f;
172
- const nextLowerBound = cpDist > epDist ? cpDist : epDist;
213
+ let nextLowerBound = cpDist > epDist ? cpDist : epDist;
173
214
  if (nextLowerBound > remainingDepth - 1) {
174
215
  continue;
175
216
  }
176
- const face = PHASE2_MOVE_FACES[moveIndex];
177
- path.push(context.tables.phase2Moves[moveIndex]);
178
- if (searchPhase2(nextCornerPermutation, nextUdEdgePermutation, nextSlicePermutation, face, previousFace, remainingDepth - 1, path, context)) {
217
+ const pair = (0, pair_tables_js_1.pairBound)(pairTables, nextCornerPermutation, nextUdEdgePermutation);
218
+ if (pair > nextLowerBound) {
219
+ nextLowerBound = pair;
220
+ }
221
+ if (nextLowerBound > remainingDepth - 1) {
222
+ continue;
223
+ }
224
+ children.push({
225
+ moveIndex,
226
+ lowerBound: nextLowerBound,
227
+ cornerPermutation: nextCornerPermutation,
228
+ udEdgePermutation: nextUdEdgePermutation,
229
+ slicePermutation: nextSlicePermutation
230
+ });
231
+ }
232
+ children.sort((left, right) => left.lowerBound - right.lowerBound);
233
+ for (const child of children) {
234
+ path.push(context.tables.phase2Moves[child.moveIndex]);
235
+ if (searchPhase2(child.cornerPermutation, child.udEdgePermutation, child.slicePermutation, PHASE2_MOVE_FACES[child.moveIndex], previousFace, remainingDepth - 1, path, context)) {
179
236
  return true;
180
237
  }
181
238
  path.pop();
@@ -226,6 +283,7 @@ function searchPhase1Interleaved(cornerOrientation, edgeOrientation, udSlice, pr
226
283
  const maxPhase2Depth = 6;
227
284
  const phase2Context = {
228
285
  tables: context.phase2Tables,
286
+ pairTables: undefined,
229
287
  deadline: context.deadline,
230
288
  visitedNodes: 0,
231
289
  timedOut: false
@@ -60,7 +60,8 @@ function generateCube3Phase1Tables() {
60
60
  edgeOrientation: generateCube3PruningTable(edgeOrientation),
61
61
  udSlice: generateCube3PruningTable(udSlice),
62
62
  cornerOrientationEdgeOrientation: generateCube3CombinedPruningTable(cornerOrientation, edgeOrientation)
63
- }
63
+ },
64
+ jointTables: undefined
64
65
  };
65
66
  }
66
67
  function generateCube3SolverTables() {
@@ -87,7 +88,8 @@ function generateCube3SolverTables() {
87
88
  slicePermutation: phase2Tables.pruningTables.slicePermutation,
88
89
  cornerPermutationSlicePermutation: phase2Tables.pruningTables.cornerPermutationSlicePermutation,
89
90
  udEdgePermutationSlicePermutation: phase2Tables.pruningTables.udEdgePermutationSlicePermutation
90
- }
91
+ },
92
+ jointTables: undefined
91
93
  };
92
94
  }
93
95
  function generateCube3Phase2Tables() {