@cubingcenter/scrambler 0.7.0 → 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 +15 -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/cube3/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/browser-loader.js +42 -20
- 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/phase1.d.ts +19 -0
- package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase1.js +29 -6
- package/dist/puzzles/cube3/solver/phase2.d.ts +28 -0
- package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase2.js +56 -15
- package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/tables.js +14 -12
- 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 +7 -0
- package/dist/puzzles/cube4/index.d.ts.map +1 -1
- package/dist/puzzles/cube4/index.js +4 -0
- package/dist/puzzles/cube4/moves.d.ts.map +1 -1
- package/dist/puzzles/cube4/moves.js +122 -30
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts +26 -1
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/browser-loader.js +212 -48
- package/dist/puzzles/cube4/reduction/center2.d.ts +24 -0
- package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/center2.js +99 -1
- 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/hc-manifest.json +6 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts +5 -3
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/hc-pdb.js +30 -12
- package/dist/puzzles/cube4/reduction/node-loader.d.ts +10 -1
- package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/node-loader.js +170 -36
- 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/phase3-search.d.ts +33 -30
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/phase3-search.js +211 -104
- 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 +72 -13
- package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/solve.js +374 -92
- 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 +112 -18
- package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube4/scrambler.js +271 -83
- 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/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/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/cube3/solver/browser-loader.js +42 -20
- package/dist-cjs/puzzles/cube3/solver/generated-assets.js +9 -3
- package/dist-cjs/puzzles/cube3/solver/phase1.js +29 -6
- package/dist-cjs/puzzles/cube3/solver/phase2.js +56 -15
- package/dist-cjs/puzzles/cube3/solver/tables.js +14 -12
- package/dist-cjs/puzzles/cube4/browser-worker-pool.js +211 -0
- package/dist-cjs/puzzles/cube4/index.js +21 -1
- package/dist-cjs/puzzles/cube4/moves.js +122 -30
- package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +212 -48
- package/dist-cjs/puzzles/cube4/reduction/center2.js +102 -2
- package/dist-cjs/puzzles/cube4/reduction/finish3.js +398 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-manifest.json +6 -0
- package/dist-cjs/puzzles/cube4/reduction/hc-pdb.js +31 -12
- package/dist-cjs/puzzles/cube4/reduction/node-loader.js +171 -36
- package/dist-cjs/puzzles/cube4/reduction/pair-transversal.js +112 -0
- package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +211 -104
- package/dist-cjs/puzzles/cube4/reduction/rotation-fix.js +216 -0
- package/dist-cjs/puzzles/cube4/reduction/solve.js +374 -88
- package/dist-cjs/puzzles/cube4/scramble-cache.js +156 -0
- package/dist-cjs/puzzles/cube4/scrambler.js +271 -80
- 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/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/sq1/solver/browser-loader.js +58 -26
- package/docs/development.md +19 -7
- package/package.json +4 -3
|
@@ -1,29 +1,48 @@
|
|
|
1
1
|
import { SQ1_SOLVER_TABLE_METADATA } from "./table-metadata.js";
|
|
2
2
|
import { fetchTableBuffer } from "../../shared/compressed-fetch.js";
|
|
3
3
|
const TABLES_DIR = "puzzles/sq1/solver/generated";
|
|
4
|
+
const CACHE_NAME = "cubingcenter-scrambler-tables-v1";
|
|
4
5
|
let cachedBrowserTables;
|
|
6
|
+
let cachedBrowserTablesPromise;
|
|
5
7
|
export function getCachedBrowserTables() {
|
|
6
8
|
return cachedBrowserTables;
|
|
7
9
|
}
|
|
8
10
|
export function clearCachedBrowserTables() {
|
|
9
11
|
cachedBrowserTables = undefined;
|
|
12
|
+
cachedBrowserTablesPromise = undefined;
|
|
10
13
|
}
|
|
11
14
|
export async function loadBrowserTables(baseUrl) {
|
|
12
15
|
if (cachedBrowserTables) {
|
|
13
16
|
return cachedBrowserTables;
|
|
14
17
|
}
|
|
18
|
+
if (cachedBrowserTablesPromise) {
|
|
19
|
+
// One shared in-flight promise: concurrent calls trigger exactly one
|
|
20
|
+
// fetch pass (dedup).
|
|
21
|
+
return cachedBrowserTablesPromise;
|
|
22
|
+
}
|
|
23
|
+
const promise = fetchTables(baseUrl);
|
|
24
|
+
cachedBrowserTablesPromise = promise;
|
|
25
|
+
promise.then(() => { }, () => {
|
|
26
|
+
// A failed load must not poison the dedup slot: purge the rejected
|
|
27
|
+
// promise so a later call retries from scratch.
|
|
28
|
+
if (cachedBrowserTablesPromise === promise)
|
|
29
|
+
cachedBrowserTablesPromise = undefined;
|
|
30
|
+
});
|
|
31
|
+
return promise;
|
|
32
|
+
}
|
|
33
|
+
async function fetchTables(baseUrl) {
|
|
15
34
|
const resolved = resolveBaseUrl(baseUrl);
|
|
16
35
|
const manifest = await fetchManifest(resolved);
|
|
17
36
|
const [shapeIdx, shapePrun, shapeTopMove, shapeBottomMove, shapeTwistMove, squareTwistMove, squareTopMove, squareBottomMove, squarePrun] = await Promise.all([
|
|
18
|
-
fetchInt32Table(resolved, manifest.files.shapeIdx, SQ1_SOLVER_TABLE_METADATA.shapeIdx
|
|
19
|
-
fetchInt8Table(resolved, manifest.files.shapePrun, SQ1_SOLVER_TABLE_METADATA.shapePrun
|
|
20
|
-
fetchInt32Table(resolved, manifest.files.shapeTopMove, SQ1_SOLVER_TABLE_METADATA.shapeTopMove
|
|
21
|
-
fetchInt32Table(resolved, manifest.files.shapeBottomMove, SQ1_SOLVER_TABLE_METADATA.shapeBottomMove
|
|
22
|
-
fetchInt32Table(resolved, manifest.files.shapeTwistMove, SQ1_SOLVER_TABLE_METADATA.shapeTwistMove
|
|
23
|
-
fetchUint16Table(resolved, manifest.files.squareTwistMove, SQ1_SOLVER_TABLE_METADATA.squareTwistMove
|
|
24
|
-
fetchUint16Table(resolved, manifest.files.squareTopMove, SQ1_SOLVER_TABLE_METADATA.squareTopMove
|
|
25
|
-
fetchUint16Table(resolved, manifest.files.squareBottomMove, SQ1_SOLVER_TABLE_METADATA.squareBottomMove
|
|
26
|
-
fetchInt8Table(resolved, manifest.files.squarePrun, SQ1_SOLVER_TABLE_METADATA.squarePrun
|
|
37
|
+
fetchInt32Table(resolved, manifest.files.shapeIdx, SQ1_SOLVER_TABLE_METADATA.shapeIdx),
|
|
38
|
+
fetchInt8Table(resolved, manifest.files.shapePrun, SQ1_SOLVER_TABLE_METADATA.shapePrun),
|
|
39
|
+
fetchInt32Table(resolved, manifest.files.shapeTopMove, SQ1_SOLVER_TABLE_METADATA.shapeTopMove),
|
|
40
|
+
fetchInt32Table(resolved, manifest.files.shapeBottomMove, SQ1_SOLVER_TABLE_METADATA.shapeBottomMove),
|
|
41
|
+
fetchInt32Table(resolved, manifest.files.shapeTwistMove, SQ1_SOLVER_TABLE_METADATA.shapeTwistMove),
|
|
42
|
+
fetchUint16Table(resolved, manifest.files.squareTwistMove, SQ1_SOLVER_TABLE_METADATA.squareTwistMove),
|
|
43
|
+
fetchUint16Table(resolved, manifest.files.squareTopMove, SQ1_SOLVER_TABLE_METADATA.squareTopMove),
|
|
44
|
+
fetchUint16Table(resolved, manifest.files.squareBottomMove, SQ1_SOLVER_TABLE_METADATA.squareBottomMove),
|
|
45
|
+
fetchInt8Table(resolved, manifest.files.squarePrun, SQ1_SOLVER_TABLE_METADATA.squarePrun),
|
|
27
46
|
]);
|
|
28
47
|
cachedBrowserTables = {
|
|
29
48
|
shapeIdx,
|
|
@@ -59,34 +78,47 @@ async function fetchManifest(baseUrl) {
|
|
|
59
78
|
}
|
|
60
79
|
return (await response.json());
|
|
61
80
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Fetch + verify with the SAME checks as node-loader.ts: byte length from
|
|
83
|
+
* the manifest entries, FNV-1a checksum against the table-metadata
|
|
84
|
+
* constants (node skips the checksum when that constant is 0 = "unchecked";
|
|
85
|
+
* the browser mirrors the guard).
|
|
86
|
+
*/
|
|
87
|
+
async function fetchInt32Table(baseUrl, entry, meta) {
|
|
88
|
+
const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
|
|
89
|
+
cacheName: CACHE_NAME,
|
|
90
|
+
compressed: entry.compressed !== false,
|
|
91
|
+
expectedBytes: entry.entries * 4,
|
|
92
|
+
...(meta.checksum !== 0 ? { expectedFnv1a: { value: meta.checksum, elementSize: 4 } } : {})
|
|
93
|
+
});
|
|
94
|
+
const values = new Int32Array(entry.entries);
|
|
68
95
|
const view = new DataView(buffer);
|
|
69
96
|
for (let i = 0; i < values.length; i++) {
|
|
70
97
|
values[i] = view.getInt32(i * 4, true);
|
|
71
98
|
}
|
|
72
99
|
return values;
|
|
73
100
|
}
|
|
74
|
-
async function fetchUint16Table(baseUrl, entry,
|
|
75
|
-
const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
101
|
+
async function fetchUint16Table(baseUrl, entry, meta) {
|
|
102
|
+
const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
|
|
103
|
+
cacheName: CACHE_NAME,
|
|
104
|
+
compressed: entry.compressed !== false,
|
|
105
|
+
expectedBytes: entry.entries * 2,
|
|
106
|
+
...(meta.checksum !== 0 ? { expectedFnv1a: { value: meta.checksum, elementSize: 2 } } : {})
|
|
107
|
+
});
|
|
108
|
+
const values = new Uint16Array(entry.entries);
|
|
80
109
|
const view = new DataView(buffer);
|
|
81
110
|
for (let i = 0; i < values.length; i++) {
|
|
82
111
|
values[i] = view.getUint16(i * 2, true);
|
|
83
112
|
}
|
|
84
113
|
return values;
|
|
85
114
|
}
|
|
86
|
-
async function fetchInt8Table(baseUrl, entry,
|
|
87
|
-
const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
115
|
+
async function fetchInt8Table(baseUrl, entry, meta) {
|
|
116
|
+
const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
|
|
117
|
+
cacheName: CACHE_NAME,
|
|
118
|
+
compressed: entry.compressed !== false,
|
|
119
|
+
expectedBytes: entry.entries,
|
|
120
|
+
// Square-1's int8 tables hash SIGNED values in node (Int8Array).
|
|
121
|
+
...(meta.checksum !== 0 ? { expectedFnv1a: { value: meta.checksum, elementSize: 1, signed: true } } : {})
|
|
122
|
+
});
|
|
91
123
|
return new Int8Array(buffer);
|
|
92
124
|
}
|
package/dist-cjs/core/api.js
CHANGED
|
@@ -80,8 +80,16 @@ async function initializeScrambler(baseUrl, eventId) {
|
|
|
80
80
|
await loadBrowserTables(baseUrl);
|
|
81
81
|
}
|
|
82
82
|
else if (eventId === "444") {
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
// Reduction ends with a 3x3 solve in the same worker. Initializing 444
|
|
84
|
+
// must also load those required tables, including the caller's base URL.
|
|
85
|
+
const [cube4, cube3] = await Promise.all([
|
|
86
|
+
Promise.resolve().then(() => __importStar(require("../puzzles/cube4/reduction/browser-loader.js"))),
|
|
87
|
+
Promise.resolve().then(() => __importStar(require("../puzzles/cube3/solver/browser-loader.js")))
|
|
88
|
+
]);
|
|
89
|
+
await Promise.all([
|
|
90
|
+
cube4.loadCube4ReductionTablesBrowser(baseUrl),
|
|
91
|
+
cube3.loadBrowserTables(baseUrl)
|
|
92
|
+
]);
|
|
85
93
|
}
|
|
86
94
|
else if (!eventId) {
|
|
87
95
|
await Promise.all([
|
|
@@ -6,23 +6,42 @@ exports.loadBrowserTables = loadBrowserTables;
|
|
|
6
6
|
const table_metadata_js_1 = require("./table-metadata.js");
|
|
7
7
|
const compressed_fetch_js_1 = require("../../shared/compressed-fetch.js");
|
|
8
8
|
const TABLES_DIR = "puzzles/cube2/solver/generated";
|
|
9
|
+
const CACHE_NAME = "cubingcenter-scrambler-tables-v1";
|
|
9
10
|
let cachedBrowserTables;
|
|
11
|
+
let cachedBrowserTablesPromise;
|
|
10
12
|
function getCachedBrowserTables() {
|
|
11
13
|
return cachedBrowserTables;
|
|
12
14
|
}
|
|
13
15
|
function clearCachedBrowserTables() {
|
|
14
16
|
cachedBrowserTables = undefined;
|
|
17
|
+
cachedBrowserTablesPromise = undefined;
|
|
15
18
|
}
|
|
16
19
|
async function loadBrowserTables(baseUrl) {
|
|
17
20
|
if (cachedBrowserTables) {
|
|
18
21
|
return cachedBrowserTables;
|
|
19
22
|
}
|
|
23
|
+
if (cachedBrowserTablesPromise) {
|
|
24
|
+
// One shared in-flight promise: concurrent calls trigger exactly one
|
|
25
|
+
// fetch pass (dedup).
|
|
26
|
+
return cachedBrowserTablesPromise;
|
|
27
|
+
}
|
|
28
|
+
const promise = fetchTables(baseUrl);
|
|
29
|
+
cachedBrowserTablesPromise = promise;
|
|
30
|
+
promise.then(() => { }, () => {
|
|
31
|
+
// A failed load must not poison the dedup slot: purge the rejected
|
|
32
|
+
// promise so a later call retries from scratch.
|
|
33
|
+
if (cachedBrowserTablesPromise === promise)
|
|
34
|
+
cachedBrowserTablesPromise = undefined;
|
|
35
|
+
});
|
|
36
|
+
return promise;
|
|
37
|
+
}
|
|
38
|
+
async function fetchTables(baseUrl) {
|
|
20
39
|
const resolved = resolveBaseUrl(baseUrl);
|
|
21
40
|
const manifest = await fetchManifest(resolved);
|
|
22
41
|
const [permTable, orientTable, dist] = await Promise.all([
|
|
23
|
-
fetchUint16Table(resolved, manifest.files.permutationTable, table_metadata_js_1.CUBE2_SOLVER_TABLE_METADATA.permutationTable
|
|
24
|
-
fetchUint16Table(resolved, manifest.files.orientationTable, table_metadata_js_1.CUBE2_SOLVER_TABLE_METADATA.orientationTable
|
|
25
|
-
fetchUint8Table(resolved, manifest.files.policy, table_metadata_js_1.CUBE2_SOLVER_TABLE_METADATA.policy
|
|
42
|
+
fetchUint16Table(resolved, manifest.files.permutationTable, table_metadata_js_1.CUBE2_SOLVER_TABLE_METADATA.permutationTable),
|
|
43
|
+
fetchUint16Table(resolved, manifest.files.orientationTable, table_metadata_js_1.CUBE2_SOLVER_TABLE_METADATA.orientationTable),
|
|
44
|
+
fetchUint8Table(resolved, manifest.files.policy, table_metadata_js_1.CUBE2_SOLVER_TABLE_METADATA.policy)
|
|
26
45
|
]);
|
|
27
46
|
cachedBrowserTables = {
|
|
28
47
|
permutationTable: permTable,
|
|
@@ -56,22 +75,31 @@ async function fetchManifest(baseUrl) {
|
|
|
56
75
|
}
|
|
57
76
|
return manifest;
|
|
58
77
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Fetch + verify with the SAME checks as node-loader.ts: byte length from
|
|
80
|
+
* the manifest entries, FNV-1a checksum against the table-metadata
|
|
81
|
+
* constants (the values node asserts against).
|
|
82
|
+
*/
|
|
83
|
+
async function fetchUint16Table(baseUrl, entry, meta) {
|
|
84
|
+
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
85
|
+
cacheName: CACHE_NAME,
|
|
86
|
+
compressed: entry.compressed !== false,
|
|
87
|
+
expectedBytes: entry.entries * 2,
|
|
88
|
+
expectedFnv1a: { value: meta.checksum, elementSize: 2 }
|
|
89
|
+
});
|
|
90
|
+
const values = new Uint16Array(entry.entries);
|
|
65
91
|
const view = new DataView(buffer);
|
|
66
92
|
for (let i = 0; i < values.length; i++) {
|
|
67
93
|
values[i] = view.getUint16(i * 2, true);
|
|
68
94
|
}
|
|
69
95
|
return values;
|
|
70
96
|
}
|
|
71
|
-
async function fetchUint8Table(baseUrl, entry,
|
|
72
|
-
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
97
|
+
async function fetchUint8Table(baseUrl, entry, meta) {
|
|
98
|
+
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
99
|
+
cacheName: CACHE_NAME,
|
|
100
|
+
compressed: entry.compressed !== false,
|
|
101
|
+
expectedBytes: entry.entries,
|
|
102
|
+
expectedFnv1a: { value: meta.checksum, elementSize: 1 }
|
|
103
|
+
});
|
|
76
104
|
return new Uint8Array(buffer);
|
|
77
105
|
}
|
|
@@ -35,7 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.loadGeneratedCube2SolverTables = loadGeneratedCube2SolverTables;
|
|
37
37
|
const browser_loader_js_1 = require("./browser-loader.js");
|
|
38
|
-
|
|
38
|
+
const load_once_js_1 = require("../../shared/load-once.js");
|
|
39
|
+
// One shared in-flight promise for the node fs load (same pattern as
|
|
40
|
+
// cube3/solver/generated-assets.ts): the previous `cached ??= await …` raced —
|
|
41
|
+
// concurrent callers interleaving on the await both ran `loadNodeTables()`
|
|
42
|
+
// (two filesystem reads, last assignment wins). On rejection the slot is
|
|
43
|
+
// purged so the next call can retry.
|
|
44
|
+
const nodeTablesSlot = { promise: undefined };
|
|
39
45
|
function isBrowser() {
|
|
40
46
|
return typeof self !== "undefined";
|
|
41
47
|
}
|
|
@@ -49,8 +55,7 @@ async function loadGeneratedCube2SolverTables() {
|
|
|
49
55
|
"In browser environments, call `initializeScrambler(baseUrl)` " +
|
|
50
56
|
"before generating scrambles.");
|
|
51
57
|
}
|
|
52
|
-
|
|
53
|
-
return cachedTables;
|
|
58
|
+
return (0, load_once_js_1.loadOnce)(nodeTablesSlot, loadNodeTables);
|
|
54
59
|
}
|
|
55
60
|
async function loadNodeTables() {
|
|
56
61
|
const loader = await Promise.resolve().then(() => __importStar(require(
|
|
@@ -7,6 +7,7 @@ const compressed_fetch_js_1 = require("../../shared/compressed-fetch.js");
|
|
|
7
7
|
const TABLES_DIR = "puzzles/cube3/solver/generated";
|
|
8
8
|
const CACHE_NAME = "cubingcenter-scrambler-tables-v1";
|
|
9
9
|
let browserTables;
|
|
10
|
+
let browserTablesPromise;
|
|
10
11
|
function resolveBaseUrl(provided) {
|
|
11
12
|
if (provided) {
|
|
12
13
|
return provided.replace(/\/$/, "");
|
|
@@ -25,11 +26,28 @@ function getCachedBrowserTables() {
|
|
|
25
26
|
}
|
|
26
27
|
function clearCachedBrowserTables() {
|
|
27
28
|
browserTables = undefined;
|
|
29
|
+
browserTablesPromise = undefined;
|
|
28
30
|
}
|
|
29
31
|
async function loadBrowserTables(baseUrl) {
|
|
30
32
|
if (browserTables) {
|
|
31
33
|
return browserTables;
|
|
32
34
|
}
|
|
35
|
+
if (browserTablesPromise) {
|
|
36
|
+
// One shared in-flight promise: concurrent calls trigger exactly one
|
|
37
|
+
// fetch pass (dedup).
|
|
38
|
+
return browserTablesPromise;
|
|
39
|
+
}
|
|
40
|
+
const promise = fetchTables(baseUrl);
|
|
41
|
+
browserTablesPromise = promise;
|
|
42
|
+
promise.then(() => { }, () => {
|
|
43
|
+
// A failed load must not poison the dedup slot: purge the rejected
|
|
44
|
+
// promise so a later call retries from scratch.
|
|
45
|
+
if (browserTablesPromise === promise)
|
|
46
|
+
browserTablesPromise = undefined;
|
|
47
|
+
});
|
|
48
|
+
return promise;
|
|
49
|
+
}
|
|
50
|
+
async function fetchTables(baseUrl) {
|
|
33
51
|
const resolvedBase = resolveBaseUrl(baseUrl);
|
|
34
52
|
const manifest = await fetchManifest(resolvedBase);
|
|
35
53
|
browserTables = {
|
|
@@ -63,19 +81,15 @@ async function fetchJointTables(baseUrl, entries) {
|
|
|
63
81
|
return undefined;
|
|
64
82
|
}
|
|
65
83
|
const fetchJoint = async (entry) => {
|
|
84
|
+
// Verified by fetchTableBuffer with the SAME checks as node-loader.ts:
|
|
85
|
+
// nibble-packed byte length + FNV-1a over the packed bytes.
|
|
66
86
|
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
67
87
|
cacheName: CACHE_NAME,
|
|
68
|
-
compressed: entry.compressed !== false
|
|
88
|
+
compressed: entry.compressed !== false,
|
|
89
|
+
expectedBytes: Math.ceil((entry.rangeA * entry.rangeB) / 2),
|
|
90
|
+
expectedFnv1a: { value: entry.checksum, elementSize: 1 }
|
|
69
91
|
});
|
|
70
|
-
|
|
71
|
-
if (buffer.byteLength !== expectedBytes) {
|
|
72
|
-
throw new Error(`Invalid byte size for ${entry.file}.`);
|
|
73
|
-
}
|
|
74
|
-
const values = new Uint8Array(buffer);
|
|
75
|
-
if (checksum(values) !== entry.checksum) {
|
|
76
|
-
throw new Error(`Checksum mismatch for ${entry.file}.`);
|
|
77
|
-
}
|
|
78
|
-
return values;
|
|
92
|
+
return new Uint8Array(buffer);
|
|
79
93
|
};
|
|
80
94
|
return {
|
|
81
95
|
jointUdCo: await fetchJoint(entries.udCo),
|
|
@@ -95,11 +109,15 @@ async function fetchManifest(baseUrl) {
|
|
|
95
109
|
return manifest;
|
|
96
110
|
}
|
|
97
111
|
async function fetchMoveTable(baseUrl, entry) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
112
|
+
// Verified by fetchTableBuffer with the SAME checks as node-loader.ts:
|
|
113
|
+
// int32 wire length + FNV-1a over the decoded coordinate values (element
|
|
114
|
+
// size 4 reproduces node's hash over the uint16 values — same numbers).
|
|
115
|
+
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
116
|
+
cacheName: CACHE_NAME,
|
|
117
|
+
compressed: entry.compressed !== false,
|
|
118
|
+
expectedBytes: entry.range * entry.moves.length * 4,
|
|
119
|
+
expectedFnv1a: { value: entry.checksum, elementSize: 4 }
|
|
120
|
+
});
|
|
103
121
|
const values = new Uint16Array(entry.range * entry.moves.length);
|
|
104
122
|
const view = new DataView(buffer);
|
|
105
123
|
for (let index = 0; index < values.length; index += 1) {
|
|
@@ -119,11 +137,15 @@ async function fetchMoveTable(baseUrl, entry) {
|
|
|
119
137
|
};
|
|
120
138
|
}
|
|
121
139
|
async function fetchPruningTable(baseUrl, entry) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
140
|
+
// Verified by fetchTableBuffer with the SAME checks as node-loader.ts:
|
|
141
|
+
// nibble-packed byte length + FNV-1a over the packed bytes (this browser
|
|
142
|
+
// path did NOT compare the pruning checksum before — only the length).
|
|
143
|
+
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
144
|
+
cacheName: CACHE_NAME,
|
|
145
|
+
compressed: entry.compressed !== false,
|
|
146
|
+
expectedBytes: Math.ceil(entry.range / 2),
|
|
147
|
+
expectedFnv1a: { value: entry.checksum, elementSize: 1 }
|
|
148
|
+
});
|
|
127
149
|
const values = new Uint8Array(buffer);
|
|
128
150
|
return {
|
|
129
151
|
coordinate: entry.coordinate,
|
|
@@ -37,10 +37,17 @@ exports.loadGeneratedCube3SolverTables = loadGeneratedCube3SolverTables;
|
|
|
37
37
|
exports.loadGeneratedCube3Phase1Tables = loadGeneratedCube3Phase1Tables;
|
|
38
38
|
exports.loadGeneratedCube3Phase2Tables = loadGeneratedCube3Phase2Tables;
|
|
39
39
|
const browser_loader_js_1 = require("./browser-loader.js");
|
|
40
|
-
|
|
40
|
+
const load_once_js_1 = require("../../shared/load-once.js");
|
|
41
41
|
function isBrowser() {
|
|
42
42
|
return typeof self !== "undefined";
|
|
43
43
|
}
|
|
44
|
+
// One shared in-flight promise for the node fs load: mixed concurrent
|
|
45
|
+
// phase-1 + phase-2 loads (separate slots in tables.ts) converge on ONE
|
|
46
|
+
// `readGeneratedCube3SolverTables()` call. The previous `cached ??= await …`
|
|
47
|
+
// raced — two callers interleaving on the await both ran `loadNodeTables()`
|
|
48
|
+
// (two filesystem reads, two table sets, last assignment wins). On
|
|
49
|
+
// rejection the slot is purged so the next call can retry.
|
|
50
|
+
const nodeTablesSlot = { promise: undefined };
|
|
44
51
|
async function loadGeneratedCube3SolverTables() {
|
|
45
52
|
if (isBrowser()) {
|
|
46
53
|
const browserTables = (0, browser_loader_js_1.getCachedBrowserTables)();
|
|
@@ -51,8 +58,7 @@ async function loadGeneratedCube3SolverTables() {
|
|
|
51
58
|
"In browser environments, call `initializeScrambler(baseUrl)` " +
|
|
52
59
|
"before generating scrambles.");
|
|
53
60
|
}
|
|
54
|
-
|
|
55
|
-
return cachedGeneratedTables;
|
|
61
|
+
return (0, load_once_js_1.loadOnce)(nodeTablesSlot, loadNodeTables);
|
|
56
62
|
}
|
|
57
63
|
async function loadNodeTables() {
|
|
58
64
|
const loader = await Promise.resolve().then(() => __importStar(require(
|
|
@@ -37,7 +37,7 @@ async function solveCube3Phase1(state, options = {}) {
|
|
|
37
37
|
const maxDepth = options.maxDepth ?? 12;
|
|
38
38
|
const start = encodeCube3Phase1Coordinates(state);
|
|
39
39
|
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
40
|
-
const context = createPhase1SearchContext(tables, jointTables, maxDepth, deadline);
|
|
40
|
+
const context = createPhase1SearchContext(tables, jointTables, maxDepth, deadline, options.maxNodes);
|
|
41
41
|
if (await isCube3Phase1Solved(start, tables)) {
|
|
42
42
|
return {
|
|
43
43
|
moves: [],
|
|
@@ -79,6 +79,9 @@ async function solveCube3Phase1(state, options = {}) {
|
|
|
79
79
|
visitedNodes: context.visitedNodes
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
+
if (context.nodesExceeded) {
|
|
83
|
+
throw new Cube3Phase1SearchError(`3x3 Phase 1 node budget exceeded (${options.maxNodes} nodes).`, context.visitedNodes);
|
|
84
|
+
}
|
|
82
85
|
if (context.timedOut) {
|
|
83
86
|
throw new Cube3Phase1SearchError(`3x3 Phase 1 search timed out after ${options.timeoutMs}ms.`, context.visitedNodes);
|
|
84
87
|
}
|
|
@@ -109,8 +112,17 @@ const PHASE1_MOVE_FACES = tables_js_1.CUBE3_PHASE1_MOVES.map((move) => {
|
|
|
109
112
|
});
|
|
110
113
|
function searchPhase1(cornerOrientation, edgeOrientation, udSlice, previousFace, previousPreviousFace, remainingDepth, path, context) {
|
|
111
114
|
context.visitedNodes += 1;
|
|
112
|
-
if (context.
|
|
115
|
+
if (context.nodeCap !== undefined &&
|
|
116
|
+
context.visitedNodes > context.nodeCap) {
|
|
117
|
+
context.nodesExceeded = true;
|
|
118
|
+
}
|
|
119
|
+
if (!context.nodesExceeded &&
|
|
120
|
+
context.deadline !== undefined &&
|
|
121
|
+
(context.visitedNodes & 1023) === 0 &&
|
|
122
|
+
performance.now() > context.deadline) {
|
|
113
123
|
context.timedOut = true;
|
|
124
|
+
}
|
|
125
|
+
if (context.timedOut || context.nodesExceeded) {
|
|
114
126
|
return false;
|
|
115
127
|
}
|
|
116
128
|
if (remainingDepth === 0) {
|
|
@@ -133,7 +145,7 @@ function searchPhase1(cornerOrientation, edgeOrientation, udSlice, previousFace,
|
|
|
133
145
|
return true;
|
|
134
146
|
}
|
|
135
147
|
path.pop();
|
|
136
|
-
if (context.timedOut) {
|
|
148
|
+
if (context.timedOut || context.nodesExceeded) {
|
|
137
149
|
return false;
|
|
138
150
|
}
|
|
139
151
|
}
|
|
@@ -150,13 +162,13 @@ async function collectCube3Phase1Solutions(state, depth, options = {}) {
|
|
|
150
162
|
const jointTables = options.jointTables ?? tables.jointTables;
|
|
151
163
|
const limit = options.limit ?? 32;
|
|
152
164
|
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
153
|
-
const context = createPhase1SearchContext(tables, jointTables, depth, deadline);
|
|
165
|
+
const context = createPhase1SearchContext(tables, jointTables, depth, deadline, options.maxNodes);
|
|
154
166
|
const start = encodeCube3Phase1Coordinates(state);
|
|
155
167
|
const solutions = [];
|
|
156
168
|
searchPhase1Collect(start.cornerOrientation, start.edgeOrientation, start.udSlice, -1, -1, depth, [], solutions, limit, context);
|
|
157
169
|
return { solutions, visitedNodes: context.visitedNodes };
|
|
158
170
|
}
|
|
159
|
-
function createPhase1SearchContext(tables, jointTables, maxDepth, deadline) {
|
|
171
|
+
function createPhase1SearchContext(tables, jointTables, maxDepth, deadline, nodeCap) {
|
|
160
172
|
const scratchLevels = maxDepth + 1;
|
|
161
173
|
const scratchSize = scratchLevels * tables_js_1.CUBE3_PHASE1_MOVES.length;
|
|
162
174
|
return {
|
|
@@ -164,8 +176,10 @@ function createPhase1SearchContext(tables, jointTables, maxDepth, deadline) {
|
|
|
164
176
|
jointTables,
|
|
165
177
|
maxDepth,
|
|
166
178
|
deadline,
|
|
179
|
+
nodeCap,
|
|
167
180
|
visitedNodes: 0,
|
|
168
181
|
timedOut: false,
|
|
182
|
+
nodesExceeded: false,
|
|
169
183
|
scratchMove: new Int32Array(scratchSize),
|
|
170
184
|
scratchBound: new Int32Array(scratchSize),
|
|
171
185
|
scratchCo: new Int32Array(scratchSize),
|
|
@@ -261,8 +275,17 @@ function collectPhase1Children(cornerOrientation, edgeOrientation, udSlice, prev
|
|
|
261
275
|
*/
|
|
262
276
|
function searchPhase1Collect(cornerOrientation, edgeOrientation, udSlice, previousFace, previousPreviousFace, remainingDepth, path, solutions, limit, context) {
|
|
263
277
|
context.visitedNodes += 1;
|
|
264
|
-
if (context.
|
|
278
|
+
if (context.nodeCap !== undefined &&
|
|
279
|
+
context.visitedNodes > context.nodeCap) {
|
|
280
|
+
context.nodesExceeded = true;
|
|
281
|
+
}
|
|
282
|
+
if (!context.nodesExceeded &&
|
|
283
|
+
context.deadline !== undefined &&
|
|
284
|
+
(context.visitedNodes & 1023) === 0 &&
|
|
285
|
+
performance.now() > context.deadline) {
|
|
265
286
|
context.timedOut = true;
|
|
287
|
+
}
|
|
288
|
+
if (context.timedOut || context.nodesExceeded) {
|
|
266
289
|
return true;
|
|
267
290
|
}
|
|
268
291
|
if (remainingDepth === 0) {
|
|
@@ -55,7 +55,7 @@ async function solveCube3Phase2(state, options = {}) {
|
|
|
55
55
|
const maxDepth = options.maxDepth ?? 18;
|
|
56
56
|
const start = encodeCube3Phase2Coordinates(state);
|
|
57
57
|
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
58
|
-
const context = createPhase2SearchContext(tables, pairTables, maxDepth, deadline);
|
|
58
|
+
const context = createPhase2SearchContext(tables, pairTables, maxDepth, deadline, options.maxNodes);
|
|
59
59
|
const spRange = tables.moveTables.slicePermutation.range;
|
|
60
60
|
const cpspTable = tables.pruningTables.cornerPermutationSlicePermutation.values;
|
|
61
61
|
const epspTable = tables.pruningTables.udEdgePermutationSlicePermutation.values;
|
|
@@ -86,6 +86,9 @@ async function solveCube3Phase2(state, options = {}) {
|
|
|
86
86
|
visitedNodes: context.visitedNodes
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
|
+
if (context.nodesExceeded) {
|
|
90
|
+
throw new Cube3Phase2SearchError(`3x3 Phase 2 node budget exceeded (${options.maxNodes} nodes).`, context.visitedNodes);
|
|
91
|
+
}
|
|
89
92
|
if (context.timedOut) {
|
|
90
93
|
throw new Cube3Phase2SearchError(`3x3 Phase 2 search timed out after ${options.timeoutMs}ms.`, context.visitedNodes);
|
|
91
94
|
}
|
|
@@ -126,7 +129,19 @@ async function solveCube3State(state, options = {}) {
|
|
|
126
129
|
const p2Tables = options.phase2?.tables ?? await (0, tables_js_1.loadCube3Phase2Tables)();
|
|
127
130
|
const scoredPairTables = options.phase2?.pairTables ??
|
|
128
131
|
(options.phase2?.skipPairTables ? undefined : await (0, pair_tables_js_1.loadCube3PairTables)());
|
|
129
|
-
|
|
132
|
+
// Work budget for the improvement loop. Preferred: `improveMaxNodes`
|
|
133
|
+
// (deterministic node counter — the wall deadline stays unarmed so the
|
|
134
|
+
// outcome cannot depend on CPU load/clock). Legacy: `timeoutMs ?? 800`
|
|
135
|
+
// wall deadline, kept for existing callers.
|
|
136
|
+
const nodeMode = options.improveMaxNodes !== undefined;
|
|
137
|
+
let remainingNodes = options.improveMaxNodes ?? 0;
|
|
138
|
+
const deadline = nodeMode ? undefined : performance.now() + (options.timeoutMs ?? 800);
|
|
139
|
+
const budgetExhausted = () => nodeMode ? remainingNodes <= 0 : performance.now() > deadline;
|
|
140
|
+
const spendNodes = (used) => {
|
|
141
|
+
if (nodeMode) {
|
|
142
|
+
remainingNodes = Math.max(0, remainingNodes - used);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
130
145
|
const p2SpRange = p2Tables.moveTables.slicePermutation.range;
|
|
131
146
|
const p2cpsp = p2Tables.pruningTables.cornerPermutationSlicePermutation.values;
|
|
132
147
|
const p2uepsp = p2Tables.pruningTables.udEdgePermutationSlicePermutation.values;
|
|
@@ -138,18 +153,21 @@ async function solveCube3State(state, options = {}) {
|
|
|
138
153
|
const minDepth = phase1.depth;
|
|
139
154
|
const scored = [];
|
|
140
155
|
for (let d = minDepth; d <= minDepth + improveDepths && d < bestMoves.length; d++) {
|
|
141
|
-
if (
|
|
156
|
+
if (budgetExhausted())
|
|
142
157
|
break;
|
|
143
158
|
const { solutions: p1Solutions, visitedNodes } = await (0, phase1_js_1.collectCube3Phase1Solutions)(state, d, {
|
|
144
159
|
tables: p1Tables,
|
|
145
160
|
limit: solutionLimit,
|
|
146
|
-
|
|
161
|
+
...(nodeMode
|
|
162
|
+
? { maxNodes: Math.max(1, remainingNodes) }
|
|
163
|
+
: { timeoutMs: Math.max(1, deadline - performance.now()) })
|
|
147
164
|
});
|
|
148
165
|
improvementNodes += visitedNodes;
|
|
149
|
-
|
|
166
|
+
spendNodes(visitedNodes);
|
|
167
|
+
if (budgetExhausted())
|
|
150
168
|
break;
|
|
151
169
|
for (const p1Moves of p1Solutions) {
|
|
152
|
-
if (
|
|
170
|
+
if (budgetExhausted())
|
|
153
171
|
break;
|
|
154
172
|
if (p1Moves.length >= bestMoves.length)
|
|
155
173
|
continue;
|
|
@@ -186,7 +204,7 @@ async function solveCube3State(state, options = {}) {
|
|
|
186
204
|
// Sort ALL candidates globally across depths by admissible total lower bound
|
|
187
205
|
scored.sort((left, right) => left.est - right.est);
|
|
188
206
|
for (const cand of scored) {
|
|
189
|
-
if (
|
|
207
|
+
if (budgetExhausted())
|
|
190
208
|
break;
|
|
191
209
|
// Prune: since candidates are sorted by est, no subsequent candidate can beat bestMoves.length
|
|
192
210
|
if (cand.est >= bestMoves.length)
|
|
@@ -195,19 +213,31 @@ async function solveCube3State(state, options = {}) {
|
|
|
195
213
|
if (maxP2 <= 0)
|
|
196
214
|
continue;
|
|
197
215
|
try {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
216
|
+
// Strip any legacy wall `timeoutMs` from the spread: in node mode
|
|
217
|
+
// the wall deadline must not be armed at all (pure function of
|
|
218
|
+
// budgets); in wall mode the per-candidate cap below re-adds it.
|
|
219
|
+
const phase2Source = options.phase2 ?? {};
|
|
220
|
+
const { timeoutMs: legacyPhase2Wall, ...phase2CandidateBase } = phase2Source;
|
|
221
|
+
// Per-candidate budget: node cap (deterministic) when the loop runs
|
|
222
|
+
// on `improveMaxNodes`, else the legacy wall cap.
|
|
223
|
+
const candidateCap = nodeMode
|
|
224
|
+
? {
|
|
225
|
+
maxNodes: Math.max(1, Math.min(options.improvePhase2MaxNodes ?? phase2CandidateBase.maxNodes ?? remainingNodes, remainingNodes))
|
|
226
|
+
}
|
|
227
|
+
: {
|
|
228
|
+
timeoutMs: Math.min(options.improvePhase2TimeoutMs ?? legacyPhase2Wall ?? 25, Math.max(1, deadline - performance.now()))
|
|
229
|
+
};
|
|
201
230
|
const candP2 = await solveCube3Phase2(cand.state, {
|
|
202
|
-
...
|
|
231
|
+
...phase2CandidateBase,
|
|
203
232
|
pairTables: scoredPairTables,
|
|
204
233
|
tables: p2Tables,
|
|
205
234
|
maxDepth: maxP2,
|
|
206
235
|
previousFace: cand.pFace,
|
|
207
236
|
previousPreviousFace: cand.ppFace,
|
|
208
|
-
|
|
237
|
+
...candidateCap
|
|
209
238
|
});
|
|
210
239
|
improvementNodes += candP2.visitedNodes;
|
|
240
|
+
spendNodes(candP2.visitedNodes);
|
|
211
241
|
const candTotal = cand.moves.length + candP2.moves.length;
|
|
212
242
|
if (candTotal < bestMoves.length) {
|
|
213
243
|
bestMoves = [...cand.moves, ...candP2.moves];
|
|
@@ -301,7 +331,7 @@ const PHASE2_MOVE_FACES = tables_js_1.CUBE3_PHASE2_MOVES.map((move) => {
|
|
|
301
331
|
const char = move[0];
|
|
302
332
|
return char === "U" ? 0 : char === "D" ? 1 : char === "R" ? 2 : char === "L" ? 3 : char === "F" ? 4 : 5;
|
|
303
333
|
});
|
|
304
|
-
function createPhase2SearchContext(tables, pairTables, maxDepth, deadline) {
|
|
334
|
+
function createPhase2SearchContext(tables, pairTables, maxDepth, deadline, nodeCap = undefined) {
|
|
305
335
|
const scratchLevels = maxDepth + 1;
|
|
306
336
|
const scratchSize = scratchLevels * tables_js_1.CUBE3_PHASE2_MOVES.length;
|
|
307
337
|
return {
|
|
@@ -309,8 +339,10 @@ function createPhase2SearchContext(tables, pairTables, maxDepth, deadline) {
|
|
|
309
339
|
pairTables,
|
|
310
340
|
maxDepth,
|
|
311
341
|
deadline,
|
|
342
|
+
nodeCap,
|
|
312
343
|
visitedNodes: 0,
|
|
313
344
|
timedOut: false,
|
|
345
|
+
nodesExceeded: false,
|
|
314
346
|
scratchMove: new Int32Array(scratchSize),
|
|
315
347
|
scratchBound: new Int32Array(scratchSize),
|
|
316
348
|
scratchCp: new Int32Array(scratchSize),
|
|
@@ -320,8 +352,17 @@ function createPhase2SearchContext(tables, pairTables, maxDepth, deadline) {
|
|
|
320
352
|
}
|
|
321
353
|
function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, previousFace, previousPreviousFace, remainingDepth, path, context) {
|
|
322
354
|
context.visitedNodes += 1;
|
|
323
|
-
if (context.
|
|
355
|
+
if (context.nodeCap !== undefined &&
|
|
356
|
+
context.visitedNodes > context.nodeCap) {
|
|
357
|
+
context.nodesExceeded = true;
|
|
358
|
+
}
|
|
359
|
+
if (!context.nodesExceeded &&
|
|
360
|
+
context.deadline !== undefined &&
|
|
361
|
+
(context.visitedNodes & 1023) === 0 &&
|
|
362
|
+
performance.now() > context.deadline) {
|
|
324
363
|
context.timedOut = true;
|
|
364
|
+
}
|
|
365
|
+
if (context.timedOut || context.nodesExceeded) {
|
|
325
366
|
return false;
|
|
326
367
|
}
|
|
327
368
|
if (remainingDepth === 0) {
|
|
@@ -412,7 +453,7 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
|
|
|
412
453
|
return true;
|
|
413
454
|
}
|
|
414
455
|
path.pop();
|
|
415
|
-
if (context.timedOut) {
|
|
456
|
+
if (context.timedOut || context.nodesExceeded) {
|
|
416
457
|
return false;
|
|
417
458
|
}
|
|
418
459
|
}
|