@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,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.POLICY_TABLE = exports.ORIENTATION_TABLE = exports.PERMUTATION_TABLE = exports.CENTERS_TABLE = exports.PYRAMINX_MOVES = exports.POLICY_PACKED_ENTRIES = exports.STATE_COUNT = exports.MOVE_COUNT = exports.ORIENTATION_COUNT = exports.CENTERS_COUNT = exports.PERM_360_TO_720 = exports.PERM_720_TO_360 = exports.PERMUTATION_COUNT = exports.PERMUTATION_FULL_COUNT = void 0;
|
|
3
|
+
exports.POLICY_TABLE_EXPANDED = exports.POLICY_TABLE = exports.ORIENTATION_TABLE = exports.PERMUTATION_TABLE = exports.CENTERS_TABLE = exports.PYRAMINX_MOVES = exports.POLICY_PACKED_ENTRIES = exports.STATE_COUNT = exports.MOVE_COUNT = exports.ORIENTATION_COUNT = exports.CENTERS_COUNT = exports.PERM_360_TO_720 = exports.PERM_720_TO_360 = exports.PERMUTATION_COUNT = exports.PERMUTATION_FULL_COUNT = void 0;
|
|
4
4
|
exports.getPermutationIndex = getPermutationIndex;
|
|
5
5
|
exports.getPermutationFromIndex = getPermutationFromIndex;
|
|
6
6
|
exports.getOrientationIndex = getOrientationIndex;
|
|
@@ -289,6 +289,12 @@ exports.CENTERS_TABLE = null;
|
|
|
289
289
|
exports.PERMUTATION_TABLE = null;
|
|
290
290
|
exports.ORIENTATION_TABLE = null;
|
|
291
291
|
exports.POLICY_TABLE = null;
|
|
292
|
+
/**
|
|
293
|
+
* Byte-expanded policy (one optimal move per state, no bit-unpacking in
|
|
294
|
+
* the hot walk). Built at load from the 3-bit packed table: +0.9 MB RAM,
|
|
295
|
+
* zero wire/format change.
|
|
296
|
+
*/
|
|
297
|
+
exports.POLICY_TABLE_EXPANDED = null;
|
|
292
298
|
async function ensureTablesLoaded() {
|
|
293
299
|
if (tablesLoaded)
|
|
294
300
|
return;
|
|
@@ -297,8 +303,18 @@ async function ensureTablesLoaded() {
|
|
|
297
303
|
exports.PERMUTATION_TABLE = tables.permutationTable;
|
|
298
304
|
exports.ORIENTATION_TABLE = tables.orientationTable;
|
|
299
305
|
exports.POLICY_TABLE = tables.policyTable;
|
|
306
|
+
exports.POLICY_TABLE_EXPANDED = expandPolicy3Bit(tables.policyTable, exports.STATE_COUNT);
|
|
300
307
|
tablesLoaded = true;
|
|
301
308
|
}
|
|
309
|
+
function expandPolicy3Bit(packed, count) {
|
|
310
|
+
const expanded = new Uint8Array(count);
|
|
311
|
+
for (let index = 0; index < count; index += 1) {
|
|
312
|
+
const bits = index * 3;
|
|
313
|
+
const byteIndex = bits >> 3;
|
|
314
|
+
expanded[index] = (((packed[byteIndex] | ((packed[byteIndex + 1] ?? 0) << 8)) >> (bits & 7)) & 0x07);
|
|
315
|
+
}
|
|
316
|
+
return expanded;
|
|
317
|
+
}
|
|
302
318
|
function arePyraminxTablesLoaded() {
|
|
303
319
|
return tablesLoaded;
|
|
304
320
|
}
|
|
@@ -328,4 +344,5 @@ function resetTablesForTests() {
|
|
|
328
344
|
exports.PERMUTATION_TABLE = null;
|
|
329
345
|
exports.ORIENTATION_TABLE = null;
|
|
330
346
|
exports.POLICY_TABLE = null;
|
|
347
|
+
exports.POLICY_TABLE_EXPANDED = null;
|
|
331
348
|
}
|
|
@@ -5,8 +5,19 @@
|
|
|
5
5
|
//
|
|
6
6
|
// Node never uses this module: node-loader.ts reads the .bin files directly
|
|
7
7
|
// from the filesystem.
|
|
8
|
+
//
|
|
9
|
+
// Integrity: callers can pass the manifest's expectations — sha256 + byte
|
|
10
|
+
// length (4x4 manifests) or the FNV-1a32 checksums the older manifests ship
|
|
11
|
+
// — and every check runs on the FINAL (decompressed) bytes, i.e. exactly the
|
|
12
|
+
// .bin payload: identical checks to the node loaders. A payload served by
|
|
13
|
+
// the Cache API that fails verification is purged (`cache.delete`) and
|
|
14
|
+
// re-downloaded exactly once, so a corrupt cache entry heals instead of
|
|
15
|
+
// failing every load forever.
|
|
8
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
17
|
exports.fetchTableBuffer = fetchTableBuffer;
|
|
18
|
+
exports.fnv1a32 = fnv1a32;
|
|
19
|
+
exports.sha256Hex = sha256Hex;
|
|
20
|
+
exports.sha256HexJs = sha256HexJs;
|
|
10
21
|
// DecompressionStream only guarantees "gzip"/"deflate"; "br" support varies,
|
|
11
22
|
// so probe once and never attempt formats the runtime cannot decompress.
|
|
12
23
|
const SUPPORTED_FORMATS = (() => {
|
|
@@ -26,46 +37,233 @@ const SUPPORTED_FORMATS = (() => {
|
|
|
26
37
|
})();
|
|
27
38
|
async function fetchTableBuffer(baseUrl, dir, file, options = {}) {
|
|
28
39
|
const urlFor = (name) => `${baseUrl}/${dir}/${name}`;
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
const canonicalUrl = urlFor(file);
|
|
41
|
+
const cache = options.cacheName === undefined ? undefined : makeCacheChannel(options.cacheName);
|
|
42
|
+
// Cache API URLs that served the current download attempt; when the
|
|
43
|
+
// payload fails verification they are purged before the single retry.
|
|
44
|
+
let servedFromCache = [];
|
|
45
|
+
const get = async (url) => {
|
|
46
|
+
if (!cache)
|
|
47
|
+
return fetch(url);
|
|
48
|
+
const { response, fromCache } = await cache.fetch(url);
|
|
49
|
+
if (fromCache)
|
|
50
|
+
servedFromCache.push(url);
|
|
51
|
+
return response;
|
|
52
|
+
};
|
|
53
|
+
const download = async () => {
|
|
54
|
+
servedFromCache = [];
|
|
55
|
+
if (options.compressed !== false && SUPPORTED_FORMATS.length > 0) {
|
|
56
|
+
for (const [suffix, format] of SUPPORTED_FORMATS) {
|
|
57
|
+
try {
|
|
58
|
+
const response = await get(urlFor(`${file}.${suffix}`));
|
|
59
|
+
if (response.ok && response.body) {
|
|
60
|
+
const stream = response.body.pipeThrough(new DecompressionStream(format));
|
|
61
|
+
return await new Response(stream).arrayBuffer();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// try the next candidate
|
|
37
66
|
}
|
|
38
67
|
}
|
|
39
|
-
catch {
|
|
40
|
-
// try the next candidate
|
|
41
|
-
}
|
|
42
68
|
}
|
|
69
|
+
const response = await get(canonicalUrl);
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new Error(`Failed to load table ${canonicalUrl}: ${response.status} ${response.statusText}` +
|
|
72
|
+
(options.expectedBytes !== undefined ? ` (expected ${options.expectedBytes} bytes)` : ""));
|
|
73
|
+
}
|
|
74
|
+
return response.arrayBuffer();
|
|
75
|
+
};
|
|
76
|
+
let buffer = await download();
|
|
77
|
+
try {
|
|
78
|
+
await verifyTableBuffer(buffer, canonicalUrl, options);
|
|
43
79
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
80
|
+
catch (error) {
|
|
81
|
+
// A corrupt Cache API entry fails every load until it is purged: delete
|
|
82
|
+
// the cached copies that served this attempt and re-download exactly
|
|
83
|
+
// ONCE. A second verification failure (or a payload that never came
|
|
84
|
+
// from the cache) propagates unchanged.
|
|
85
|
+
if (!cache || servedFromCache.length === 0)
|
|
86
|
+
throw error;
|
|
87
|
+
await cache.purge(servedFromCache);
|
|
88
|
+
buffer = await download();
|
|
89
|
+
await verifyTableBuffer(buffer, canonicalUrl, options);
|
|
47
90
|
}
|
|
48
|
-
return
|
|
91
|
+
return buffer;
|
|
49
92
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Verify the downloaded payload against the manifest: byte length first
|
|
95
|
+
* (clearer truncation messages), then sha256 / FNV-1a. Runs on the
|
|
96
|
+
* decompressed bytes, so .br/.gz variants and the raw file are checked
|
|
97
|
+
* identically — and identically to the node loaders.
|
|
98
|
+
*/
|
|
99
|
+
async function verifyTableBuffer(buffer, url, options) {
|
|
100
|
+
if (options.expectedBytes !== undefined && buffer.byteLength !== options.expectedBytes) {
|
|
101
|
+
throw new Error(`Table size mismatch: ${url} (expected ${options.expectedBytes} bytes, got ${buffer.byteLength})`);
|
|
102
|
+
}
|
|
103
|
+
if (options.expectedSha256 !== undefined) {
|
|
104
|
+
const actual = await sha256Hex(buffer);
|
|
105
|
+
if (actual !== options.expectedSha256) {
|
|
106
|
+
throw new Error(`Table checksum mismatch: ${url} (expected sha256 ${options.expectedSha256}, got ${actual}, ${buffer.byteLength} bytes)`);
|
|
54
107
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
108
|
+
}
|
|
109
|
+
if (options.expectedFnv1a !== undefined) {
|
|
110
|
+
const spec = options.expectedFnv1a;
|
|
111
|
+
const actual = fnv1a32(buffer, spec.elementSize, spec.signed ?? false);
|
|
112
|
+
if (actual !== spec.value) {
|
|
113
|
+
throw new Error(`Table checksum mismatch: ${url} (expected fnv1a ${spec.value}, got ${actual}, ${buffer.byteLength} bytes)`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* FNV-1a (32-bit) over the elements decoded from a wire buffer: the browser
|
|
119
|
+
* twin of the node loaders' `checksum(values)` (imul multiply, `>>> 0`
|
|
120
|
+
* result). `elementSize` mirrors each loader's decoding of the manifest
|
|
121
|
+
* checksum — see `ExpectedFnv1a`.
|
|
122
|
+
*/
|
|
123
|
+
function fnv1a32(buffer, elementSize, signed = false) {
|
|
124
|
+
if (buffer.byteLength % elementSize !== 0) {
|
|
125
|
+
throw new Error(`FNV-1a input length ${buffer.byteLength} is not a multiple of ${elementSize}-byte elements`);
|
|
126
|
+
}
|
|
127
|
+
let hash = 0x811c9dc5;
|
|
128
|
+
const view = new DataView(buffer);
|
|
129
|
+
const count = buffer.byteLength / elementSize;
|
|
130
|
+
for (let index = 0; index < count; index += 1) {
|
|
131
|
+
let value;
|
|
132
|
+
if (elementSize === 1)
|
|
133
|
+
value = signed ? view.getInt8(index) : view.getUint8(index);
|
|
134
|
+
else if (elementSize === 2)
|
|
135
|
+
value = view.getUint16(index * 2, true);
|
|
136
|
+
else
|
|
137
|
+
value = view.getInt32(index * 4, true);
|
|
138
|
+
hash ^= value;
|
|
139
|
+
hash = Math.imul(hash, 0x01000193);
|
|
140
|
+
}
|
|
141
|
+
return hash >>> 0;
|
|
142
|
+
}
|
|
143
|
+
/** sha256 hex of a buffer; WebCrypto when available, portable JS fallback otherwise. */
|
|
144
|
+
async function sha256Hex(buffer) {
|
|
145
|
+
const subtle = globalThis.crypto?.subtle;
|
|
146
|
+
if (subtle) {
|
|
147
|
+
const digest = await subtle.digest("SHA-256", buffer);
|
|
148
|
+
const bytes = new Uint8Array(digest);
|
|
149
|
+
let hex = "";
|
|
150
|
+
for (let i = 0; i < bytes.length; i++)
|
|
151
|
+
hex += bytes[i].toString(16).padStart(2, "0");
|
|
152
|
+
return hex;
|
|
153
|
+
}
|
|
154
|
+
// Insecure contexts expose no SubtleCrypto; keep integrity checks working
|
|
155
|
+
// with a self-contained implementation instead of skipping verification.
|
|
156
|
+
return sha256HexJs(buffer);
|
|
157
|
+
}
|
|
158
|
+
// FIPS 180-4 SHA-256, used only when `crypto.subtle` is unavailable.
|
|
159
|
+
const SHA256_K = new Uint32Array([
|
|
160
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
161
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
162
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
163
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
164
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
165
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
166
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
167
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
|
168
|
+
]);
|
|
169
|
+
function sha256HexJs(buffer) {
|
|
170
|
+
const bytes = new Uint8Array(buffer);
|
|
171
|
+
const paddedLength = (bytes.length + 72) & ~63;
|
|
172
|
+
const data = new Uint8Array(paddedLength);
|
|
173
|
+
data.set(bytes);
|
|
174
|
+
data[bytes.length] = 0x80;
|
|
175
|
+
const view = new DataView(data.buffer);
|
|
176
|
+
view.setUint32(paddedLength - 8, Math.floor(bytes.length / 0x20000000));
|
|
177
|
+
view.setUint32(paddedLength - 4, (bytes.length << 3) >>> 0);
|
|
178
|
+
let h0 = 0x6a09e667;
|
|
179
|
+
let h1 = 0xbb67ae85;
|
|
180
|
+
let h2 = 0x3c6ef372;
|
|
181
|
+
let h3 = 0xa54ff53a;
|
|
182
|
+
let h4 = 0x510e527f;
|
|
183
|
+
let h5 = 0x9b05688c;
|
|
184
|
+
let h6 = 0x1f83d9ab;
|
|
185
|
+
let h7 = 0x5be0cd19;
|
|
186
|
+
const w = new Int32Array(64);
|
|
187
|
+
for (let offset = 0; offset < paddedLength; offset += 64) {
|
|
188
|
+
for (let i = 0; i < 16; i += 1)
|
|
189
|
+
w[i] = view.getInt32(offset + i * 4);
|
|
190
|
+
for (let i = 16; i < 64; i += 1) {
|
|
191
|
+
const w15 = w[i - 15];
|
|
192
|
+
const w2 = w[i - 2];
|
|
193
|
+
const s0 = ((w15 >>> 7) | (w15 << 25)) ^ ((w15 >>> 18) | (w15 << 14)) ^ (w15 >>> 3);
|
|
194
|
+
const s1 = ((w2 >>> 17) | (w2 << 15)) ^ ((w2 >>> 19) | (w2 << 13)) ^ (w2 >>> 10);
|
|
195
|
+
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) | 0;
|
|
196
|
+
}
|
|
197
|
+
let a = h0;
|
|
198
|
+
let b = h1;
|
|
199
|
+
let c = h2;
|
|
200
|
+
let d = h3;
|
|
201
|
+
let e = h4;
|
|
202
|
+
let f = h5;
|
|
203
|
+
let g = h6;
|
|
204
|
+
let h = h7;
|
|
205
|
+
for (let i = 0; i < 64; i += 1) {
|
|
206
|
+
const s1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
|
|
207
|
+
const ch = (e & f) ^ (~e & g);
|
|
208
|
+
const t1 = (h + s1 + ch + SHA256_K[i] + w[i]) | 0;
|
|
209
|
+
const s0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
|
|
210
|
+
const maj = (a & b) ^ (a & c) ^ (b & c);
|
|
211
|
+
const t2 = (s0 + maj) | 0;
|
|
212
|
+
h = g;
|
|
213
|
+
g = f;
|
|
214
|
+
f = e;
|
|
215
|
+
e = (d + t1) | 0;
|
|
216
|
+
d = c;
|
|
217
|
+
c = b;
|
|
218
|
+
b = a;
|
|
219
|
+
a = (t1 + t2) | 0;
|
|
220
|
+
}
|
|
221
|
+
h0 = (h0 + a) | 0;
|
|
222
|
+
h1 = (h1 + b) | 0;
|
|
223
|
+
h2 = (h2 + c) | 0;
|
|
224
|
+
h3 = (h3 + d) | 0;
|
|
225
|
+
h4 = (h4 + e) | 0;
|
|
226
|
+
h5 = (h5 + f) | 0;
|
|
227
|
+
h6 = (h6 + g) | 0;
|
|
228
|
+
h7 = (h7 + h) | 0;
|
|
229
|
+
}
|
|
230
|
+
return [h0, h1, h2, h3, h4, h5, h6, h7]
|
|
231
|
+
.map((x) => (x >>> 0).toString(16).padStart(8, "0"))
|
|
232
|
+
.join("");
|
|
233
|
+
}
|
|
234
|
+
function makeCacheChannel(cacheName) {
|
|
235
|
+
if (typeof caches === "undefined") {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
async fetch(url) {
|
|
240
|
+
try {
|
|
241
|
+
const cache = await caches.open(cacheName);
|
|
242
|
+
const cached = await cache.match(url);
|
|
243
|
+
if (cached) {
|
|
244
|
+
return { response: cached, fromCache: true };
|
|
245
|
+
}
|
|
246
|
+
const response = await fetch(url);
|
|
247
|
+
if (response.ok) {
|
|
248
|
+
await cache.put(url, response.clone());
|
|
249
|
+
}
|
|
250
|
+
return { response, fromCache: false };
|
|
60
251
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
252
|
+
catch {
|
|
253
|
+
return { response: await fetch(url), fromCache: false };
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
async purge(urls) {
|
|
257
|
+
try {
|
|
258
|
+
const cache = await caches.open(cacheName);
|
|
259
|
+
for (const url of urls) {
|
|
260
|
+
await cache.delete(url);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// Best effort: a failed purge must not mask the verification error
|
|
265
|
+
// that triggered it — the load still fails loudly either way.
|
|
64
266
|
}
|
|
65
|
-
return response;
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
return fetch(url);
|
|
69
267
|
}
|
|
70
268
|
};
|
|
71
269
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadOnce = loadOnce;
|
|
4
|
+
function loadOnce(slot, load) {
|
|
5
|
+
if (!slot.promise) {
|
|
6
|
+
const wrapped = load().catch((error) => {
|
|
7
|
+
if (slot.promise === wrapped)
|
|
8
|
+
slot.promise = undefined;
|
|
9
|
+
throw error;
|
|
10
|
+
});
|
|
11
|
+
slot.promise = wrapped;
|
|
12
|
+
}
|
|
13
|
+
return slot.promise;
|
|
14
|
+
}
|
|
@@ -6,25 +6,44 @@ 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/skewb/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, centersTable, helpers, policy] = await Promise.all([
|
|
23
|
-
fetchUint16Table(resolved, manifest.files.permutationTable, table_metadata_js_1.SKEWB_SOLVER_TABLE_METADATA.permutationTable
|
|
24
|
-
fetchUint16Table(resolved, manifest.files.orientationTable, table_metadata_js_1.SKEWB_SOLVER_TABLE_METADATA.orientationTable
|
|
25
|
-
fetchUint16Table(resolved, manifest.files.centersTable, table_metadata_js_1.SKEWB_SOLVER_TABLE_METADATA.centersTable
|
|
26
|
-
fetchUint8Table(resolved, manifest.files.helpers, table_metadata_js_1.SKEWB_SOLVER_TABLE_METADATA.helpers
|
|
27
|
-
fetchUint8Table(resolved, manifest.files.policyTable, table_metadata_js_1.SKEWB_SOLVER_TABLE_METADATA.policyTable
|
|
42
|
+
fetchUint16Table(resolved, manifest.files.permutationTable, table_metadata_js_1.SKEWB_SOLVER_TABLE_METADATA.permutationTable),
|
|
43
|
+
fetchUint16Table(resolved, manifest.files.orientationTable, table_metadata_js_1.SKEWB_SOLVER_TABLE_METADATA.orientationTable),
|
|
44
|
+
fetchUint16Table(resolved, manifest.files.centersTable, table_metadata_js_1.SKEWB_SOLVER_TABLE_METADATA.centersTable),
|
|
45
|
+
fetchUint8Table(resolved, manifest.files.helpers, table_metadata_js_1.SKEWB_SOLVER_TABLE_METADATA.helpers),
|
|
46
|
+
fetchUint8Table(resolved, manifest.files.policyTable, table_metadata_js_1.SKEWB_SOLVER_TABLE_METADATA.policyTable)
|
|
28
47
|
]);
|
|
29
48
|
const helpersOffset = table_metadata_js_1.SKEWB_SOLVER_TABLE_METADATA.helpers;
|
|
30
49
|
const evenCenterRank = new Uint16Array(helpersOffset.evenCenterRankBytes / 2);
|
|
@@ -70,22 +89,31 @@ async function fetchManifest(baseUrl) {
|
|
|
70
89
|
}
|
|
71
90
|
return await response.json();
|
|
72
91
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Fetch + verify with the SAME checks as node-loader.ts: byte length from
|
|
94
|
+
* the manifest entries, FNV-1a checksum against the table-metadata
|
|
95
|
+
* constants (the values node asserts against).
|
|
96
|
+
*/
|
|
97
|
+
async function fetchUint16Table(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 * 2,
|
|
102
|
+
expectedFnv1a: { value: meta.checksum, elementSize: 2 }
|
|
103
|
+
});
|
|
104
|
+
const values = new Uint16Array(entry.entries);
|
|
79
105
|
const view = new DataView(buffer);
|
|
80
106
|
for (let i = 0; i < values.length; i++) {
|
|
81
107
|
values[i] = view.getUint16(i * 2, true);
|
|
82
108
|
}
|
|
83
109
|
return values;
|
|
84
110
|
}
|
|
85
|
-
async function fetchUint8Table(baseUrl, entry,
|
|
86
|
-
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
111
|
+
async function fetchUint8Table(baseUrl, entry, meta) {
|
|
112
|
+
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
113
|
+
cacheName: CACHE_NAME,
|
|
114
|
+
compressed: entry.compressed !== false,
|
|
115
|
+
expectedBytes: entry.entries,
|
|
116
|
+
expectedFnv1a: { value: meta.checksum, elementSize: 1 }
|
|
117
|
+
});
|
|
90
118
|
return new Uint8Array(buffer);
|
|
91
119
|
}
|
|
@@ -35,7 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.loadGeneratedSkewbSolverTables = loadGeneratedSkewbSolverTables;
|
|
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
|
}
|
|
@@ -48,8 +54,7 @@ async function loadGeneratedSkewbSolverTables() {
|
|
|
48
54
|
"In browser environments, call `initializeScrambler(baseUrl)` " +
|
|
49
55
|
"before generating scrambles.");
|
|
50
56
|
}
|
|
51
|
-
|
|
52
|
-
return cachedTables;
|
|
57
|
+
return (0, load_once_js_1.loadOnce)(nodeTablesSlot, loadNodeTables);
|
|
53
58
|
}
|
|
54
59
|
async function loadNodeTables() {
|
|
55
60
|
const loader = await Promise.resolve().then(() => __importStar(require(
|
|
@@ -43,7 +43,7 @@ function solveWithTables(state) {
|
|
|
43
43
|
return { moves, depth: moves.length };
|
|
44
44
|
}
|
|
45
45
|
function solveWithTablesIndices(state) {
|
|
46
|
-
const policy = tables_js_1.
|
|
46
|
+
const policy = tables_js_1.POLICY_TABLE_EXPANDED;
|
|
47
47
|
const permTable = tables_js_1.PERMUTATION_TABLE;
|
|
48
48
|
const orientTable = tables_js_1.ORIENTATION_TABLE;
|
|
49
49
|
const centersTable = tables_js_1.CENTERS_TABLE;
|
|
@@ -60,7 +60,7 @@ function solveWithTablesIndices(state) {
|
|
|
60
60
|
}
|
|
61
61
|
const moves = [];
|
|
62
62
|
while (rank !== tables_js_1.SOLVED_RANK) {
|
|
63
|
-
const m =
|
|
63
|
+
const m = policy[rank];
|
|
64
64
|
moves.push(m);
|
|
65
65
|
r7 = permTable[r7 * tables_js_1.MOVE_COUNT + m];
|
|
66
66
|
rO = orientTable[rO * tables_js_1.MOVE_COUNT + m];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SOLVED_RANK = exports.ORIENT_UNRANK = exports.CENTER_UNRANK = exports.PERM7_UNRANK = exports.R7_SETS = exports.ORIENT_CLASS = exports.EVEN_CENTER_FULL = exports.EVEN_CENTER_RANK = exports.POLICY_TABLE = exports.CENTERS_TABLE = exports.ORIENTATION_TABLE = exports.PERMUTATION_TABLE = exports.SKEWB_MOVES = exports.POLICY_PACKED_ENTRIES = exports.STATE_COUNT = exports.MOVE_COUNT = exports.PER_SET_SIZE = exports.ORIENT_CLASS_COUNT = exports.EVEN_CENTER_COUNT = exports.CENTER_COUNT = exports.ORIENTATION_COUNT = exports.PERMUTATION_COUNT = void 0;
|
|
3
|
+
exports.SOLVED_RANK = exports.ORIENT_UNRANK = exports.CENTER_UNRANK = exports.PERM7_UNRANK = exports.R7_SETS = exports.ORIENT_CLASS = exports.EVEN_CENTER_FULL = exports.EVEN_CENTER_RANK = exports.POLICY_TABLE_EXPANDED = exports.POLICY_TABLE = exports.CENTERS_TABLE = exports.ORIENTATION_TABLE = exports.PERMUTATION_TABLE = exports.SKEWB_MOVES = exports.POLICY_PACKED_ENTRIES = exports.STATE_COUNT = exports.MOVE_COUNT = exports.PER_SET_SIZE = exports.ORIENT_CLASS_COUNT = exports.EVEN_CENTER_COUNT = exports.CENTER_COUNT = exports.ORIENTATION_COUNT = exports.PERMUTATION_COUNT = void 0;
|
|
4
4
|
exports.getPermutationIndex = getPermutationIndex;
|
|
5
5
|
exports.getPermutationFromIndex = getPermutationFromIndex;
|
|
6
6
|
exports.getOrientationIndex = getOrientationIndex;
|
|
@@ -175,6 +175,12 @@ exports.PERMUTATION_TABLE = null; // r7 * MOVE_COUNT + m
|
|
|
175
175
|
exports.ORIENTATION_TABLE = null; // rO * MOVE_COUNT + m
|
|
176
176
|
exports.CENTERS_TABLE = null; // rC * MOVE_COUNT + m
|
|
177
177
|
exports.POLICY_TABLE = null; // optimal move per rank, 3-bit packed
|
|
178
|
+
/**
|
|
179
|
+
* Byte-expanded policy (one optimal move per rank, no bit-unpacking in
|
|
180
|
+
* the hot walk). Built at load from the 3-bit packed table: +3.1 MB RAM,
|
|
181
|
+
* zero wire/format change.
|
|
182
|
+
*/
|
|
183
|
+
exports.POLICY_TABLE_EXPANDED = null;
|
|
178
184
|
exports.EVEN_CENTER_RANK = null; // rC -> compact 0..359 (65535 = odd)
|
|
179
185
|
exports.EVEN_CENTER_FULL = null; // compact 0..359 -> rC (derived at load)
|
|
180
186
|
exports.ORIENT_CLASS = null; // rO -> class 0..8
|
|
@@ -224,9 +230,19 @@ function assignTables(tables) {
|
|
|
224
230
|
for (let i = 0; i < 7; i++)
|
|
225
231
|
exports.ORIENT_UNRANK[rO * 7 + i] = orient[i + 1];
|
|
226
232
|
}
|
|
233
|
+
exports.POLICY_TABLE_EXPANDED = expandPolicy3Bit(tables.policyTable, exports.STATE_COUNT);
|
|
227
234
|
exports.SOLVED_RANK = rankFromComponents(0, 0, 0);
|
|
228
235
|
tablesLoaded = true;
|
|
229
236
|
}
|
|
237
|
+
function expandPolicy3Bit(packed, count) {
|
|
238
|
+
const expanded = new Uint8Array(count);
|
|
239
|
+
for (let index = 0; index < count; index += 1) {
|
|
240
|
+
const bits = index * 3;
|
|
241
|
+
const byteIndex = bits >> 3;
|
|
242
|
+
expanded[index] = (((packed[byteIndex] | ((packed[byteIndex + 1] ?? 0) << 8)) >> (bits & 7)) & 0x07);
|
|
243
|
+
}
|
|
244
|
+
return expanded;
|
|
245
|
+
}
|
|
230
246
|
function resetTablesForTests() {
|
|
231
247
|
tablesLoaded = false;
|
|
232
248
|
exports.PERMUTATION_TABLE = null;
|
|
@@ -240,6 +256,7 @@ function resetTablesForTests() {
|
|
|
240
256
|
exports.PERM7_UNRANK = null;
|
|
241
257
|
exports.CENTER_UNRANK = null;
|
|
242
258
|
exports.ORIENT_UNRANK = null;
|
|
259
|
+
exports.POLICY_TABLE_EXPANDED = null;
|
|
243
260
|
}
|
|
244
261
|
// ---------------------------------------------------------------------------
|
|
245
262
|
// Compact rank helpers
|
|
@@ -6,29 +6,48 @@ 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/sq1/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 [shapeIdx, shapePrun, shapeTopMove, shapeBottomMove, shapeTwistMove, squareTwistMove, squareTopMove, squareBottomMove, squarePrun] = await Promise.all([
|
|
23
|
-
fetchInt32Table(resolved, manifest.files.shapeIdx, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.shapeIdx
|
|
24
|
-
fetchInt8Table(resolved, manifest.files.shapePrun, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.shapePrun
|
|
25
|
-
fetchInt32Table(resolved, manifest.files.shapeTopMove, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.shapeTopMove
|
|
26
|
-
fetchInt32Table(resolved, manifest.files.shapeBottomMove, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.shapeBottomMove
|
|
27
|
-
fetchInt32Table(resolved, manifest.files.shapeTwistMove, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.shapeTwistMove
|
|
28
|
-
fetchUint16Table(resolved, manifest.files.squareTwistMove, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.squareTwistMove
|
|
29
|
-
fetchUint16Table(resolved, manifest.files.squareTopMove, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.squareTopMove
|
|
30
|
-
fetchUint16Table(resolved, manifest.files.squareBottomMove, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.squareBottomMove
|
|
31
|
-
fetchInt8Table(resolved, manifest.files.squarePrun, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.squarePrun
|
|
42
|
+
fetchInt32Table(resolved, manifest.files.shapeIdx, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.shapeIdx),
|
|
43
|
+
fetchInt8Table(resolved, manifest.files.shapePrun, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.shapePrun),
|
|
44
|
+
fetchInt32Table(resolved, manifest.files.shapeTopMove, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.shapeTopMove),
|
|
45
|
+
fetchInt32Table(resolved, manifest.files.shapeBottomMove, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.shapeBottomMove),
|
|
46
|
+
fetchInt32Table(resolved, manifest.files.shapeTwistMove, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.shapeTwistMove),
|
|
47
|
+
fetchUint16Table(resolved, manifest.files.squareTwistMove, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.squareTwistMove),
|
|
48
|
+
fetchUint16Table(resolved, manifest.files.squareTopMove, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.squareTopMove),
|
|
49
|
+
fetchUint16Table(resolved, manifest.files.squareBottomMove, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.squareBottomMove),
|
|
50
|
+
fetchInt8Table(resolved, manifest.files.squarePrun, table_metadata_js_1.SQ1_SOLVER_TABLE_METADATA.squarePrun),
|
|
32
51
|
]);
|
|
33
52
|
cachedBrowserTables = {
|
|
34
53
|
shapeIdx,
|
|
@@ -64,34 +83,47 @@ async function fetchManifest(baseUrl) {
|
|
|
64
83
|
}
|
|
65
84
|
return (await response.json());
|
|
66
85
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Fetch + verify with the SAME checks as node-loader.ts: byte length from
|
|
88
|
+
* the manifest entries, FNV-1a checksum against the table-metadata
|
|
89
|
+
* constants (node skips the checksum when that constant is 0 = "unchecked";
|
|
90
|
+
* the browser mirrors the guard).
|
|
91
|
+
*/
|
|
92
|
+
async function fetchInt32Table(baseUrl, entry, meta) {
|
|
93
|
+
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
94
|
+
cacheName: CACHE_NAME,
|
|
95
|
+
compressed: entry.compressed !== false,
|
|
96
|
+
expectedBytes: entry.entries * 4,
|
|
97
|
+
...(meta.checksum !== 0 ? { expectedFnv1a: { value: meta.checksum, elementSize: 4 } } : {})
|
|
98
|
+
});
|
|
99
|
+
const values = new Int32Array(entry.entries);
|
|
73
100
|
const view = new DataView(buffer);
|
|
74
101
|
for (let i = 0; i < values.length; i++) {
|
|
75
102
|
values[i] = view.getInt32(i * 4, true);
|
|
76
103
|
}
|
|
77
104
|
return values;
|
|
78
105
|
}
|
|
79
|
-
async function fetchUint16Table(baseUrl, entry,
|
|
80
|
-
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
106
|
+
async function fetchUint16Table(baseUrl, entry, meta) {
|
|
107
|
+
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
108
|
+
cacheName: CACHE_NAME,
|
|
109
|
+
compressed: entry.compressed !== false,
|
|
110
|
+
expectedBytes: entry.entries * 2,
|
|
111
|
+
...(meta.checksum !== 0 ? { expectedFnv1a: { value: meta.checksum, elementSize: 2 } } : {})
|
|
112
|
+
});
|
|
113
|
+
const values = new Uint16Array(entry.entries);
|
|
85
114
|
const view = new DataView(buffer);
|
|
86
115
|
for (let i = 0; i < values.length; i++) {
|
|
87
116
|
values[i] = view.getUint16(i * 2, true);
|
|
88
117
|
}
|
|
89
118
|
return values;
|
|
90
119
|
}
|
|
91
|
-
async function fetchInt8Table(baseUrl, entry,
|
|
92
|
-
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
120
|
+
async function fetchInt8Table(baseUrl, entry, meta) {
|
|
121
|
+
const buffer = await (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, TABLES_DIR, entry.file, {
|
|
122
|
+
cacheName: CACHE_NAME,
|
|
123
|
+
compressed: entry.compressed !== false,
|
|
124
|
+
expectedBytes: entry.entries,
|
|
125
|
+
// Square-1's int8 tables hash SIGNED values in node (Int8Array).
|
|
126
|
+
...(meta.checksum !== 0 ? { expectedFnv1a: { value: meta.checksum, elementSize: 1, signed: true } } : {})
|
|
127
|
+
});
|
|
96
128
|
return new Int8Array(buffer);
|
|
97
129
|
}
|