@cubingcenter/scrambler 0.6.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/dist/core/api.d.ts.map +1 -1
- package/dist/core/api.js +10 -2
- package/dist/puzzles/cube2/solver/browser-loader.d.ts +4 -7
- package/dist/puzzles/cube2/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/browser-loader.js +42 -14
- package/dist/puzzles/cube2/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/generated-assets.js +8 -3
- package/dist/puzzles/cube2/solver/search.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/search.js +3 -3
- package/dist/puzzles/cube2/solver/tables.d.ts +6 -0
- package/dist/puzzles/cube2/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/tables.js +16 -2
- package/dist/puzzles/cube3/index.d.ts +1 -0
- package/dist/puzzles/cube3/index.d.ts.map +1 -1
- package/dist/puzzles/cube3/moves.d.ts.map +1 -1
- package/dist/puzzles/cube3/moves.js +104 -18
- package/dist/puzzles/cube3/scrambler.d.ts +16 -1
- package/dist/puzzles/cube3/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube3/scrambler.js +1 -1
- package/dist/puzzles/cube3/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/browser-loader.js +48 -22
- package/dist/puzzles/cube3/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/generated-assets.js +9 -3
- package/dist/puzzles/cube3/solver/index.d.ts +5 -3
- package/dist/puzzles/cube3/solver/index.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/index.js +2 -1
- package/dist/puzzles/cube3/solver/node-loader.js +9 -2
- package/dist/puzzles/cube3/solver/phase1.d.ts +37 -0
- package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase1.js +156 -15
- package/dist/puzzles/cube3/solver/phase2.d.ts +75 -0
- package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase2.js +279 -68
- package/dist/puzzles/cube3/solver/search-utils.d.ts +1 -1
- package/dist/puzzles/cube3/solver/search-utils.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/symmetry.d.ts +24 -0
- package/dist/puzzles/cube3/solver/symmetry.d.ts.map +1 -0
- package/dist/puzzles/cube3/solver/symmetry.js +74 -0
- package/dist/puzzles/cube3/solver/tables.d.ts +6 -1
- package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/tables.js +14 -12
- package/dist/puzzles/cube3/validation.d.ts.map +1 -1
- package/dist/puzzles/cube3/validation.js +15 -7
- package/dist/puzzles/cube4/browser-worker-pool.d.ts +82 -0
- package/dist/puzzles/cube4/browser-worker-pool.d.ts.map +1 -0
- package/dist/puzzles/cube4/browser-worker-pool.js +205 -0
- package/dist/puzzles/cube4/browser-worker.d.ts +2 -0
- package/dist/puzzles/cube4/browser-worker.d.ts.map +1 -0
- package/dist/puzzles/cube4/browser-worker.js +31 -0
- package/dist/puzzles/cube4/index.d.ts +10 -2
- package/dist/puzzles/cube4/index.d.ts.map +1 -1
- package/dist/puzzles/cube4/index.js +6 -2
- package/dist/puzzles/cube4/moves.d.ts +5 -0
- package/dist/puzzles/cube4/moves.d.ts.map +1 -1
- package/dist/puzzles/cube4/moves.js +212 -11
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts +29 -1
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/browser-loader.js +227 -32
- package/dist/puzzles/cube4/reduction/center2.d.ts +44 -3
- package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/center2.js +152 -10
- package/dist/puzzles/cube4/reduction/edge3.d.ts +4 -0
- package/dist/puzzles/cube4/reduction/edge3.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/edge3.js +23 -3
- package/dist/puzzles/cube4/reduction/finish3.d.ts +53 -0
- package/dist/puzzles/cube4/reduction/finish3.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/finish3.js +393 -0
- package/dist/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
- package/dist/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/hc-manifest.json +44 -0
- package/dist/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts +52 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.js +220 -0
- package/dist/puzzles/cube4/reduction/node-loader.d.ts +15 -1
- package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/node-loader.js +199 -25
- package/dist/puzzles/cube4/reduction/pair-transversal.d.ts +28 -0
- package/dist/puzzles/cube4/reduction/pair-transversal.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/pair-transversal.js +107 -0
- package/dist/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts +116 -7
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/phase3-search.js +458 -32
- package/dist/puzzles/cube4/reduction/rotation-fix.d.ts +28 -0
- package/dist/puzzles/cube4/reduction/rotation-fix.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/rotation-fix.js +212 -0
- package/dist/puzzles/cube4/reduction/solve.d.ts +172 -2
- package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/solve.js +1201 -180
- package/dist/puzzles/cube4/scramble-cache.d.ts +93 -0
- package/dist/puzzles/cube4/scramble-cache.d.ts.map +1 -0
- package/dist/puzzles/cube4/scramble-cache.js +150 -0
- package/dist/puzzles/cube4/scrambler.d.ts +201 -6
- package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube4/scrambler.js +414 -176
- package/dist/puzzles/cube4/state.d.ts.map +1 -1
- package/dist/puzzles/cube4/state.js +4 -1
- package/dist/puzzles/cube4/worker-pool.d.ts +139 -0
- package/dist/puzzles/cube4/worker-pool.d.ts.map +1 -0
- package/dist/puzzles/cube4/worker-pool.js +405 -0
- package/dist/puzzles/cube4/worker-protocol.d.ts +58 -0
- package/dist/puzzles/cube4/worker-protocol.d.ts.map +1 -0
- package/dist/puzzles/cube4/worker-protocol.js +58 -0
- package/dist/puzzles/cube4/worker.d.ts +26 -0
- package/dist/puzzles/cube4/worker.d.ts.map +1 -0
- package/dist/puzzles/cube4/worker.js +49 -0
- package/dist/puzzles/pyraminx/solver/browser-loader.d.ts +4 -8
- package/dist/puzzles/pyraminx/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/browser-loader.js +44 -15
- package/dist/puzzles/pyraminx/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/generated-assets.js +8 -3
- package/dist/puzzles/pyraminx/solver/search.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/search.js +3 -6
- package/dist/puzzles/pyraminx/solver/tables.d.ts +6 -0
- package/dist/puzzles/pyraminx/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/tables.js +17 -0
- package/dist/puzzles/shared/compressed-fetch.d.ts +37 -1
- package/dist/puzzles/shared/compressed-fetch.d.ts.map +1 -1
- package/dist/puzzles/shared/compressed-fetch.js +226 -31
- package/dist/puzzles/shared/load-once.d.ts +16 -0
- package/dist/puzzles/shared/load-once.d.ts.map +1 -0
- package/dist/puzzles/shared/load-once.js +11 -0
- package/dist/puzzles/skewb/solver/browser-loader.d.ts +4 -11
- package/dist/puzzles/skewb/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/browser-loader.js +44 -16
- package/dist/puzzles/skewb/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/generated-assets.js +8 -3
- package/dist/puzzles/skewb/solver/search.js +3 -3
- package/dist/puzzles/skewb/solver/tables.d.ts +6 -0
- package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/tables.js +17 -0
- package/dist/puzzles/sq1/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/sq1/solver/browser-loader.js +58 -26
- package/dist-cjs/core/api.js +10 -2
- package/dist-cjs/puzzles/cube2/solver/browser-loader.js +42 -14
- package/dist-cjs/puzzles/cube2/solver/generated-assets.js +8 -3
- package/dist-cjs/puzzles/cube2/solver/search.js +2 -2
- package/dist-cjs/puzzles/cube2/solver/tables.js +17 -3
- package/dist-cjs/puzzles/cube3/moves.js +104 -18
- package/dist-cjs/puzzles/cube3/scrambler.js +1 -1
- package/dist-cjs/puzzles/cube3/solver/browser-loader.js +48 -22
- package/dist-cjs/puzzles/cube3/solver/generated-assets.js +9 -3
- package/dist-cjs/puzzles/cube3/solver/index.js +8 -1
- package/dist-cjs/puzzles/cube3/solver/node-loader.js +9 -2
- package/dist-cjs/puzzles/cube3/solver/phase1.js +157 -15
- package/dist-cjs/puzzles/cube3/solver/phase2.js +276 -65
- package/dist-cjs/puzzles/cube3/solver/symmetry.js +80 -0
- package/dist-cjs/puzzles/cube3/solver/tables.js +14 -12
- package/dist-cjs/puzzles/cube3/validation.js +15 -7
- package/dist-cjs/puzzles/cube4/browser-worker-pool.js +211 -0
- package/dist-cjs/puzzles/cube4/index.js +23 -1
- package/dist-cjs/puzzles/cube4/moves.js +213 -11
- package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +227 -32
- package/dist-cjs/puzzles/cube4/reduction/center2.js +155 -10
- package/dist-cjs/puzzles/cube4/reduction/edge3.js +24 -3
- package/dist-cjs/puzzles/cube4/reduction/finish3.js +398 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-manifest.json +44 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
- package/dist-cjs/puzzles/cube4/reduction/hc-pdb.js +232 -0
- package/dist-cjs/puzzles/cube4/reduction/node-loader.js +200 -25
- package/dist-cjs/puzzles/cube4/reduction/pair-transversal.js +112 -0
- package/dist-cjs/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
- package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +460 -33
- package/dist-cjs/puzzles/cube4/reduction/rotation-fix.js +216 -0
- package/dist-cjs/puzzles/cube4/reduction/solve.js +1203 -172
- package/dist-cjs/puzzles/cube4/scramble-cache.js +156 -0
- package/dist-cjs/puzzles/cube4/scrambler.js +416 -174
- package/dist-cjs/puzzles/cube4/state.js +4 -1
- package/dist-cjs/puzzles/cube4/worker-pool.js +447 -0
- package/dist-cjs/puzzles/cube4/worker-protocol.js +62 -0
- package/dist-cjs/puzzles/pyraminx/solver/browser-loader.js +44 -15
- package/dist-cjs/puzzles/pyraminx/solver/generated-assets.js +8 -3
- package/dist-cjs/puzzles/pyraminx/solver/search.js +2 -5
- package/dist-cjs/puzzles/pyraminx/solver/tables.js +18 -1
- package/dist-cjs/puzzles/shared/compressed-fetch.js +229 -31
- package/dist-cjs/puzzles/shared/load-once.js +14 -0
- package/dist-cjs/puzzles/skewb/solver/browser-loader.js +44 -16
- package/dist-cjs/puzzles/skewb/solver/generated-assets.js +8 -3
- package/dist-cjs/puzzles/skewb/solver/search.js +2 -2
- package/dist-cjs/puzzles/skewb/solver/tables.js +18 -1
- package/dist-cjs/puzzles/sq1/solver/browser-loader.js +58 -26
- package/docs/development.md +19 -7
- package/package.json +17 -7
|
@@ -2,6 +2,7 @@ import { fetchTableBuffer } from "../../shared/compressed-fetch.js";
|
|
|
2
2
|
const TABLES_DIR = "puzzles/cube3/solver/generated";
|
|
3
3
|
const CACHE_NAME = "cubingcenter-scrambler-tables-v1";
|
|
4
4
|
let browserTables;
|
|
5
|
+
let browserTablesPromise;
|
|
5
6
|
function resolveBaseUrl(provided) {
|
|
6
7
|
if (provided) {
|
|
7
8
|
return provided.replace(/\/$/, "");
|
|
@@ -20,11 +21,28 @@ export function getCachedBrowserTables() {
|
|
|
20
21
|
}
|
|
21
22
|
export function clearCachedBrowserTables() {
|
|
22
23
|
browserTables = undefined;
|
|
24
|
+
browserTablesPromise = undefined;
|
|
23
25
|
}
|
|
24
26
|
export async function loadBrowserTables(baseUrl) {
|
|
25
27
|
if (browserTables) {
|
|
26
28
|
return browserTables;
|
|
27
29
|
}
|
|
30
|
+
if (browserTablesPromise) {
|
|
31
|
+
// One shared in-flight promise: concurrent calls trigger exactly one
|
|
32
|
+
// fetch pass (dedup).
|
|
33
|
+
return browserTablesPromise;
|
|
34
|
+
}
|
|
35
|
+
const promise = fetchTables(baseUrl);
|
|
36
|
+
browserTablesPromise = promise;
|
|
37
|
+
promise.then(() => { }, () => {
|
|
38
|
+
// A failed load must not poison the dedup slot: purge the rejected
|
|
39
|
+
// promise so a later call retries from scratch.
|
|
40
|
+
if (browserTablesPromise === promise)
|
|
41
|
+
browserTablesPromise = undefined;
|
|
42
|
+
});
|
|
43
|
+
return promise;
|
|
44
|
+
}
|
|
45
|
+
async function fetchTables(baseUrl) {
|
|
28
46
|
const resolvedBase = resolveBaseUrl(baseUrl);
|
|
29
47
|
const manifest = await fetchManifest(resolvedBase);
|
|
30
48
|
browserTables = {
|
|
@@ -58,19 +76,15 @@ async function fetchJointTables(baseUrl, entries) {
|
|
|
58
76
|
return undefined;
|
|
59
77
|
}
|
|
60
78
|
const fetchJoint = async (entry) => {
|
|
79
|
+
// Verified by fetchTableBuffer with the SAME checks as node-loader.ts:
|
|
80
|
+
// nibble-packed byte length + FNV-1a over the packed bytes.
|
|
61
81
|
const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
|
|
62
82
|
cacheName: CACHE_NAME,
|
|
63
|
-
compressed: entry.compressed !== false
|
|
83
|
+
compressed: entry.compressed !== false,
|
|
84
|
+
expectedBytes: Math.ceil((entry.rangeA * entry.rangeB) / 2),
|
|
85
|
+
expectedFnv1a: { value: entry.checksum, elementSize: 1 }
|
|
64
86
|
});
|
|
65
|
-
|
|
66
|
-
if (buffer.byteLength !== expectedBytes) {
|
|
67
|
-
throw new Error(`Invalid byte size for ${entry.file}.`);
|
|
68
|
-
}
|
|
69
|
-
const values = new Uint8Array(buffer);
|
|
70
|
-
if (checksum(values) !== entry.checksum) {
|
|
71
|
-
throw new Error(`Checksum mismatch for ${entry.file}.`);
|
|
72
|
-
}
|
|
73
|
-
return values;
|
|
87
|
+
return new Uint8Array(buffer);
|
|
74
88
|
};
|
|
75
89
|
return {
|
|
76
90
|
jointUdCo: await fetchJoint(entries.udCo),
|
|
@@ -90,15 +104,23 @@ async function fetchManifest(baseUrl) {
|
|
|
90
104
|
return manifest;
|
|
91
105
|
}
|
|
92
106
|
async function fetchMoveTable(baseUrl, entry) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
107
|
+
// Verified by fetchTableBuffer with the SAME checks as node-loader.ts:
|
|
108
|
+
// int32 wire length + FNV-1a over the decoded coordinate values (element
|
|
109
|
+
// size 4 reproduces node's hash over the uint16 values — same numbers).
|
|
110
|
+
const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
|
|
111
|
+
cacheName: CACHE_NAME,
|
|
112
|
+
compressed: entry.compressed !== false,
|
|
113
|
+
expectedBytes: entry.range * entry.moves.length * 4,
|
|
114
|
+
expectedFnv1a: { value: entry.checksum, elementSize: 4 }
|
|
115
|
+
});
|
|
116
|
+
const values = new Uint16Array(entry.range * entry.moves.length);
|
|
99
117
|
const view = new DataView(buffer);
|
|
100
118
|
for (let index = 0; index < values.length; index += 1) {
|
|
101
|
-
|
|
119
|
+
const value = view.getInt32(index * 4, true);
|
|
120
|
+
if (value < 0 || value > 0xffff) {
|
|
121
|
+
throw new Error(`Move table value out of range for ${entry.coordinate}: ${value}.`);
|
|
122
|
+
}
|
|
123
|
+
values[index] = value;
|
|
102
124
|
}
|
|
103
125
|
return {
|
|
104
126
|
coordinate: entry.coordinate,
|
|
@@ -110,11 +132,15 @@ async function fetchMoveTable(baseUrl, entry) {
|
|
|
110
132
|
};
|
|
111
133
|
}
|
|
112
134
|
async function fetchPruningTable(baseUrl, entry) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
135
|
+
// Verified by fetchTableBuffer with the SAME checks as node-loader.ts:
|
|
136
|
+
// nibble-packed byte length + FNV-1a over the packed bytes (this browser
|
|
137
|
+
// path did NOT compare the pruning checksum before — only the length).
|
|
138
|
+
const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
|
|
139
|
+
cacheName: CACHE_NAME,
|
|
140
|
+
compressed: entry.compressed !== false,
|
|
141
|
+
expectedBytes: Math.ceil(entry.range / 2),
|
|
142
|
+
expectedFnv1a: { value: entry.checksum, elementSize: 1 }
|
|
143
|
+
});
|
|
118
144
|
const values = new Uint8Array(buffer);
|
|
119
145
|
return {
|
|
120
146
|
coordinate: entry.coordinate,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated-assets.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/generated-assets.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"generated-assets.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/generated-assets.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAgBrB,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAcjF;AAWD,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAkBjF;AAED,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAkBjF"}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { getCachedBrowserTables } from "./browser-loader.js";
|
|
2
|
-
|
|
2
|
+
import { loadOnce } from "../../shared/load-once.js";
|
|
3
3
|
function isBrowser() {
|
|
4
4
|
return typeof self !== "undefined";
|
|
5
5
|
}
|
|
6
|
+
// One shared in-flight promise for the node fs load: mixed concurrent
|
|
7
|
+
// phase-1 + phase-2 loads (separate slots in tables.ts) converge on ONE
|
|
8
|
+
// `readGeneratedCube3SolverTables()` call. The previous `cached ??= await …`
|
|
9
|
+
// raced — two callers interleaving on the await both ran `loadNodeTables()`
|
|
10
|
+
// (two filesystem reads, two table sets, last assignment wins). On
|
|
11
|
+
// rejection the slot is purged so the next call can retry.
|
|
12
|
+
const nodeTablesSlot = { promise: undefined };
|
|
6
13
|
export async function loadGeneratedCube3SolverTables() {
|
|
7
14
|
if (isBrowser()) {
|
|
8
15
|
const browserTables = getCachedBrowserTables();
|
|
@@ -13,8 +20,7 @@ export async function loadGeneratedCube3SolverTables() {
|
|
|
13
20
|
"In browser environments, call `initializeScrambler(baseUrl)` " +
|
|
14
21
|
"before generating scrambles.");
|
|
15
22
|
}
|
|
16
|
-
|
|
17
|
-
return cachedGeneratedTables;
|
|
23
|
+
return loadOnce(nodeTablesSlot, loadNodeTables);
|
|
18
24
|
}
|
|
19
25
|
async function loadNodeTables() {
|
|
20
26
|
const loader = await import(
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export { applyCube3Phase2Moves, encodeCube3Phase2Coordinates, isCube3Phase2Solved, solveCube3Phase2, solveCube3State, Cube3Phase2SearchError } from "./phase2.js";
|
|
2
|
-
export { applyCube3Phase1Moves, encodeCube3Phase1Coordinates, isCube3Phase1Solved, solveCube3Phase1, Cube3Phase1SearchError } from "./phase1.js";
|
|
2
|
+
export { applyCube3Phase1Moves, collectCube3Phase1Solutions, encodeCube3Phase1Coordinates, isCube3Phase1Solved, solveCube3Phase1, Cube3Phase1SearchError } from "./phase1.js";
|
|
3
|
+
export { CUBE3_ORIENTATIONS_24, CUBE3_ROTATION_INVERSE, invertCube3RotationSequence, rotateCube3Algorithm, rotateCube3AlgorithmBySequence } from "./symmetry.js";
|
|
4
|
+
export type { Cube3Rotation, Cube3RotationSequence } from "./symmetry.js";
|
|
3
5
|
export { CUBE3_COORDINATE_RANGES, createRepresentativeStateForCoordinate, decodeCornerOrientation, decodeEdgeOrientation, decodePermutation, decodeSlicePermutation, decodeUdEdgePermutation, decodeUdSlice, encodeCube3Coordinate, encodeCornerOrientation, encodeCube3Coordinates, encodeEdgeOrientation, encodePermutation, encodeSlicePermutation, encodeUdEdgePermutation, encodeUdSlice, moveCoordinate } from "./coordinates.js";
|
|
4
6
|
export { CUBE3_SOLVER_TABLE_METADATA } from "./table-metadata.js";
|
|
5
7
|
export { CUBE3_PHASE1_MOVES, CUBE3_PHASE2_MOVES, checksum, generateCube3Phase1Tables, generateCube3Phase2Tables, generateCube3MoveTable, generateCube3PruningTable, generateCube3SolverTables, loadCube3Phase1Tables, loadCube3Phase2Tables, loadCube3SolverTables, lookupCube3MoveTable } from "./tables.js";
|
|
6
|
-
export type { Cube3Phase2Coordinates, Cube3Phase2Options, Cube3Phase2Result, Cube3SolveOptions, Cube3SolveResult } from "./phase2.js";
|
|
7
|
-
export type { Cube3Phase1Coordinates, Cube3Phase1Options, Cube3Phase1Result } from "./phase1.js";
|
|
8
|
+
export type { Cube3Phase2Coordinates, Cube3Phase2Options, Cube3Phase2Result, Cube3SolveOptions, Cube3SolveQuality, Cube3SolveResult } from "./phase2.js";
|
|
9
|
+
export type { Cube3Phase1CollectOptions, Cube3Phase1Coordinates, Cube3Phase1Options, Cube3Phase1Result } from "./phase1.js";
|
|
8
10
|
export type { Cube3CoordinateState } from "./coordinates.js";
|
|
9
11
|
export type { Cube3CoordinateName, Cube3Phase1Tables, Cube3Phase2Tables, Cube3MoveTable, Cube3PruningTable, Cube3SolverTables } from "./tables.js";
|
|
10
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,4BAA4B,EAC5B,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,qBAAqB,EACrB,4BAA4B,EAC5B,mBAAmB,EACnB,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,uBAAuB,EACvB,sCAAsC,EACtC,uBAAuB,EACvB,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,aAAa,EACb,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,aAAa,EACb,cAAc,EACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,2BAA2B,EAC5B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,QAAQ,EACR,yBAAyB,EACzB,yBAAyB,EACzB,sBAAsB,EACtB,yBAAyB,EACzB,yBAAyB,EACzB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,oBAAoB,EACrB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,4BAA4B,EAC5B,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,qBAAqB,EACrB,2BAA2B,EAC3B,4BAA4B,EAC5B,mBAAmB,EACnB,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,2BAA2B,EAC3B,oBAAoB,EACpB,8BAA8B,EAC/B,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EACL,uBAAuB,EACvB,sCAAsC,EACtC,uBAAuB,EACvB,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,aAAa,EACb,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,aAAa,EACb,cAAc,EACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,2BAA2B,EAC5B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,QAAQ,EACR,yBAAyB,EACzB,yBAAyB,EACzB,sBAAsB,EACtB,yBAAyB,EACzB,yBAAyB,EACzB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,yBAAyB,EACzB,sBAAsB,EACtB,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,oBAAoB,EACrB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,aAAa,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { applyCube3Phase2Moves, encodeCube3Phase2Coordinates, isCube3Phase2Solved, solveCube3Phase2, solveCube3State, Cube3Phase2SearchError } from "./phase2.js";
|
|
2
|
-
export { applyCube3Phase1Moves, encodeCube3Phase1Coordinates, isCube3Phase1Solved, solveCube3Phase1, Cube3Phase1SearchError } from "./phase1.js";
|
|
2
|
+
export { applyCube3Phase1Moves, collectCube3Phase1Solutions, encodeCube3Phase1Coordinates, isCube3Phase1Solved, solveCube3Phase1, Cube3Phase1SearchError } from "./phase1.js";
|
|
3
|
+
export { CUBE3_ORIENTATIONS_24, CUBE3_ROTATION_INVERSE, invertCube3RotationSequence, rotateCube3Algorithm, rotateCube3AlgorithmBySequence } from "./symmetry.js";
|
|
3
4
|
export { CUBE3_COORDINATE_RANGES, createRepresentativeStateForCoordinate, decodeCornerOrientation, decodeEdgeOrientation, decodePermutation, decodeSlicePermutation, decodeUdEdgePermutation, decodeUdSlice, encodeCube3Coordinate, encodeCornerOrientation, encodeCube3Coordinates, encodeEdgeOrientation, encodePermutation, encodeSlicePermutation, encodeUdEdgePermutation, encodeUdSlice, moveCoordinate } from "./coordinates.js";
|
|
4
5
|
export { CUBE3_SOLVER_TABLE_METADATA } from "./table-metadata.js";
|
|
5
6
|
export { CUBE3_PHASE1_MOVES, CUBE3_PHASE2_MOVES, checksum, generateCube3Phase1Tables, generateCube3Phase2Tables, generateCube3MoveTable, generateCube3PruningTable, generateCube3SolverTables, loadCube3Phase1Tables, loadCube3Phase2Tables, loadCube3SolverTables, lookupCube3MoveTable } from "./tables.js";
|
|
@@ -65,9 +65,16 @@ function readMoveTable(assetDirectory, entry) {
|
|
|
65
65
|
if (buffer.length !== expectedBytes) {
|
|
66
66
|
throw new Error(`Invalid byte size for ${entry.coordinate} move table.`);
|
|
67
67
|
}
|
|
68
|
-
|
|
68
|
+
// Every coordinate fits in 16 bits (max range 40320): store half the
|
|
69
|
+
// bytes the int32 wire format uses. Checksums are order-sensitive FNV
|
|
70
|
+
// over the same numeric sequence, so they still match the manifest.
|
|
71
|
+
const values = new Uint16Array(entry.range * entry.moves.length);
|
|
69
72
|
for (let index = 0; index < values.length; index += 1) {
|
|
70
|
-
|
|
73
|
+
const value = buffer.readInt32LE(index * 4);
|
|
74
|
+
if (value < 0 || value > 0xffff) {
|
|
75
|
+
throw new Error(`Move table value out of range for ${entry.coordinate}: ${value}.`);
|
|
76
|
+
}
|
|
77
|
+
values[index] = value;
|
|
71
78
|
}
|
|
72
79
|
const table = {
|
|
73
80
|
coordinate: entry.coordinate,
|
|
@@ -7,7 +7,20 @@ export interface Cube3Phase1Coordinates {
|
|
|
7
7
|
}
|
|
8
8
|
export interface Cube3Phase1Options {
|
|
9
9
|
maxDepth?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Legacy wall-clock deadline (checked every 1024 nodes). The stopping
|
|
12
|
+
* point — and therefore the result — depends on machine speed and CPU
|
|
13
|
+
* load. Prefer `maxNodes` when the caller needs a reproducible outcome.
|
|
14
|
+
*/
|
|
10
15
|
timeoutMs?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Deterministic work cap: stop after visiting this many nodes (checked on
|
|
18
|
+
* every node; overshoot ≤ 1). The result then depends only on
|
|
19
|
+
* (state, tables, budgets) — never on the clock — so the same input
|
|
20
|
+
* yields the same solution under any CPU load. Composes with `timeoutMs`
|
|
21
|
+
* when both are set (whichever trips first stops the search).
|
|
22
|
+
*/
|
|
23
|
+
maxNodes?: number;
|
|
11
24
|
tables?: Cube3Phase1Tables;
|
|
12
25
|
/** Phase-1 joint tables (ud x co / ud x eo), experimental. */
|
|
13
26
|
jointTables?: Cube3JointTables | undefined;
|
|
@@ -27,4 +40,28 @@ export declare function solveCube3Phase1(state: Cube3State, options?: Cube3Phase
|
|
|
27
40
|
export declare function encodeCube3Phase1Coordinates(state: Cube3State): Cube3Phase1Coordinates;
|
|
28
41
|
export declare function isCube3Phase1Solved(input: Cube3State | Cube3Phase1Coordinates, tables: Cube3Phase1Tables): boolean;
|
|
29
42
|
export declare function applyCube3Phase1Moves(state: Cube3State, moves: readonly Cube3Move[]): Cube3State;
|
|
43
|
+
export interface Cube3Phase1CollectOptions {
|
|
44
|
+
tables?: Cube3Phase1Tables;
|
|
45
|
+
/** Phase-1 joint tables (ud x co / ud x eo), experimental. */
|
|
46
|
+
jointTables?: Cube3JointTables | undefined;
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Deterministic node budget for this enumeration (see
|
|
50
|
+
* `Cube3Phase1Options.maxNodes`). Preferred over `timeoutMs` when the
|
|
51
|
+
* collected set must not depend on CPU load.
|
|
52
|
+
*/
|
|
53
|
+
maxNodes?: number;
|
|
54
|
+
/** Stop after collecting this many solutions. Defaults to 32. */
|
|
55
|
+
limit?: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Enumerates up to `limit` phase-1 solutions of exactly `depth` moves for
|
|
59
|
+
* the given state, most promising first (same ordered DFS as the search).
|
|
60
|
+
* Used to try several G1 landing states and keep the best phase-1+phase-2
|
|
61
|
+
* total — a slightly longer phase 1 often collapses phase 2.
|
|
62
|
+
*/
|
|
63
|
+
export declare function collectCube3Phase1Solutions(state: Cube3State, depth: number, options?: Cube3Phase1CollectOptions): Promise<{
|
|
64
|
+
solutions: Cube3Move[][];
|
|
65
|
+
visitedNodes: number;
|
|
66
|
+
}>;
|
|
30
67
|
//# sourceMappingURL=phase1.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phase1.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/phase1.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAMzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAOvE,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,8DAA8D;IAC9D,WAAW,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;CAC5C;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,sBAAsB,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;gBAElB,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;CAKlD;
|
|
1
|
+
{"version":3,"file":"phase1.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/phase1.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAMzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAOvE,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,8DAA8D;IAC9D,WAAW,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;CAC5C;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,sBAAsB,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;gBAElB,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;CAKlD;AA4CD,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAwF5B;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,UAAU,GAAG,sBAAsB,CAMtF;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,UAAU,GAAG,sBAAsB,EAC1C,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAQT;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,SAAS,SAAS,EAAE,GAC1B,UAAU,CAEZ;AAyFD,MAAM,WAAW,yBAAyB;IACxC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,8DAA8D;IAC9D,WAAW,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC;IAAE,SAAS,EAAE,SAAS,EAAE,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,CA2B7D"}
|
|
@@ -28,14 +28,7 @@ export async function solveCube3Phase1(state, options = {}) {
|
|
|
28
28
|
const maxDepth = options.maxDepth ?? 12;
|
|
29
29
|
const start = encodeCube3Phase1Coordinates(state);
|
|
30
30
|
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
31
|
-
const context =
|
|
32
|
-
tables,
|
|
33
|
-
jointTables,
|
|
34
|
-
maxDepth,
|
|
35
|
-
deadline,
|
|
36
|
-
visitedNodes: 0,
|
|
37
|
-
timedOut: false
|
|
38
|
-
};
|
|
31
|
+
const context = createPhase1SearchContext(tables, jointTables, maxDepth, deadline, options.maxNodes);
|
|
39
32
|
if (await isCube3Phase1Solved(start, tables)) {
|
|
40
33
|
return {
|
|
41
34
|
moves: [],
|
|
@@ -77,6 +70,9 @@ export async function solveCube3Phase1(state, options = {}) {
|
|
|
77
70
|
visitedNodes: context.visitedNodes
|
|
78
71
|
};
|
|
79
72
|
}
|
|
73
|
+
if (context.nodesExceeded) {
|
|
74
|
+
throw new Cube3Phase1SearchError(`3x3 Phase 1 node budget exceeded (${options.maxNodes} nodes).`, context.visitedNodes);
|
|
75
|
+
}
|
|
80
76
|
if (context.timedOut) {
|
|
81
77
|
throw new Cube3Phase1SearchError(`3x3 Phase 1 search timed out after ${options.timeoutMs}ms.`, context.visitedNodes);
|
|
82
78
|
}
|
|
@@ -107,8 +103,17 @@ const PHASE1_MOVE_FACES = CUBE3_PHASE1_MOVES.map((move) => {
|
|
|
107
103
|
});
|
|
108
104
|
function searchPhase1(cornerOrientation, edgeOrientation, udSlice, previousFace, previousPreviousFace, remainingDepth, path, context) {
|
|
109
105
|
context.visitedNodes += 1;
|
|
110
|
-
if (context.
|
|
106
|
+
if (context.nodeCap !== undefined &&
|
|
107
|
+
context.visitedNodes > context.nodeCap) {
|
|
108
|
+
context.nodesExceeded = true;
|
|
109
|
+
}
|
|
110
|
+
if (!context.nodesExceeded &&
|
|
111
|
+
context.deadline !== undefined &&
|
|
112
|
+
(context.visitedNodes & 1023) === 0 &&
|
|
113
|
+
performance.now() > context.deadline) {
|
|
111
114
|
context.timedOut = true;
|
|
115
|
+
}
|
|
116
|
+
if (context.timedOut || context.nodesExceeded) {
|
|
112
117
|
return false;
|
|
113
118
|
}
|
|
114
119
|
if (remainingDepth === 0) {
|
|
@@ -116,6 +121,68 @@ function searchPhase1(cornerOrientation, edgeOrientation, udSlice, previousFace,
|
|
|
116
121
|
edgeOrientation === 0 &&
|
|
117
122
|
udSlice === context.tables.moveTables.udSlice.solvedCoordinate;
|
|
118
123
|
}
|
|
124
|
+
const moveCount = context.tables.phase1Moves.length;
|
|
125
|
+
const childCount = collectPhase1Children(cornerOrientation, edgeOrientation, udSlice, previousFace, previousPreviousFace, remainingDepth, context);
|
|
126
|
+
const slot = remainingDepth * moveCount;
|
|
127
|
+
const scratchMove = context.scratchMove;
|
|
128
|
+
const scratchCo = context.scratchCo;
|
|
129
|
+
const scratchEo = context.scratchEo;
|
|
130
|
+
const scratchUd = context.scratchUd;
|
|
131
|
+
for (let i = 0; i < childCount; i += 1) {
|
|
132
|
+
const moveIndex = scratchMove[slot + i];
|
|
133
|
+
const face = PHASE1_MOVE_FACES[moveIndex];
|
|
134
|
+
path.push(context.tables.phase1Moves[moveIndex]);
|
|
135
|
+
if (searchPhase1(scratchCo[slot + i], scratchEo[slot + i], scratchUd[slot + i], face, previousFace, remainingDepth - 1, path, context)) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
path.pop();
|
|
139
|
+
if (context.timedOut || context.nodesExceeded) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Enumerates up to `limit` phase-1 solutions of exactly `depth` moves for
|
|
147
|
+
* the given state, most promising first (same ordered DFS as the search).
|
|
148
|
+
* Used to try several G1 landing states and keep the best phase-1+phase-2
|
|
149
|
+
* total — a slightly longer phase 1 often collapses phase 2.
|
|
150
|
+
*/
|
|
151
|
+
export async function collectCube3Phase1Solutions(state, depth, options = {}) {
|
|
152
|
+
const tables = options.tables ?? await loadCube3Phase1Tables();
|
|
153
|
+
const jointTables = options.jointTables ?? tables.jointTables;
|
|
154
|
+
const limit = options.limit ?? 32;
|
|
155
|
+
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
156
|
+
const context = createPhase1SearchContext(tables, jointTables, depth, deadline, options.maxNodes);
|
|
157
|
+
const start = encodeCube3Phase1Coordinates(state);
|
|
158
|
+
const solutions = [];
|
|
159
|
+
searchPhase1Collect(start.cornerOrientation, start.edgeOrientation, start.udSlice, -1, -1, depth, [], solutions, limit, context);
|
|
160
|
+
return { solutions, visitedNodes: context.visitedNodes };
|
|
161
|
+
}
|
|
162
|
+
function createPhase1SearchContext(tables, jointTables, maxDepth, deadline, nodeCap) {
|
|
163
|
+
const scratchLevels = maxDepth + 1;
|
|
164
|
+
const scratchSize = scratchLevels * CUBE3_PHASE1_MOVES.length;
|
|
165
|
+
return {
|
|
166
|
+
tables,
|
|
167
|
+
jointTables,
|
|
168
|
+
maxDepth,
|
|
169
|
+
deadline,
|
|
170
|
+
nodeCap,
|
|
171
|
+
visitedNodes: 0,
|
|
172
|
+
timedOut: false,
|
|
173
|
+
nodesExceeded: false,
|
|
174
|
+
scratchMove: new Int32Array(scratchSize),
|
|
175
|
+
scratchBound: new Int32Array(scratchSize),
|
|
176
|
+
scratchCo: new Int32Array(scratchSize),
|
|
177
|
+
scratchEo: new Int32Array(scratchSize),
|
|
178
|
+
scratchUd: new Int32Array(scratchSize)
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Fills the scratch slot for `remainingDepth` with the surviving children
|
|
183
|
+
* in ascending lower-bound order (stable). Returns the child count.
|
|
184
|
+
*/
|
|
185
|
+
function collectPhase1Children(cornerOrientation, edgeOrientation, udSlice, previousFace, previousPreviousFace, remainingDepth, context) {
|
|
119
186
|
const moveCount = context.tables.phase1Moves.length;
|
|
120
187
|
const validMoves = PHASE1_VALID_MOVES[previousFace + 1][previousPreviousFace + 1];
|
|
121
188
|
const coTable = context.tables.moveTables.cornerOrientation.values;
|
|
@@ -123,9 +190,17 @@ function searchPhase1(cornerOrientation, edgeOrientation, udSlice, previousFace,
|
|
|
123
190
|
const udTable = context.tables.moveTables.udSlice.values;
|
|
124
191
|
const coeoTable = context.tables.pruningTables.cornerOrientationEdgeOrientation.values;
|
|
125
192
|
const udPruningTable = context.tables.pruningTables.udSlice.values;
|
|
193
|
+
const jointTables = context.jointTables;
|
|
126
194
|
const coOffset = cornerOrientation * moveCount;
|
|
127
195
|
const eoOffset = edgeOrientation * moveCount;
|
|
128
196
|
const udOffset = udSlice * moveCount;
|
|
197
|
+
const slot = remainingDepth * moveCount;
|
|
198
|
+
const scratchMove = context.scratchMove;
|
|
199
|
+
const scratchBound = context.scratchBound;
|
|
200
|
+
const scratchCo = context.scratchCo;
|
|
201
|
+
const scratchEo = context.scratchEo;
|
|
202
|
+
const scratchUd = context.scratchUd;
|
|
203
|
+
let childCount = 0;
|
|
129
204
|
for (let i = 0; i < validMoves.length; i += 1) {
|
|
130
205
|
const moveIndex = validMoves[i];
|
|
131
206
|
const nextCornerOrientation = coTable[coOffset + moveIndex];
|
|
@@ -138,7 +213,6 @@ function searchPhase1(cornerOrientation, edgeOrientation, udSlice, previousFace,
|
|
|
138
213
|
if (nextLowerBound > remainingDepth - 1) {
|
|
139
214
|
continue;
|
|
140
215
|
}
|
|
141
|
-
const jointTables = context.jointTables;
|
|
142
216
|
if (jointTables) {
|
|
143
217
|
const udCoIndex = nextUdSlice * 2187 + nextCornerOrientation;
|
|
144
218
|
const jc = (jointTables.jointUdCo[udCoIndex >> 1] >> ((udCoIndex & 1) << 2)) & 0x0f;
|
|
@@ -154,14 +228,81 @@ function searchPhase1(cornerOrientation, edgeOrientation, udSlice, previousFace,
|
|
|
154
228
|
continue;
|
|
155
229
|
}
|
|
156
230
|
}
|
|
231
|
+
scratchMove[slot + childCount] = moveIndex;
|
|
232
|
+
scratchBound[slot + childCount] = nextLowerBound;
|
|
233
|
+
scratchCo[slot + childCount] = nextCornerOrientation;
|
|
234
|
+
scratchEo[slot + childCount] = nextEdgeOrientation;
|
|
235
|
+
scratchUd[slot + childCount] = nextUdSlice;
|
|
236
|
+
childCount += 1;
|
|
237
|
+
}
|
|
238
|
+
// Stable insertion sort by bound (n <= 18; ties keep reference order).
|
|
239
|
+
for (let i = 1; i < childCount; i += 1) {
|
|
240
|
+
const move = scratchMove[slot + i];
|
|
241
|
+
const bound = scratchBound[slot + i];
|
|
242
|
+
const co = scratchCo[slot + i];
|
|
243
|
+
const eo = scratchEo[slot + i];
|
|
244
|
+
const ud = scratchUd[slot + i];
|
|
245
|
+
let j = i - 1;
|
|
246
|
+
while (j >= 0 && scratchBound[slot + j] > bound) {
|
|
247
|
+
scratchMove[slot + j + 1] = scratchMove[slot + j];
|
|
248
|
+
scratchBound[slot + j + 1] = scratchBound[slot + j];
|
|
249
|
+
scratchCo[slot + j + 1] = scratchCo[slot + j];
|
|
250
|
+
scratchEo[slot + j + 1] = scratchEo[slot + j];
|
|
251
|
+
scratchUd[slot + j + 1] = scratchUd[slot + j];
|
|
252
|
+
j -= 1;
|
|
253
|
+
}
|
|
254
|
+
scratchMove[slot + j + 1] = move;
|
|
255
|
+
scratchBound[slot + j + 1] = bound;
|
|
256
|
+
scratchCo[slot + j + 1] = co;
|
|
257
|
+
scratchEo[slot + j + 1] = eo;
|
|
258
|
+
scratchUd[slot + j + 1] = ud;
|
|
259
|
+
}
|
|
260
|
+
return childCount;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Ordered exact-depth DFS that collects solutions instead of stopping at
|
|
264
|
+
* the first one. Returns true when collection should stop (limit reached
|
|
265
|
+
* or timed out).
|
|
266
|
+
*/
|
|
267
|
+
function searchPhase1Collect(cornerOrientation, edgeOrientation, udSlice, previousFace, previousPreviousFace, remainingDepth, path, solutions, limit, context) {
|
|
268
|
+
context.visitedNodes += 1;
|
|
269
|
+
if (context.nodeCap !== undefined &&
|
|
270
|
+
context.visitedNodes > context.nodeCap) {
|
|
271
|
+
context.nodesExceeded = true;
|
|
272
|
+
}
|
|
273
|
+
if (!context.nodesExceeded &&
|
|
274
|
+
context.deadline !== undefined &&
|
|
275
|
+
(context.visitedNodes & 1023) === 0 &&
|
|
276
|
+
performance.now() > context.deadline) {
|
|
277
|
+
context.timedOut = true;
|
|
278
|
+
}
|
|
279
|
+
if (context.timedOut || context.nodesExceeded) {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
if (remainingDepth === 0) {
|
|
283
|
+
if (cornerOrientation === 0 &&
|
|
284
|
+
edgeOrientation === 0 &&
|
|
285
|
+
udSlice === context.tables.moveTables.udSlice.solvedCoordinate) {
|
|
286
|
+
solutions.push([...path]);
|
|
287
|
+
return solutions.length >= limit;
|
|
288
|
+
}
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
const moveCount = context.tables.phase1Moves.length;
|
|
292
|
+
const childCount = collectPhase1Children(cornerOrientation, edgeOrientation, udSlice, previousFace, previousPreviousFace, remainingDepth, context);
|
|
293
|
+
const slot = remainingDepth * moveCount;
|
|
294
|
+
const scratchMove = context.scratchMove;
|
|
295
|
+
const scratchCo = context.scratchCo;
|
|
296
|
+
const scratchEo = context.scratchEo;
|
|
297
|
+
const scratchUd = context.scratchUd;
|
|
298
|
+
for (let i = 0; i < childCount; i += 1) {
|
|
299
|
+
const moveIndex = scratchMove[slot + i];
|
|
157
300
|
const face = PHASE1_MOVE_FACES[moveIndex];
|
|
158
301
|
path.push(context.tables.phase1Moves[moveIndex]);
|
|
159
|
-
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
302
|
+
const stop = searchPhase1Collect(scratchCo[slot + i], scratchEo[slot + i], scratchUd[slot + i], face, previousFace, remainingDepth - 1, path, solutions, limit, context);
|
|
162
303
|
path.pop();
|
|
163
|
-
if (
|
|
164
|
-
return
|
|
304
|
+
if (stop) {
|
|
305
|
+
return true;
|
|
165
306
|
}
|
|
166
307
|
}
|
|
167
308
|
return false;
|
|
@@ -9,10 +9,29 @@ export interface Cube3Phase2Coordinates {
|
|
|
9
9
|
}
|
|
10
10
|
export interface Cube3Phase2Options {
|
|
11
11
|
maxDepth?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Legacy wall-clock deadline (checked every 1024 nodes): the stopping
|
|
14
|
+
* point — and the result — depends on machine speed and CPU load.
|
|
15
|
+
* Prefer `maxNodes` for reproducible outcomes.
|
|
16
|
+
*/
|
|
12
17
|
timeoutMs?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Deterministic work cap: stop after visiting this many nodes (checked
|
|
20
|
+
* on every node; overshoot ≤ 1). Same input + same cap ⇒ same result
|
|
21
|
+
* regardless of load/clock. Composes with `timeoutMs` when both are set.
|
|
22
|
+
*/
|
|
23
|
+
maxNodes?: number;
|
|
13
24
|
tables?: Cube3Phase2Tables;
|
|
14
25
|
/** Combined (cp x uep) pair-distance pruning tables (experimental). */
|
|
15
26
|
pairTables?: Cube3PairTables | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Skip the pair tables entirely (no lazy load). All pair-bound uses are
|
|
29
|
+
* already guarded, so the search runs without pair pruning. Used by the
|
|
30
|
+
* 4x4 finish, where pair pruning was measured to change nothing (identical
|
|
31
|
+
* lengths on a 44-state corpus, zero extra retries) while costing a 54MB
|
|
32
|
+
* table load.
|
|
33
|
+
*/
|
|
34
|
+
skipPairTables?: boolean;
|
|
16
35
|
previousFace?: number;
|
|
17
36
|
previousPreviousFace?: number;
|
|
18
37
|
}
|
|
@@ -23,9 +42,65 @@ export interface Cube3Phase2Result {
|
|
|
23
42
|
coordinates: Cube3Phase2Coordinates;
|
|
24
43
|
visitedNodes: number;
|
|
25
44
|
}
|
|
45
|
+
export type Cube3SolveQuality = "fast" | "short" | "best";
|
|
26
46
|
export interface Cube3SolveOptions {
|
|
27
47
|
phase1?: Cube3Phase1Options;
|
|
28
48
|
phase2?: Cube3Phase2Options;
|
|
49
|
+
targetLength?: number;
|
|
50
|
+
timeoutMs?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Quality tier (defaults to `"short"`):
|
|
53
|
+
* - `"fast"`: legacy min-P1 + min-P2 (~14 ms, ~23.1 moves, browser tables).
|
|
54
|
+
* - `"short"`: best of several G1 landings (~36 ms, ~21.9 moves).
|
|
55
|
+
* - `"best"`: short x 3 orientations (~116 ms, ~20.8 moves, max 22).
|
|
56
|
+
* Explicit `phase1ExtraDepth` / `phase1SolutionLimit` / `orientations`
|
|
57
|
+
* always override the tier preset.
|
|
58
|
+
*/
|
|
59
|
+
quality?: Cube3SolveQuality;
|
|
60
|
+
/**
|
|
61
|
+
* Extra phase-1 depths to explore beyond the minimal one, keeping the
|
|
62
|
+
* best phase-1+phase-2 total. A slightly longer phase 1 often collapses
|
|
63
|
+
* phase 2, shortening the total (and sometimes the search itself).
|
|
64
|
+
* Defaults to 0 (legacy min-P1 + min-P2 behavior). The `targetLength`
|
|
65
|
+
* path always explores up to 4 extra depths.
|
|
66
|
+
*/
|
|
67
|
+
phase1ExtraDepth?: number;
|
|
68
|
+
/**
|
|
69
|
+
* Max phase-1 solutions enumerated per extra depth (most promising
|
|
70
|
+
* first). Defaults to 32.
|
|
71
|
+
*/
|
|
72
|
+
phase1SolutionLimit?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Per-candidate phase-2 budget (ms) inside the improvement loop.
|
|
75
|
+
* Defaults to `phase2.timeoutMs` when set, else 25. The initial solve
|
|
76
|
+
* always uses `phase2.timeoutMs` untouched, so this never starves it.
|
|
77
|
+
* Legacy wall-clock; superseded by `improvePhase2MaxNodes`.
|
|
78
|
+
*/
|
|
79
|
+
improvePhase2TimeoutMs?: number;
|
|
80
|
+
/**
|
|
81
|
+
* Deterministic node budget for the improvement loop (phase-1 collection
|
|
82
|
+
* + candidate phase-2 searches), counted as visited search nodes across
|
|
83
|
+
* the whole loop of ONE solve call. When set, the wall deadline
|
|
84
|
+
* (`timeoutMs`, default 800 ms) is NOT armed: the returned solution
|
|
85
|
+
* depends only on (state, tables, budgets) and is reproducible under any
|
|
86
|
+
* CPU load — this is the mode the 4x4 finish uses.
|
|
87
|
+
*/
|
|
88
|
+
improveMaxNodes?: number;
|
|
89
|
+
/**
|
|
90
|
+
* Deterministic per-candidate phase-2 node cap inside the improvement
|
|
91
|
+
* loop (counterpart of `improvePhase2TimeoutMs`). Defaults to
|
|
92
|
+
* `phase2.maxNodes` when set, else the shared `improveMaxNodes`
|
|
93
|
+
* remainder. Ignored unless `improveMaxNodes` is set.
|
|
94
|
+
*/
|
|
95
|
+
improvePhase2MaxNodes?: number;
|
|
96
|
+
/**
|
|
97
|
+
* Solve rotated copies of the state and keep the shortest solution
|
|
98
|
+
* (1 = UD only, 3 = UD/x/z triple, up to 24 for the full rotation
|
|
99
|
+
* group). Each extra orientation buys roughly 0.3-0.5 moves for ~1x
|
|
100
|
+
* single-solve time. Composes with the other options (each orientation
|
|
101
|
+
* is solved with the same settings). Defaults to 1.
|
|
102
|
+
*/
|
|
103
|
+
orientations?: number;
|
|
29
104
|
}
|
|
30
105
|
export interface Cube3SolveResult {
|
|
31
106
|
phase1: Cube3Phase1Result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phase2.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/phase2.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"phase2.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/phase2.ts"],"names":[],"mappings":"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"}
|