@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,22 +1,74 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Browser loader for the cube4 reduction tables (the committed .bin files).
|
|
4
|
-
* The manifests are per-phase (`center1-manifest.json`, ...)
|
|
4
|
+
* The manifests are per-phase (`center1-manifest.json`, ...) plus the shared
|
|
5
|
+
* `hc-manifest.json`, which also seals the optional `c3_pair_dist.bin`.
|
|
6
|
+
*
|
|
7
|
+
* Integrity — identical to node-loader.ts: every downloaded .bin is verified
|
|
8
|
+
* against the manifest sha256 + expected byte length (on the DECOMPRESSED
|
|
9
|
+
* payload, so .br/.gz variants and the raw file are checked the same way).
|
|
10
|
+
*
|
|
11
|
+
* Optional tables (`hc` + `c3PairDist`), NEVER silent:
|
|
12
|
+
* - `hc-manifest.json` present → its entries must download and verify, else
|
|
13
|
+
* the whole load REJECTS (node throws too). There is no silent degradation
|
|
14
|
+
* to an unpruned search: it would change the scramble produced for the
|
|
15
|
+
* same seed, breaking node/browser determinism.
|
|
16
|
+
* - `hc-manifest.json` absent (404) → the optional tables were not deployed;
|
|
17
|
+
* `c3_pair_dist.bin` is then probed to tell "not deployed" (both absent →
|
|
18
|
+
* skip, like node) apart from "stale/half-deployed assets" (bin present,
|
|
19
|
+
* manifest missing → REJECT with an actionable message — node refuses the
|
|
20
|
+
* same asset set).
|
|
21
|
+
* There is deliberately no "degraded" flag: the only valid outcomes are
|
|
22
|
+
* "fully loaded tables" or "loud failure", so both platforms always compute
|
|
23
|
+
* the same scrambles.
|
|
24
|
+
*
|
|
25
|
+
* Caching: concurrent loads share ONE in-flight promise (one fetch pass per
|
|
26
|
+
* table). The promise is kept after success; when it rejects it is purged so
|
|
27
|
+
* the next call can retry instead of being stuck on a poisoned cache.
|
|
5
28
|
*/
|
|
6
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
30
|
exports.getCachedCube4Tables = getCachedCube4Tables;
|
|
8
31
|
exports.clearCachedCube4Tables = clearCachedCube4Tables;
|
|
9
32
|
exports.loadCube4ReductionTablesBrowser = loadCube4ReductionTablesBrowser;
|
|
33
|
+
const center3_js_1 = require("./center3.js");
|
|
10
34
|
const edge3_js_1 = require("./edge3.js");
|
|
11
35
|
const compressed_fetch_js_1 = require("../../shared/compressed-fetch.js");
|
|
36
|
+
const hc_pdb_js_1 = require("./hc-pdb.js");
|
|
12
37
|
const GENERATED_DIRECTORY = "puzzles/cube4/reduction/generated";
|
|
13
38
|
const CACHE_NAME = "cubingcenter-scrambler-tables-v1";
|
|
39
|
+
const TABLE_MEMORY_BUDGET_BYTES = 150 * 1024 * 1024;
|
|
40
|
+
/**
|
|
41
|
+
* Byte length of `c3_pair_dist.bin`: CENTER3_STATES x 144 nibble entries
|
|
42
|
+
* (index = ct3 * 144 + f * 12 + h, see phase3-search.ts), nibble-packed.
|
|
43
|
+
* Kept in sync with node-loader.ts.
|
|
44
|
+
*/
|
|
45
|
+
const C3_PAIR_DIST_BYTES = (center3_js_1.CENTER3_STATES * 144) / 2;
|
|
14
46
|
let cachedTables;
|
|
47
|
+
let cachedTablesPromise;
|
|
48
|
+
function assertCube4ReductionTableBudget(tables) {
|
|
49
|
+
const bytes = tables.center1.distance.byteLength +
|
|
50
|
+
tables.center1.moveMaps.byteLength +
|
|
51
|
+
tables.center2.ctMove.byteLength +
|
|
52
|
+
tables.center2.rlMove.byteLength +
|
|
53
|
+
tables.center2.pruning.byteLength +
|
|
54
|
+
(tables.center2.pruningWide?.byteLength ?? 0) +
|
|
55
|
+
tables.center3.moveTable.byteLength +
|
|
56
|
+
tables.center3.pruning.byteLength +
|
|
57
|
+
tables.edge3.distances.byteLength +
|
|
58
|
+
(tables.hc?.moves.byteLength ?? 0) +
|
|
59
|
+
(tables.hc?.distances.byteLength ?? 0) +
|
|
60
|
+
(tables.hc?.coordinates.byteLength ?? 0) +
|
|
61
|
+
(tables.c3PairDist?.byteLength ?? 0);
|
|
62
|
+
if (bytes > TABLE_MEMORY_BUDGET_BYTES) {
|
|
63
|
+
throw new Error(`Cube4 reduction tables use ${(bytes / (1024 * 1024)).toFixed(1)} MiB, above the 150 MiB table budget.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
15
66
|
function getCachedCube4Tables() {
|
|
16
67
|
return cachedTables;
|
|
17
68
|
}
|
|
18
69
|
function clearCachedCube4Tables() {
|
|
19
70
|
cachedTables = undefined;
|
|
71
|
+
cachedTablesPromise = undefined;
|
|
20
72
|
}
|
|
21
73
|
function resolveBaseUrl(provided) {
|
|
22
74
|
if (provided) {
|
|
@@ -31,33 +83,95 @@ function resolveBaseUrl(provided) {
|
|
|
31
83
|
}
|
|
32
84
|
return ".";
|
|
33
85
|
}
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
|
|
86
|
+
function loadCube4ReductionTablesBrowser(baseUrl) {
|
|
87
|
+
if (cachedTablesPromise) {
|
|
88
|
+
// One shared in-flight promise: concurrent calls trigger exactly one
|
|
89
|
+
// fetch pass (dedup), and the resolved value stays cached afterwards.
|
|
90
|
+
return cachedTablesPromise;
|
|
37
91
|
}
|
|
38
92
|
const resolved = resolveBaseUrl(baseUrl);
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
93
|
+
const promise = (async () => {
|
|
94
|
+
const [center1, center2, center3, edge3, optional] = await Promise.all([
|
|
95
|
+
loadCenter1(resolved),
|
|
96
|
+
loadCenter2(resolved),
|
|
97
|
+
loadCenter3(resolved),
|
|
98
|
+
loadEdge3(resolved),
|
|
99
|
+
loadOptionalTables(resolved)
|
|
100
|
+
]);
|
|
101
|
+
const tables = { center1, center2, center3, edge3 };
|
|
102
|
+
if (optional.hc)
|
|
103
|
+
tables.hc = optional.hc;
|
|
104
|
+
if (optional.c3PairDist)
|
|
105
|
+
tables.c3PairDist = optional.c3PairDist;
|
|
106
|
+
assertCube4ReductionTableBudget(tables);
|
|
107
|
+
return tables;
|
|
108
|
+
})();
|
|
109
|
+
cachedTablesPromise = promise;
|
|
110
|
+
promise.then((value) => {
|
|
111
|
+
cachedTables = value;
|
|
112
|
+
}, () => {
|
|
113
|
+
// A failed load must not poison the cache: purge the rejected promise
|
|
114
|
+
// so a later call retries from scratch.
|
|
115
|
+
if (cachedTablesPromise === promise)
|
|
116
|
+
cachedTablesPromise = undefined;
|
|
117
|
+
});
|
|
118
|
+
return promise;
|
|
119
|
+
}
|
|
120
|
+
function requireEntry(entry, manifestUrl, label) {
|
|
121
|
+
if (!entry || typeof entry.file !== "string" || typeof entry.checksum !== "string") {
|
|
122
|
+
throw new Error(`Invalid table manifest ${manifestUrl}: entry "${label}" must declare { file, checksum } — ` +
|
|
123
|
+
"the deployed assets are stale; sync them with pnpm tables:web:sync.");
|
|
124
|
+
}
|
|
125
|
+
return entry;
|
|
47
126
|
}
|
|
48
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Expected byte length of a table file from its manifest entry.
|
|
129
|
+
* `packing` matches how each table is serialized — keep in sync with
|
|
130
|
+
* node-loader.ts.
|
|
131
|
+
*/
|
|
132
|
+
function expectedBytes(entry, packing) {
|
|
133
|
+
if (typeof entry.bytes === "number")
|
|
134
|
+
return entry.bytes;
|
|
135
|
+
if (typeof entry.range !== "number")
|
|
136
|
+
return undefined;
|
|
137
|
+
if (packing === "byte")
|
|
138
|
+
return entry.range;
|
|
139
|
+
if (packing === "nibble")
|
|
140
|
+
return Math.ceil(entry.range / 2);
|
|
141
|
+
return entry.range * 4;
|
|
142
|
+
}
|
|
143
|
+
async function fetchVerified(baseUrl, entry, packing, expectedLength) {
|
|
144
|
+
const length = expectedLength ?? expectedBytes(entry, packing);
|
|
145
|
+
return (0, compressed_fetch_js_1.fetchTableBuffer)(baseUrl, GENERATED_DIRECTORY, entry.file, {
|
|
146
|
+
cacheName: CACHE_NAME,
|
|
147
|
+
compressed: entry.compressed !== false,
|
|
148
|
+
expectedSha256: entry.checksum,
|
|
149
|
+
...(length !== undefined ? { expectedBytes: length } : {})
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
async function fetchManifest(baseUrl, file) {
|
|
49
153
|
const url = `${baseUrl}/${GENERATED_DIRECTORY}/${file}`;
|
|
50
154
|
const response = await fetch(url);
|
|
51
155
|
if (!response.ok) {
|
|
52
|
-
throw new Error(`Failed to load ${
|
|
156
|
+
throw new Error(`Failed to load table manifest ${url}: ${response.status} ${response.statusText}`);
|
|
157
|
+
}
|
|
158
|
+
const text = await response.text();
|
|
159
|
+
try {
|
|
160
|
+
return JSON.parse(text);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
// Always name the URL: a bare JSON.parse error is unactionable.
|
|
164
|
+
throw new Error(`Invalid table manifest JSON: ${url}: ${error instanceof Error ? error.message : String(error)}`);
|
|
53
165
|
}
|
|
54
|
-
return response.json();
|
|
55
166
|
}
|
|
56
167
|
async function loadCenter1(baseUrl) {
|
|
57
|
-
const
|
|
168
|
+
const manifestUrl = `${baseUrl}/${GENERATED_DIRECTORY}/center1-manifest.json`;
|
|
169
|
+
const manifest = await fetchManifest(baseUrl, "center1-manifest.json");
|
|
170
|
+
const distanceTable = requireEntry(manifest.distanceTable, manifestUrl, "distanceTable");
|
|
171
|
+
const moveMaps = requireEntry(manifest.moveMaps, manifestUrl, "moveMaps");
|
|
58
172
|
const [distanceBuffer, moveMapsBuffer] = await Promise.all([
|
|
59
|
-
(
|
|
60
|
-
(
|
|
173
|
+
fetchVerified(baseUrl, distanceTable, "nibble"),
|
|
174
|
+
fetchVerified(baseUrl, moveMaps, "int32")
|
|
61
175
|
]);
|
|
62
176
|
return {
|
|
63
177
|
distance: new Uint8Array(distanceBuffer),
|
|
@@ -65,27 +179,48 @@ async function loadCenter1(baseUrl) {
|
|
|
65
179
|
};
|
|
66
180
|
}
|
|
67
181
|
async function loadCenter2(baseUrl) {
|
|
68
|
-
const
|
|
182
|
+
const manifestUrl = `${baseUrl}/${GENERATED_DIRECTORY}/center2-manifest.json`;
|
|
183
|
+
const manifest = await fetchManifest(baseUrl, "center2-manifest.json");
|
|
184
|
+
const ctMove = requireEntry(manifest.ctMove, manifestUrl, "ctMove");
|
|
185
|
+
const rlMove = requireEntry(manifest.rlMove, manifestUrl, "rlMove");
|
|
186
|
+
const pruningTable = requireEntry(manifest.pruningTable, manifestUrl, "pruningTable");
|
|
187
|
+
const pruningWideEntry = manifest.pruningWideTable
|
|
188
|
+
? requireEntry(manifest.pruningWideTable, manifestUrl, "pruningWideTable")
|
|
189
|
+
: undefined;
|
|
69
190
|
const [ctMoveBuffer, rlMoveBuffer, pruningBuffer] = await Promise.all([
|
|
70
|
-
(
|
|
71
|
-
(
|
|
72
|
-
(
|
|
191
|
+
fetchVerified(baseUrl, ctMove, "int32"),
|
|
192
|
+
fetchVerified(baseUrl, rlMove, "int32"),
|
|
193
|
+
fetchVerified(baseUrl, pruningTable, "nibble")
|
|
73
194
|
]);
|
|
74
|
-
const
|
|
75
|
-
const
|
|
195
|
+
const ctMoveValues = new Int32Array(ctMoveBuffer);
|
|
196
|
+
const rlMoveValues = new Int32Array(rlMoveBuffer);
|
|
76
197
|
const pruningPacked = new Uint8Array(pruningBuffer);
|
|
77
198
|
const pruning = new Uint8Array(450450);
|
|
78
199
|
for (let i = 0; i < 450450; i++) {
|
|
79
200
|
const byte = pruningPacked[i >> 1];
|
|
80
201
|
pruning[i] = (i & 1) === 0 ? byte & 0x0f : byte >> 4;
|
|
81
202
|
}
|
|
82
|
-
|
|
203
|
+
const tables = { ctMove: ctMoveValues, rlMove: rlMoveValues, pruning };
|
|
204
|
+
if (pruningWideEntry) {
|
|
205
|
+
const wideBuffer = await fetchVerified(baseUrl, pruningWideEntry, "nibble");
|
|
206
|
+
const widePacked = new Uint8Array(wideBuffer);
|
|
207
|
+
const pruningWide = new Uint8Array(450450);
|
|
208
|
+
for (let i = 0; i < 450450; i++) {
|
|
209
|
+
const byte = widePacked[i >> 1];
|
|
210
|
+
pruningWide[i] = (i & 1) === 0 ? byte & 0x0f : byte >> 4;
|
|
211
|
+
}
|
|
212
|
+
tables.pruningWide = pruningWide;
|
|
213
|
+
}
|
|
214
|
+
return tables;
|
|
83
215
|
}
|
|
84
216
|
async function loadCenter3(baseUrl) {
|
|
85
|
-
const
|
|
217
|
+
const manifestUrl = `${baseUrl}/${GENERATED_DIRECTORY}/center3-manifest.json`;
|
|
218
|
+
const manifest = await fetchManifest(baseUrl, "center3-manifest.json");
|
|
219
|
+
const moveTable = requireEntry(manifest.moveTable, manifestUrl, "moveTable");
|
|
220
|
+
const pruningTable = requireEntry(manifest.pruningTable, manifestUrl, "pruningTable");
|
|
86
221
|
const [moveTableBuffer, pruningBuffer] = await Promise.all([
|
|
87
|
-
(
|
|
88
|
-
(
|
|
222
|
+
fetchVerified(baseUrl, moveTable, "int32"),
|
|
223
|
+
fetchVerified(baseUrl, pruningTable, "byte")
|
|
89
224
|
]);
|
|
90
225
|
return {
|
|
91
226
|
moveTable: new Int32Array(moveTableBuffer),
|
|
@@ -93,8 +228,68 @@ async function loadCenter3(baseUrl) {
|
|
|
93
228
|
};
|
|
94
229
|
}
|
|
95
230
|
async function loadEdge3(baseUrl) {
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
const distances =
|
|
99
|
-
|
|
231
|
+
const manifestUrl = `${baseUrl}/${GENERATED_DIRECTORY}/edge3-manifest.json`;
|
|
232
|
+
const manifest = await fetchManifest(baseUrl, "edge3-manifest.json");
|
|
233
|
+
const distances = requireEntry(manifest.distances, manifestUrl, "distances");
|
|
234
|
+
// `range` here already counts packed nibble BYTES (classCount / 2), unlike
|
|
235
|
+
// the other manifests — derive the length from classCount instead.
|
|
236
|
+
const length = typeof manifest.classCount === "number" ? Math.ceil(manifest.classCount / 2) : expectedBytes(distances, "byte");
|
|
237
|
+
const distancesBuffer = await fetchVerified(baseUrl, distances, "nibble", length);
|
|
238
|
+
// Keep nibble-packed (see node-loader.ts): all readers handle both formats.
|
|
239
|
+
const table = new Uint8Array(distancesBuffer);
|
|
240
|
+
return { distances: table, distanceOfEdge: (0, edge3_js_1.edge3DistanceLookup)(table) };
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The two optional PDB tables sealed in `hc-manifest.json`. Semantics mirror
|
|
244
|
+
* node-loader.ts exactly (see the header comment): present → verified or
|
|
245
|
+
* reject; absent → probe the c3 bin so stale deployments fail loudly.
|
|
246
|
+
*/
|
|
247
|
+
async function loadOptionalTables(baseUrl) {
|
|
248
|
+
const manifestUrl = `${baseUrl}/${GENERATED_DIRECTORY}/hc-manifest.json`;
|
|
249
|
+
const response = await fetch(manifestUrl);
|
|
250
|
+
let manifest;
|
|
251
|
+
if (response.ok) {
|
|
252
|
+
const text = await response.text();
|
|
253
|
+
try {
|
|
254
|
+
manifest = JSON.parse(text);
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
throw new Error(`Invalid table manifest JSON: ${manifestUrl}: ${error instanceof Error ? error.message : String(error)}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
else if (response.status !== 404) {
|
|
261
|
+
throw new Error(`Failed to load table manifest ${manifestUrl}: ${response.status} ${response.statusText}`);
|
|
262
|
+
}
|
|
263
|
+
const result = {};
|
|
264
|
+
if (manifest?.c3PairDist !== undefined) {
|
|
265
|
+
const entry = requireEntry(manifest.c3PairDist, manifestUrl, "c3PairDist");
|
|
266
|
+
if (typeof entry.bytes === "number" && entry.bytes !== C3_PAIR_DIST_BYTES) {
|
|
267
|
+
throw new Error(`Invalid table manifest ${manifestUrl}: c3PairDist.bytes is ${entry.bytes}, expected ` +
|
|
268
|
+
`${C3_PAIR_DIST_BYTES} — sync the assets with pnpm tables:web:sync.`);
|
|
269
|
+
}
|
|
270
|
+
const buffer = await fetchVerified(baseUrl, entry, "nibble", C3_PAIR_DIST_BYTES);
|
|
271
|
+
result.c3PairDist = new Uint8Array(buffer);
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
// Mirror node's fs check: bin present but unsealed = broken deployment.
|
|
275
|
+
const probeUrl = `${baseUrl}/${GENERATED_DIRECTORY}/c3_pair_dist.bin`;
|
|
276
|
+
const probe = await fetch(probeUrl);
|
|
277
|
+
if (probe.ok) {
|
|
278
|
+
throw new Error(`c3_pair_dist.bin is deployed (${probeUrl}) but hc-manifest.json has no "c3PairDist" checksum ` +
|
|
279
|
+
`entry (${manifestUrl}) — refusing to load tables without integrity checks (node fails on the ` +
|
|
280
|
+
"same assets). Sync the assets: pnpm tables:web:sync.");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (manifest) {
|
|
284
|
+
const distances = requireEntry(manifest.distances, manifestUrl, "distances");
|
|
285
|
+
const packed = new Uint8Array(await fetchVerified(baseUrl, distances, "nibble"));
|
|
286
|
+
const dense = (0, hc_pdb_js_1.buildHcDense)(packed);
|
|
287
|
+
if (manifest.moveTable !== undefined) {
|
|
288
|
+
const moveTable = requireEntry(manifest.moveTable, manifestUrl, "moveTable");
|
|
289
|
+
const moveBuffer = await fetchVerified(baseUrl, moveTable, "int32");
|
|
290
|
+
dense.moves = new Int32Array(moveBuffer);
|
|
291
|
+
}
|
|
292
|
+
result.hc = dense;
|
|
293
|
+
}
|
|
294
|
+
return result;
|
|
100
295
|
}
|
|
@@ -13,19 +13,24 @@
|
|
|
13
13
|
* - goal: ct == 0 and rl in the 6 reference goal patterns (even parity);
|
|
14
14
|
* - the 28-move set keeps the ct and rl position sets separate, so the move
|
|
15
15
|
* tables factor: ctmv (6,435×28) and rlmv (70×28);
|
|
16
|
-
* - the search explores the first 23 moves (face moves + Uw2/Rw/Rw2/Rw')
|
|
16
|
+
* - the search explores the first 23 moves (face moves + Uw2/Rw/Rw2/Rw')
|
|
17
|
+
* by default; the wide set (`CENTER2_SEARCH_MOVES_WIDE`, all 28, with
|
|
18
|
+
* the `pruningWide` table) additionally tries Dw2/Lw/Lw2/Lw'/Bw2.
|
|
17
19
|
*
|
|
18
20
|
* The phase-1 → phase-2 bridge (alignment + encode) is in `reduction.ts`.
|
|
19
21
|
*/
|
|
20
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.CENTER2_STATES = exports.CENTER2_RL_STATES = exports.CENTER2_CT_STATES = exports.CENTER2_GOAL_RL = exports.CENTER2_SEARCH_MOVES = exports.CENTER2_MOVES = void 0;
|
|
23
|
+
exports.CENTER2_CHIRAL_STATES = exports.CENTER2_CHIRAL_CT_STATES = exports.ALIGNMENT_ROTATIONS = exports.CENTER2_STATES = exports.CENTER2_RL_STATES = exports.CENTER2_CT_STATES = exports.CENTER2_GOAL_RL = exports.CENTER2_SEARCH_MOVES_WIDE = exports.CENTER2_SEARCH_MOVES = exports.CENTER2_MOVES = void 0;
|
|
22
24
|
exports.wingPermutationParity = wingPermutationParity;
|
|
23
25
|
exports.encodeCenter2 = encodeCenter2;
|
|
24
26
|
exports.isCenter2Goal = isCenter2Goal;
|
|
25
27
|
exports.center2Bridge = center2Bridge;
|
|
26
28
|
exports.generateCenter2Tables = generateCenter2Tables;
|
|
29
|
+
exports.encodeCenter2ChiralCt = encodeCenter2ChiralCt;
|
|
30
|
+
exports.generateCenter2ChiralTables = generateCenter2ChiralTables;
|
|
27
31
|
exports.solveCenter2 = solveCenter2;
|
|
28
32
|
exports.solveCenter2All = solveCenter2All;
|
|
33
|
+
exports.getWingMoveMaps = getWingMoveMaps;
|
|
29
34
|
exports.solveCenter2GatedLength = solveCenter2GatedLength;
|
|
30
35
|
exports.solveCenter2Gated = solveCenter2Gated;
|
|
31
36
|
exports.verifyCenter2 = verifyCenter2;
|
|
@@ -41,11 +46,23 @@ exports.CENTER2_MOVES = [
|
|
|
41
46
|
];
|
|
42
47
|
/** The search's exploration set (the first 23 of CENTER2_MOVES). */
|
|
43
48
|
exports.CENTER2_SEARCH_MOVES = exports.CENTER2_MOVES.slice(0, 23);
|
|
49
|
+
/**
|
|
50
|
+
* The wide exploration set: all 28 moves (adds Dw2/Lw/Lw2/Lw'/Bw2).
|
|
51
|
+
* Searches over this set must use the `pruningWide` table — the
|
|
52
|
+
* 23-move pruning distances are inadmissible here (they overestimate).
|
|
53
|
+
*/
|
|
54
|
+
exports.CENTER2_SEARCH_MOVES_WIDE = exports.CENTER2_MOVES;
|
|
44
55
|
/** The 6 reference goal rl patterns (full 70-space values, even parity). */
|
|
45
56
|
exports.CENTER2_GOAL_RL = [0, 18, 28, 46, 54, 56];
|
|
46
57
|
exports.CENTER2_CT_STATES = 6435;
|
|
47
58
|
exports.CENTER2_RL_STATES = 70;
|
|
48
59
|
exports.CENTER2_STATES = exports.CENTER2_CT_STATES * exports.CENTER2_RL_STATES;
|
|
60
|
+
const CENTER2_GOAL_FLAGS = (() => {
|
|
61
|
+
const flags = new Uint8Array(exports.CENTER2_RL_STATES);
|
|
62
|
+
for (const rl of exports.CENTER2_GOAL_RL)
|
|
63
|
+
flags[rl] = 1;
|
|
64
|
+
return flags;
|
|
65
|
+
})();
|
|
49
66
|
// ct positions: U(0-3), D(12-15), F(8-11), B(20-23) — the reference's face
|
|
50
67
|
// order {U, D, F, B}, in this repo's face-major numbering.
|
|
51
68
|
const CT_POS = [0, 1, 2, 3, 12, 13, 14, 15, 8, 9, 10, 11, 20, 21, 22, 23];
|
|
@@ -109,7 +126,7 @@ function encodeCenter2(state) {
|
|
|
109
126
|
return { ct: ctRank, rl: rlRank };
|
|
110
127
|
}
|
|
111
128
|
function isCenter2Goal(ct, rl) {
|
|
112
|
-
return ct === 0 &&
|
|
129
|
+
return ct === 0 && CENTER2_GOAL_FLAGS[rl] === 1;
|
|
113
130
|
}
|
|
114
131
|
/**
|
|
115
132
|
* The phase-2 coordinate is only valid for states where exactly 8 of the
|
|
@@ -125,6 +142,11 @@ const ALIGNMENT_CANDIDATES = [
|
|
|
125
142
|
["Uw", "Dw'"]
|
|
126
143
|
];
|
|
127
144
|
function center2InvariantDiffs(centers) {
|
|
145
|
+
const rlAxis = centers[RL_POS[7]] % 3;
|
|
146
|
+
for (let i = 0; i < 7; i++) {
|
|
147
|
+
if (centers[RL_POS[i]] % 3 !== rlAxis)
|
|
148
|
+
return { ctDiff: -1, rlDiff: -1 };
|
|
149
|
+
}
|
|
128
150
|
let ctDiff = 0;
|
|
129
151
|
let rlDiff = 0;
|
|
130
152
|
for (let i = 0; i < 15; i++) {
|
|
@@ -137,12 +159,18 @@ function center2InvariantDiffs(centers) {
|
|
|
137
159
|
}
|
|
138
160
|
return { ctDiff, rlDiff };
|
|
139
161
|
}
|
|
162
|
+
exports.ALIGNMENT_ROTATIONS = [
|
|
163
|
+
[],
|
|
164
|
+
["z"],
|
|
165
|
+
["y"]
|
|
166
|
+
];
|
|
140
167
|
/**
|
|
141
168
|
* Phase-1 → phase-2 bridge: apply the shortest alignment candidate that
|
|
142
169
|
* makes the state coordinate-invariant, then encode.
|
|
143
170
|
*/
|
|
144
171
|
function center2Bridge(state) {
|
|
145
|
-
for (
|
|
172
|
+
for (let idx = 0; idx < ALIGNMENT_CANDIDATES.length; idx++) {
|
|
173
|
+
const alignment = ALIGNMENT_CANDIDATES[idx];
|
|
146
174
|
const aligned = alignment.length === 0
|
|
147
175
|
? state
|
|
148
176
|
: (0, moves_js_1.applyCube4Algorithm)(state, alignment);
|
|
@@ -150,6 +178,7 @@ function center2Bridge(state) {
|
|
|
150
178
|
if (ctDiff === 8 && rlDiff === 4) {
|
|
151
179
|
return {
|
|
152
180
|
alignment: [...alignment],
|
|
181
|
+
rotation: [...exports.ALIGNMENT_ROTATIONS[idx]],
|
|
153
182
|
state: aligned,
|
|
154
183
|
encoded: encodeCenter2(aligned)
|
|
155
184
|
};
|
|
@@ -259,7 +288,123 @@ function generateCenter2Tables() {
|
|
|
259
288
|
}
|
|
260
289
|
}
|
|
261
290
|
}
|
|
262
|
-
|
|
291
|
+
// Wide pruning BFS: same goals, all 28 moves (exact distances for the
|
|
292
|
+
// wide search; the 23-move table overestimates there).
|
|
293
|
+
const pruningWide = new Uint8Array(exports.CENTER2_STATES).fill(0xff);
|
|
294
|
+
const wideQueue = [];
|
|
295
|
+
for (const rl of exports.CENTER2_GOAL_RL) {
|
|
296
|
+
pruningWide[rl] = 0;
|
|
297
|
+
wideQueue.push(rl);
|
|
298
|
+
}
|
|
299
|
+
let wideHead = 0;
|
|
300
|
+
while (wideHead < wideQueue.length) {
|
|
301
|
+
const idx = wideQueue[wideHead++];
|
|
302
|
+
const ct = Math.floor(idx / exports.CENTER2_RL_STATES);
|
|
303
|
+
const rl = idx % exports.CENTER2_RL_STATES;
|
|
304
|
+
const d = pruningWide[idx];
|
|
305
|
+
for (let m = 0; m < exports.CENTER2_SEARCH_MOVES_WIDE.length; m++) {
|
|
306
|
+
const nextCt = ctMove[ct * exports.CENTER2_MOVES.length + m];
|
|
307
|
+
const nextRl = rlMove[rl * exports.CENTER2_MOVES.length + m];
|
|
308
|
+
const next = nextCt * exports.CENTER2_RL_STATES + nextRl;
|
|
309
|
+
if (pruningWide[next] === 0xff) {
|
|
310
|
+
pruningWide[next] = d + 1;
|
|
311
|
+
wideQueue.push(next);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return { ctMove, rlMove, pruning, pruningWide };
|
|
316
|
+
}
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// Chiral (absolute-ct) phase-2 tables
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
/**
|
|
321
|
+
* The ct rank above is RELATIVE to ct[15], so `ct == 0` accepts both
|
|
322
|
+
* assignments of the two non-R/L axes to the U/D and F/B face-pairs. Only
|
|
323
|
+
* one of them survives the downstream chirality gate (`axisDet`: the U, R, F
|
|
324
|
+
* axes must form a right-handed frame, i.e. U/D must hold axis (a + 2) % 3
|
|
325
|
+
* when R/L holds axis a). The chiral coordinate ranks the ABSOLUTE pattern
|
|
326
|
+
* "which of the 16 U/D/F/B positions hold the U/D-bound axis" (C(16,8) =
|
|
327
|
+
* 12,870) and its pruning measures the distance to the chirality-correct
|
|
328
|
+
* goals only, so the phase-2 search never walks wrong-chirality goal paths.
|
|
329
|
+
* Same goal set as `ct == 0 && axisDet == 1`; generated at runtime (~1.6 MB).
|
|
330
|
+
*/
|
|
331
|
+
exports.CENTER2_CHIRAL_CT_STATES = 12870;
|
|
332
|
+
exports.CENTER2_CHIRAL_STATES = exports.CENTER2_CHIRAL_CT_STATES * exports.CENTER2_RL_STATES;
|
|
333
|
+
function rankSubset16(bits) {
|
|
334
|
+
let idx = 0;
|
|
335
|
+
let r = 8;
|
|
336
|
+
for (let i = 15; i >= 0 && r > 0; i--) {
|
|
337
|
+
if (bits[i] === 1) {
|
|
338
|
+
idx += CNK[i][r];
|
|
339
|
+
r--;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return idx;
|
|
343
|
+
}
|
|
344
|
+
function unrankSubset16(idx) {
|
|
345
|
+
const bits = new Uint8Array(16);
|
|
346
|
+
let r = 8;
|
|
347
|
+
for (let i = 15; i >= 0 && r > 0; i--) {
|
|
348
|
+
if (idx >= CNK[i][r]) {
|
|
349
|
+
idx -= CNK[i][r];
|
|
350
|
+
bits[i] = 1;
|
|
351
|
+
r--;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return bits;
|
|
355
|
+
}
|
|
356
|
+
/** Chiral ct rank of a coordinate-invariant (bridged) phase-2 state. */
|
|
357
|
+
function encodeCenter2ChiralCt(state) {
|
|
358
|
+
const rlAxis = state.centers[RL_POS[0]] % 3;
|
|
359
|
+
const udAxis = (rlAxis + 2) % 3;
|
|
360
|
+
const bits = new Uint8Array(16);
|
|
361
|
+
for (let i = 0; i < 16; i++)
|
|
362
|
+
bits[i] = state.centers[CT_POS[i]] % 3 === udAxis ? 1 : 0;
|
|
363
|
+
return rankSubset16(bits);
|
|
364
|
+
}
|
|
365
|
+
function generateCenter2ChiralTables(rlMove) {
|
|
366
|
+
const moveMaps = buildMovePositionMaps();
|
|
367
|
+
const nMoves = exports.CENTER2_MOVES.length;
|
|
368
|
+
const ctMove = new Uint16Array(exports.CENTER2_CHIRAL_CT_STATES * nMoves);
|
|
369
|
+
const full = new Uint8Array(24);
|
|
370
|
+
const bits = new Uint8Array(16);
|
|
371
|
+
for (let ct = 0; ct < exports.CENTER2_CHIRAL_CT_STATES; ct++) {
|
|
372
|
+
const pattern = unrankSubset16(ct);
|
|
373
|
+
full.fill(0);
|
|
374
|
+
for (let i = 0; i < 16; i++)
|
|
375
|
+
full[CT_POS[i]] = pattern[i];
|
|
376
|
+
for (let m = 0; m < nMoves; m++) {
|
|
377
|
+
for (let i = 0; i < 16; i++)
|
|
378
|
+
bits[i] = full[moveMaps[m * 24 + CT_POS[i]]];
|
|
379
|
+
ctMove[ct * nMoves + m] = rankSubset16(bits);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const goalBits = new Uint8Array(16);
|
|
383
|
+
for (let i = 0; i < 8; i++)
|
|
384
|
+
goalBits[i] = 1; // CT_POS[0..7] = U and D faces
|
|
385
|
+
const goalCt = rankSubset16(goalBits);
|
|
386
|
+
const pruning = new Uint8Array(exports.CENTER2_CHIRAL_STATES).fill(0xff);
|
|
387
|
+
const queue = new Int32Array(exports.CENTER2_CHIRAL_STATES);
|
|
388
|
+
let tail = 0;
|
|
389
|
+
for (const rl of exports.CENTER2_GOAL_RL) {
|
|
390
|
+
const idx = goalCt * exports.CENTER2_RL_STATES + rl;
|
|
391
|
+
pruning[idx] = 0;
|
|
392
|
+
queue[tail++] = idx;
|
|
393
|
+
}
|
|
394
|
+
for (let head = 0; head < tail; head++) {
|
|
395
|
+
const idx = queue[head];
|
|
396
|
+
const ct = (idx / exports.CENTER2_RL_STATES) | 0;
|
|
397
|
+
const rl = idx - ct * exports.CENTER2_RL_STATES;
|
|
398
|
+
const d = pruning[idx] + 1;
|
|
399
|
+
for (let m = 0; m < exports.CENTER2_SEARCH_MOVES.length; m++) {
|
|
400
|
+
const next = ctMove[ct * nMoves + m] * exports.CENTER2_RL_STATES + rlMove[rl * nMoves + m];
|
|
401
|
+
if (pruning[next] === 0xff) {
|
|
402
|
+
pruning[next] = d;
|
|
403
|
+
queue[tail++] = next;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return { ctMove, pruning, goalCt };
|
|
263
408
|
}
|
|
264
409
|
// The reference's cancellation rule (ckmv over move2std): skip the same base
|
|
265
410
|
// move, or the same axis-class when the previous move's standard index is
|
|
@@ -434,24 +579,24 @@ function buildWingMoveMaps() {
|
|
|
434
579
|
// ---------------------------------------------------------------------------
|
|
435
580
|
// Verification gates
|
|
436
581
|
// ---------------------------------------------------------------------------
|
|
437
|
-
function verifyCenter2(tables) {
|
|
582
|
+
function verifyCenter2(tables, pruning = tables.pruning, moves = exports.CENTER2_SEARCH_MOVES) {
|
|
438
583
|
const errors = [];
|
|
439
584
|
for (const rl of exports.CENTER2_GOAL_RL) {
|
|
440
|
-
if (
|
|
585
|
+
if (pruning[rl] !== 0) {
|
|
441
586
|
errors.push(`Goal rl ${rl} must have pruning 0.`);
|
|
442
587
|
}
|
|
443
588
|
}
|
|
444
589
|
let maxDepth = 0;
|
|
445
590
|
for (let idx = 0; idx < exports.CENTER2_STATES; idx++) {
|
|
446
|
-
const d =
|
|
591
|
+
const d = pruning[idx];
|
|
447
592
|
if (d > maxDepth)
|
|
448
593
|
maxDepth = d;
|
|
449
594
|
const ct = Math.floor(idx / exports.CENTER2_RL_STATES);
|
|
450
595
|
const rl = idx % exports.CENTER2_RL_STATES;
|
|
451
|
-
for (let m = 0; m <
|
|
596
|
+
for (let m = 0; m < moves.length; m++) {
|
|
452
597
|
const nc = tables.ctMove[ct * exports.CENTER2_MOVES.length + m];
|
|
453
598
|
const nr = tables.rlMove[rl * exports.CENTER2_MOVES.length + m];
|
|
454
|
-
const nd =
|
|
599
|
+
const nd = pruning[nc * exports.CENTER2_RL_STATES + nr];
|
|
455
600
|
if (nd < d - 1 || nd > d + 1) {
|
|
456
601
|
errors.push(`Pruning inconsistency at ${idx} move ${exports.CENTER2_MOVES[m]}: ${d} -> ${nd}.`);
|
|
457
602
|
break;
|
|
@@ -28,6 +28,7 @@ exports.getSymTables = getSymTables;
|
|
|
28
28
|
exports.buildSymTables = buildSymTables;
|
|
29
29
|
exports.getMvSymFrom = getMvSymFrom;
|
|
30
30
|
exports.generateEdge3Tables = generateEdge3Tables;
|
|
31
|
+
exports.unpackEdge3Distances = unpackEdge3Distances;
|
|
31
32
|
exports.edge3DistanceLookup = edge3DistanceLookup;
|
|
32
33
|
exports.__edge3TestHooks = __edge3TestHooks;
|
|
33
34
|
exports.verifyEdge3 = verifyEdge3;
|
|
@@ -479,8 +480,23 @@ function generateEdge3Tables(maxDepthCap = -1) {
|
|
|
479
480
|
frontier = next;
|
|
480
481
|
depth++;
|
|
481
482
|
}
|
|
482
|
-
const
|
|
483
|
-
|
|
483
|
+
const unpacked = unpackEdge3Distances(distances);
|
|
484
|
+
const distanceOfEdge = edge3DistanceLookup(unpacked);
|
|
485
|
+
return { distances: unpacked, distanceOfEdge };
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Unpack 4-bit nibble-packed distances into a byte-expanded Uint8Array (1 byte per state).
|
|
489
|
+
*/
|
|
490
|
+
function unpackEdge3Distances(packed) {
|
|
491
|
+
if (packed.length >= exports.EDGE3_TOTAL)
|
|
492
|
+
return packed;
|
|
493
|
+
const unpacked = new Uint8Array(exports.EDGE3_TOTAL);
|
|
494
|
+
for (let i = 0; i < packed.length; i++) {
|
|
495
|
+
const b = packed[i];
|
|
496
|
+
unpacked[i << 1] = b & 0x0f;
|
|
497
|
+
unpacked[(i << 1) | 1] = b >> 4;
|
|
498
|
+
}
|
|
499
|
+
return unpacked;
|
|
484
500
|
}
|
|
485
501
|
/**
|
|
486
502
|
* The distance lookup over a loaded distances buffer: builds the sym
|
|
@@ -491,9 +507,12 @@ function edge3DistanceLookup(distances) {
|
|
|
491
507
|
const t = symTables ?? buildSymTables();
|
|
492
508
|
const { sym2Raw, sym2Mask, raw2Sym, mvrot, mvroto } = t;
|
|
493
509
|
const identity = Uint8Array.from({ length: 12 }, (_, i) => i);
|
|
510
|
+
const isUnpacked = distances.length >= exports.EDGE3_TOTAL;
|
|
494
511
|
return (e) => {
|
|
495
512
|
const std = (0, pairing_coordinate_js_1.normalizePairingState)(e, identity).edge;
|
|
496
513
|
const idx = getMvSymFrom(std, identity, 20, sym2Raw, sym2Mask, raw2Sym, mvrot, mvroto) >> 3;
|
|
514
|
+
if (isUnpacked)
|
|
515
|
+
return distances[idx];
|
|
497
516
|
return (distances[idx >> 1] >> ((idx & 1) * 4)) & 0x0f;
|
|
498
517
|
};
|
|
499
518
|
}
|
|
@@ -511,8 +530,10 @@ function verifyEdge3(tables) {
|
|
|
511
530
|
errors.push("Solved state must have distance 0.");
|
|
512
531
|
}
|
|
513
532
|
let seen = 0;
|
|
533
|
+
const isUnpacked = tables.distances.length >= exports.EDGE3_TOTAL;
|
|
514
534
|
for (let i = 0; i < exports.EDGE3_TOTAL; i++) {
|
|
515
|
-
|
|
535
|
+
const d = isUnpacked ? tables.distances[i] : ((tables.distances[i >> 1] >> ((i & 1) * 4)) & 0x0f);
|
|
536
|
+
if (d !== 0x0f)
|
|
516
537
|
seen++;
|
|
517
538
|
}
|
|
518
539
|
if (seen !== exports.EDGE3_TOTAL) {
|