@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
|
@@ -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(
|
|
@@ -37,7 +37,7 @@ function solveWithTablesIndices(state) {
|
|
|
37
37
|
}
|
|
38
38
|
const permTable = tables_js_1.PERMUTATION_TABLE;
|
|
39
39
|
const orientTable = tables_js_1.ORIENTATION_TABLE;
|
|
40
|
-
const policy = tables_js_1.
|
|
40
|
+
const policy = tables_js_1.POLICY_TABLE_EXPANDED;
|
|
41
41
|
let permIndex = (0, tables_js_1.encodePermutation)(state.corners.permutation);
|
|
42
42
|
let orientIndex = (0, tables_js_1.encodeOrientation)(state.corners.orientation);
|
|
43
43
|
const moves = [];
|
|
@@ -45,7 +45,7 @@ function solveWithTablesIndices(state) {
|
|
|
45
45
|
// candidate scanning (the old BFS-distance gradient walk) is needed.
|
|
46
46
|
let combined = permIndex * tables_js_1.ORIENTATION_COUNT + orientIndex;
|
|
47
47
|
while (combined !== 0) {
|
|
48
|
-
const m =
|
|
48
|
+
const m = policy[combined];
|
|
49
49
|
moves.push(m);
|
|
50
50
|
permIndex = permTable[permIndex * tables_js_1.MOVE_COUNT + m];
|
|
51
51
|
orientIndex = orientTable[orientIndex * tables_js_1.MOVE_COUNT + m];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BFS_MOVES = exports.POLICY_TABLE = exports.ORIENTATION_TABLE = exports.PERMUTATION_TABLE = exports.MOVE_COUNT = exports.ORIENTATION_COUNT = exports.PERMUTATION_COUNT = void 0;
|
|
3
|
+
exports.BFS_MOVES = exports.POLICY_TABLE_EXPANDED = exports.POLICY_TABLE = exports.ORIENTATION_TABLE = exports.PERMUTATION_TABLE = exports.MOVE_COUNT = exports.ORIENTATION_COUNT = exports.PERMUTATION_COUNT = void 0;
|
|
4
4
|
exports.encodePermutation = encodePermutation;
|
|
5
5
|
exports.encodeOrientation = encodeOrientation;
|
|
6
6
|
exports.ensureTablesLoaded = ensureTablesLoaded;
|
|
@@ -64,6 +64,12 @@ let tablesLoaded = false;
|
|
|
64
64
|
exports.PERMUTATION_TABLE = null;
|
|
65
65
|
exports.ORIENTATION_TABLE = null;
|
|
66
66
|
exports.POLICY_TABLE = null;
|
|
67
|
+
/**
|
|
68
|
+
* Byte-expanded policy (one optimal move per state, no bit-unpacking in
|
|
69
|
+
* the hot walk). Built at load from the packed table: +3.7 MB RAM,
|
|
70
|
+
* zero wire/format change.
|
|
71
|
+
*/
|
|
72
|
+
exports.POLICY_TABLE_EXPANDED = null;
|
|
67
73
|
async function ensureTablesLoaded() {
|
|
68
74
|
if (tablesLoaded) {
|
|
69
75
|
return;
|
|
@@ -72,8 +78,16 @@ async function ensureTablesLoaded() {
|
|
|
72
78
|
exports.PERMUTATION_TABLE = tables.permutationTable;
|
|
73
79
|
exports.ORIENTATION_TABLE = tables.orientationTable;
|
|
74
80
|
exports.POLICY_TABLE = tables.policyTable;
|
|
81
|
+
exports.POLICY_TABLE_EXPANDED = expandNibblePolicy(tables.policyTable, exports.PERMUTATION_COUNT * exports.ORIENTATION_COUNT);
|
|
75
82
|
tablesLoaded = true;
|
|
76
83
|
}
|
|
84
|
+
function expandNibblePolicy(packed, count) {
|
|
85
|
+
const expanded = new Uint8Array(count);
|
|
86
|
+
for (let index = 0; index < count; index += 1) {
|
|
87
|
+
expanded[index] = (packed[index >> 1] >> ((index & 1) << 2)) & 0x0f;
|
|
88
|
+
}
|
|
89
|
+
return expanded;
|
|
90
|
+
}
|
|
77
91
|
function areCube2TablesLoaded() {
|
|
78
92
|
return tablesLoaded;
|
|
79
93
|
}
|
|
@@ -81,7 +95,7 @@ function areCube2TablesLoaded() {
|
|
|
81
95
|
// Public API
|
|
82
96
|
// ---------------------------------------------------------------------------
|
|
83
97
|
function getDistance(permIndex, orientIndex) {
|
|
84
|
-
if (!exports.
|
|
98
|
+
if (!exports.POLICY_TABLE_EXPANDED) {
|
|
85
99
|
throw new Error("Cube2 solver tables not loaded. Call ensureTablesLoaded() first.");
|
|
86
100
|
}
|
|
87
101
|
const permTable = exports.PERMUTATION_TABLE;
|
|
@@ -91,7 +105,7 @@ function getDistance(permIndex, orientIndex) {
|
|
|
91
105
|
let combined = p * exports.ORIENTATION_COUNT + o;
|
|
92
106
|
let distance = 0;
|
|
93
107
|
while (combined !== 0) {
|
|
94
|
-
const m =
|
|
108
|
+
const m = exports.POLICY_TABLE_EXPANDED[combined];
|
|
95
109
|
p = permTable[p * exports.MOVE_COUNT + m];
|
|
96
110
|
o = orientTable[o * exports.MOVE_COUNT + m];
|
|
97
111
|
combined = p * exports.ORIENTATION_COUNT + o;
|
|
@@ -35,16 +35,114 @@ const FACE_MOVE_TABLES = {
|
|
|
35
35
|
edges: { permutation: [0, 1, 2, 11, 4, 5, 6, 10, 8, 9, 3, 7], orientation: [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1] }
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
|
-
function
|
|
38
|
+
function quarterEffect(face) {
|
|
39
|
+
const table = FACE_MOVE_TABLES[face];
|
|
40
|
+
return {
|
|
41
|
+
cornerPermutation: table.corners.permutation,
|
|
42
|
+
cornerOrientation: table.corners.orientation,
|
|
43
|
+
edgePermutation: table.edges.permutation,
|
|
44
|
+
edgeOrientation: table.edges.orientation
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Compose two effects (apply `first`, then `second`) into one so a move of
|
|
48
|
+
// any suffix costs exactly one pass over the state — no per-quarter-turn
|
|
49
|
+
// cloning, no intermediate states, no closure allocations.
|
|
50
|
+
function composeEffects(first, second) {
|
|
51
|
+
const cornerPermutation = new Array(8);
|
|
52
|
+
const cornerOrientation = new Array(8);
|
|
53
|
+
const edgePermutation = new Array(12);
|
|
54
|
+
const edgeOrientation = new Array(12);
|
|
55
|
+
for (let i = 0; i < 8; i++) {
|
|
56
|
+
const source = second.cornerPermutation[i];
|
|
57
|
+
cornerPermutation[i] = first.cornerPermutation[source];
|
|
58
|
+
cornerOrientation[i] = (first.cornerOrientation[source] + second.cornerOrientation[i]) % 3;
|
|
59
|
+
}
|
|
60
|
+
for (let i = 0; i < 12; i++) {
|
|
61
|
+
const source = second.edgePermutation[i];
|
|
62
|
+
edgePermutation[i] = first.edgePermutation[source];
|
|
63
|
+
edgeOrientation[i] = (first.edgeOrientation[source] + second.edgeOrientation[i]) % 2;
|
|
64
|
+
}
|
|
65
|
+
return { cornerPermutation, cornerOrientation, edgePermutation, edgeOrientation };
|
|
66
|
+
}
|
|
67
|
+
function powerEffect(face, turns) {
|
|
68
|
+
const quarter = quarterEffect(face);
|
|
69
|
+
if (turns === 1)
|
|
70
|
+
return quarter;
|
|
71
|
+
const half = composeEffects(quarter, quarter);
|
|
72
|
+
if (turns === 2)
|
|
73
|
+
return half;
|
|
74
|
+
return composeEffects(half, quarter);
|
|
75
|
+
}
|
|
76
|
+
// Total effect per move string, built once at module load (mirrors the 4x4
|
|
77
|
+
// fast path): one lookup + one pass per move.
|
|
78
|
+
const COMPOSED_EFFECTS = Object.fromEntries(types_js_1.CUBE3_MOVES.map((move) => {
|
|
79
|
+
const face = move[0];
|
|
80
|
+
const suffix = move.slice(1);
|
|
81
|
+
const turns = suffix === "2" ? 2 : suffix === "'" ? 3 : 1;
|
|
82
|
+
return [move, powerEffect(face, turns)];
|
|
83
|
+
}));
|
|
84
|
+
function effectForMove(move) {
|
|
85
|
+
const hit = COMPOSED_EFFECTS[move];
|
|
86
|
+
if (hit !== undefined)
|
|
87
|
+
return hit;
|
|
39
88
|
const { face, turns } = parseMoveParts(move);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
89
|
+
return powerEffect(face, turns);
|
|
90
|
+
}
|
|
91
|
+
function applyCube3Move(state, move) {
|
|
92
|
+
const effect = effectForMove(move);
|
|
93
|
+
const cornerPermutation = new Array(8);
|
|
94
|
+
const cornerOrientation = new Array(8);
|
|
95
|
+
const edgePermutation = new Array(12);
|
|
96
|
+
const edgeOrientation = new Array(12);
|
|
97
|
+
for (let i = 0; i < 8; i++) {
|
|
98
|
+
const source = effect.cornerPermutation[i];
|
|
99
|
+
cornerPermutation[i] = state.corners.permutation[source];
|
|
100
|
+
cornerOrientation[i] =
|
|
101
|
+
(state.corners.orientation[source] + effect.cornerOrientation[i]) % 3;
|
|
43
102
|
}
|
|
44
|
-
|
|
103
|
+
for (let i = 0; i < 12; i++) {
|
|
104
|
+
const source = effect.edgePermutation[i];
|
|
105
|
+
edgePermutation[i] = state.edges.permutation[source];
|
|
106
|
+
edgeOrientation[i] =
|
|
107
|
+
(state.edges.orientation[source] + effect.edgeOrientation[i]) % 2;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
corners: { permutation: cornerPermutation, orientation: cornerOrientation },
|
|
111
|
+
edges: { permutation: edgePermutation, orientation: edgeOrientation }
|
|
112
|
+
};
|
|
45
113
|
}
|
|
46
114
|
function applyCube3Algorithm(state, moves) {
|
|
47
|
-
|
|
115
|
+
if (moves.length === 0)
|
|
116
|
+
return (0, state_js_1.cloneCube3State)(state);
|
|
117
|
+
let curCp = [...state.corners.permutation];
|
|
118
|
+
let curCo = [...state.corners.orientation];
|
|
119
|
+
let curEp = [...state.edges.permutation];
|
|
120
|
+
let curEo = [...state.edges.orientation];
|
|
121
|
+
let nxtCp = new Array(8);
|
|
122
|
+
let nxtCo = new Array(8);
|
|
123
|
+
let nxtEp = new Array(12);
|
|
124
|
+
let nxtEo = new Array(12);
|
|
125
|
+
for (const move of moves) {
|
|
126
|
+
const effect = effectForMove(move);
|
|
127
|
+
for (let i = 0; i < 8; i++) {
|
|
128
|
+
const source = effect.cornerPermutation[i];
|
|
129
|
+
nxtCp[i] = curCp[source];
|
|
130
|
+
nxtCo[i] = (curCo[source] + effect.cornerOrientation[i]) % 3;
|
|
131
|
+
}
|
|
132
|
+
for (let i = 0; i < 12; i++) {
|
|
133
|
+
const source = effect.edgePermutation[i];
|
|
134
|
+
nxtEp[i] = curEp[source];
|
|
135
|
+
nxtEo[i] = (curEo[source] + effect.edgeOrientation[i]) % 2;
|
|
136
|
+
}
|
|
137
|
+
[curCp, nxtCp] = [nxtCp, curCp];
|
|
138
|
+
[curCo, nxtCo] = [nxtCo, curCo];
|
|
139
|
+
[curEp, nxtEp] = [nxtEp, curEp];
|
|
140
|
+
[curEo, nxtEo] = [nxtEo, curEo];
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
corners: { permutation: curCp, orientation: curCo },
|
|
144
|
+
edges: { permutation: curEp, orientation: curEo }
|
|
145
|
+
};
|
|
48
146
|
}
|
|
49
147
|
function invertCube3Algorithm(moves) {
|
|
50
148
|
return [...moves].reverse().map(invertCube3Move);
|
|
@@ -199,18 +297,6 @@ function invertCube3Move(move) {
|
|
|
199
297
|
}
|
|
200
298
|
return `${face}${suffix === "'" ? "" : "'"}`;
|
|
201
299
|
}
|
|
202
|
-
function applyFaceMove(state, table) {
|
|
203
|
-
return {
|
|
204
|
-
corners: {
|
|
205
|
-
permutation: table.corners.permutation.map((source) => state.corners.permutation[source]),
|
|
206
|
-
orientation: table.corners.permutation.map((source, target) => ((state.corners.orientation[source] + table.corners.orientation[target]) % 3))
|
|
207
|
-
},
|
|
208
|
-
edges: {
|
|
209
|
-
permutation: table.edges.permutation.map((source) => state.edges.permutation[source]),
|
|
210
|
-
orientation: table.edges.permutation.map((source, target) => ((state.edges.orientation[source] + table.edges.orientation[target]) % 2))
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
300
|
function parseMoveParts(move) {
|
|
215
301
|
const face = move[0];
|
|
216
302
|
const suffix = move.slice(1);
|
|
@@ -22,7 +22,7 @@ exports.cube3ScrambleGenerator = {
|
|
|
22
22
|
};
|
|
23
23
|
async function generateCube3Scramble(options = {}) {
|
|
24
24
|
const state = (0, random_state_js_1.generateRandomCube3State)(options);
|
|
25
|
-
const solution = await (0, index_js_1.solveCube3State)(state);
|
|
25
|
+
const solution = await (0, index_js_1.solveCube3State)(state, options.solver ?? {});
|
|
26
26
|
const scrambleMoves = (0, moves_js_1.invertCube3Algorithm)(solution.moves);
|
|
27
27
|
if (!options.skipValidation) {
|
|
28
28
|
const scrambledState = (0, moves_js_1.applyCube3Algorithm)((0, state_js_1.createSolvedCube3State)(), scrambleMoves);
|
|
@@ -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,15 +109,23 @@ async function fetchManifest(baseUrl) {
|
|
|
95
109
|
return manifest;
|
|
96
110
|
}
|
|
97
111
|
async function fetchMoveTable(baseUrl, entry) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
+
});
|
|
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) {
|
|
106
|
-
|
|
124
|
+
const value = view.getInt32(index * 4, true);
|
|
125
|
+
if (value < 0 || value > 0xffff) {
|
|
126
|
+
throw new Error(`Move table value out of range for ${entry.coordinate}: ${value}.`);
|
|
127
|
+
}
|
|
128
|
+
values[index] = value;
|
|
107
129
|
}
|
|
108
130
|
return {
|
|
109
131
|
coordinate: entry.coordinate,
|
|
@@ -115,11 +137,15 @@ async function fetchMoveTable(baseUrl, entry) {
|
|
|
115
137
|
};
|
|
116
138
|
}
|
|
117
139
|
async function fetchPruningTable(baseUrl, entry) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
+
});
|
|
123
149
|
const values = new Uint8Array(buffer);
|
|
124
150
|
return {
|
|
125
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(
|