@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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 2,
|
|
3
|
+
"puzzle": "cube4",
|
|
4
|
+
"phase": "hc",
|
|
5
|
+
"moves": [
|
|
6
|
+
"U",
|
|
7
|
+
"U2",
|
|
8
|
+
"U'",
|
|
9
|
+
"R2",
|
|
10
|
+
"F",
|
|
11
|
+
"F2",
|
|
12
|
+
"F'",
|
|
13
|
+
"D",
|
|
14
|
+
"D2",
|
|
15
|
+
"D'",
|
|
16
|
+
"L2",
|
|
17
|
+
"B",
|
|
18
|
+
"B2",
|
|
19
|
+
"B'",
|
|
20
|
+
"Uw2",
|
|
21
|
+
"Rw2",
|
|
22
|
+
"Fw2"
|
|
23
|
+
],
|
|
24
|
+
"states": 2704156,
|
|
25
|
+
"reachableStates": 58800,
|
|
26
|
+
"reachableDense": 58800,
|
|
27
|
+
"maxDepth": 11,
|
|
28
|
+
"distances": {
|
|
29
|
+
"file": "hc-distances.bin",
|
|
30
|
+
"range": 2704156,
|
|
31
|
+
"checksum": "8720ec4a83eca33c3d56ae0ab8709c0b3d847c14d011b6dcc4413b28bd9dfa12"
|
|
32
|
+
},
|
|
33
|
+
"moveTable": {
|
|
34
|
+
"file": "hc-move.bin",
|
|
35
|
+
"range": 999600,
|
|
36
|
+
"checksum": "6bd1d8f36b77e0863425bced1dfb69df7a13dde4fb18ceeaeba00abf14519922"
|
|
37
|
+
},
|
|
38
|
+
"c3PairDist": {
|
|
39
|
+
"file": "c3_pair_dist.bin",
|
|
40
|
+
"range": 4233600,
|
|
41
|
+
"bytes": 2116800,
|
|
42
|
+
"checksum": "2d070dcd9cfa8ede09cf0def93b3b2ba05a7e7e5acf631d6bf25a65bf0057035"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Half-class exact PDB (Phase-3 center heuristic, Fase 1 pivot).
|
|
3
|
+
*
|
|
4
|
+
* PIVOT NOTE: the original Fase-1 spec (3 exact face-pair color PDBs,
|
|
5
|
+
* C(24,4)xC(20,4) = 51M states each) is unworkable — verified that outer
|
|
6
|
+
* face turns never move centers across faces (only wide doubles do), so
|
|
7
|
+
* the exact-color orbit under the 17 search moves holds ~70 states and
|
|
8
|
+
* the bound would read "unknown" almost everywhere. This table instead
|
|
9
|
+
* tracks the EXACT arrangement of half-class bits ((color/3)^1: 12 ones,
|
|
10
|
+
* 12 zeros), which moves under every search move:
|
|
11
|
+
*
|
|
12
|
+
* states = C(24,12) = 2,704,156, nibble-packed (~1.35 MB)
|
|
13
|
+
*
|
|
14
|
+
* Center3 (29,400 states) is a quotient of this coordinate (rank-7 pair
|
|
15
|
+
* + rl-index + parity), so this PDB dominates it by construction:
|
|
16
|
+
* bound = max(ct3, edge, hc). BFS from solved with the 17 search moves.
|
|
17
|
+
*/
|
|
18
|
+
export declare const HC_PDB_MOVES: readonly string[];
|
|
19
|
+
/** C(24,12) = 2,704,156. */
|
|
20
|
+
export declare const HC_PDB_STATES = 2704156;
|
|
21
|
+
/** C(24,12) rank of a 24-bit mask with popcount 12 (bit i = position i holds 1). */
|
|
22
|
+
export declare function hcRank(mask: number): number;
|
|
23
|
+
/** Inverse of hcRank: mask with popcount 12. */
|
|
24
|
+
export declare function hcUnrank(rank: number): number;
|
|
25
|
+
/** Encode center colors to the half-class coordinate. */
|
|
26
|
+
export declare function encodeHcPdb(centers: Uint8Array): number;
|
|
27
|
+
export declare function getHcPdbMoveMaps(): Int32Array;
|
|
28
|
+
/** Nibble-packed lookup (high nibble = odd index). */
|
|
29
|
+
export declare function hcPdbDistance(packed: Uint8Array, coordinate: number): number;
|
|
30
|
+
/**
|
|
31
|
+
* BFS from the solved half-class mask. Returns nibble-packed distances
|
|
32
|
+
* (0x0f = unreachable). ~2.7M states x 17 moves.
|
|
33
|
+
*/
|
|
34
|
+
export declare function generateHcPdb(onProgress?: (done: number, total: number) => void): Uint8Array;
|
|
35
|
+
/** Number of reachable states (BFS orbit size). Fixed by the move set. */
|
|
36
|
+
export declare const HC_REACHABLE = 58800;
|
|
37
|
+
export interface HcDenseTables {
|
|
38
|
+
/** Sorted reachable full coordinates. Used with binary search (~0.23 MB). */
|
|
39
|
+
coordinates: Uint32Array;
|
|
40
|
+
/** Dense moves: reachableCount x 17, dense indices. */
|
|
41
|
+
moves: Int32Array;
|
|
42
|
+
/** Dense distances (unpacked, 0x0f = unknown). */
|
|
43
|
+
distances: Uint8Array;
|
|
44
|
+
reachableCount: number;
|
|
45
|
+
}
|
|
46
|
+
/** Build the compact coordinate list + unpacked distances from the packed PDB. */
|
|
47
|
+
export declare function buildHcDense(packed: Uint8Array): HcDenseTables;
|
|
48
|
+
/** Full HC coordinate -> dense index, or -1 when it is unreachable. */
|
|
49
|
+
export declare function hcDenseIndex(dense: Pick<HcDenseTables, "coordinates">, coordinate: number): number;
|
|
50
|
+
/** Dense transition table: for each reachable state and move, dense index (or -1). */
|
|
51
|
+
export declare function generateHcDenseMoves(dense: HcDenseTables): Int32Array;
|
|
52
|
+
//# sourceMappingURL=hc-pdb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hc-pdb.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube4/reduction/hc-pdb.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAA+B,CAAC;AAE1E,4BAA4B;AAC5B,eAAO,MAAM,aAAa,UAAU,CAAC;AAmBrC,oFAAoF;AACpF,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAU3C;AAED,gDAAgD;AAChD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAc7C;AAED,yDAAyD;AACzD,wBAAgB,WAAW,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAMvD;AAoBD,wBAAgB,gBAAgB,IAAI,UAAU,CAG7C;AAED,sDAAsD;AACtD,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAG5E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GACjD,UAAU,CA+CZ;AAED,0EAA0E;AAC1E,eAAO,MAAM,YAAY,QAAQ,CAAC;AAElC,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,WAAW,EAAE,WAAW,CAAC;IACzB,uDAAuD;IACvD,KAAK,EAAE,UAAU,CAAC;IAClB,kDAAkD;IAClD,SAAS,EAAE,UAAU,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,aAAa,CAgB9D;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAC1B,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,aAAa,CAAC,EACzC,UAAU,EAAE,MAAM,GACjB,MAAM,CAWR;AAED,sFAAsF;AACtF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,UAAU,CAgBrE"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Half-class exact PDB (Phase-3 center heuristic, Fase 1 pivot).
|
|
3
|
+
*
|
|
4
|
+
* PIVOT NOTE: the original Fase-1 spec (3 exact face-pair color PDBs,
|
|
5
|
+
* C(24,4)xC(20,4) = 51M states each) is unworkable — verified that outer
|
|
6
|
+
* face turns never move centers across faces (only wide doubles do), so
|
|
7
|
+
* the exact-color orbit under the 17 search moves holds ~70 states and
|
|
8
|
+
* the bound would read "unknown" almost everywhere. This table instead
|
|
9
|
+
* tracks the EXACT arrangement of half-class bits ((color/3)^1: 12 ones,
|
|
10
|
+
* 12 zeros), which moves under every search move:
|
|
11
|
+
*
|
|
12
|
+
* states = C(24,12) = 2,704,156, nibble-packed (~1.35 MB)
|
|
13
|
+
*
|
|
14
|
+
* Center3 (29,400 states) is a quotient of this coordinate (rank-7 pair
|
|
15
|
+
* + rl-index + parity), so this PDB dominates it by construction:
|
|
16
|
+
* bound = max(ct3, edge, hc). BFS from solved with the 17 search moves.
|
|
17
|
+
*/
|
|
18
|
+
import { CENTER3_MOVES } from "./center3.js";
|
|
19
|
+
import { FACE_MOVE_EFFECTS, parseMove } from "../moves.js";
|
|
20
|
+
export const HC_PDB_MOVES = CENTER3_MOVES.slice(0, 17);
|
|
21
|
+
/** C(24,12) = 2,704,156. */
|
|
22
|
+
export const HC_PDB_STATES = 2704156;
|
|
23
|
+
const CNK = (() => {
|
|
24
|
+
const table = [];
|
|
25
|
+
for (let n = 0; n <= 24; n++) {
|
|
26
|
+
table[n] = [];
|
|
27
|
+
for (let k = 0; k <= 12; k++) {
|
|
28
|
+
if (k === 0 || k === n)
|
|
29
|
+
table[n][k] = 1;
|
|
30
|
+
else if (k > n)
|
|
31
|
+
table[n][k] = 0;
|
|
32
|
+
else
|
|
33
|
+
table[n][k] = table[n - 1][k - 1] + table[n - 1][k];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return table;
|
|
37
|
+
})();
|
|
38
|
+
function halfClass(color) {
|
|
39
|
+
return (Math.floor(color / 3) ^ 1) & 1;
|
|
40
|
+
}
|
|
41
|
+
/** C(24,12) rank of a 24-bit mask with popcount 12 (bit i = position i holds 1). */
|
|
42
|
+
export function hcRank(mask) {
|
|
43
|
+
let rank = 0;
|
|
44
|
+
let k = 1;
|
|
45
|
+
for (let i = 0; i < 24; i++) {
|
|
46
|
+
if (mask & (1 << i)) {
|
|
47
|
+
rank += CNK[i][k];
|
|
48
|
+
k++;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return rank;
|
|
52
|
+
}
|
|
53
|
+
/** Inverse of hcRank: mask with popcount 12. */
|
|
54
|
+
export function hcUnrank(rank) {
|
|
55
|
+
let mask = 0;
|
|
56
|
+
let r = rank;
|
|
57
|
+
for (let k = 12; k >= 1; k--) {
|
|
58
|
+
for (let v = 23; v >= 0; v--) {
|
|
59
|
+
const c = CNK[v][k];
|
|
60
|
+
if (c <= r) {
|
|
61
|
+
mask |= 1 << v;
|
|
62
|
+
r -= c;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return mask;
|
|
68
|
+
}
|
|
69
|
+
/** Encode center colors to the half-class coordinate. */
|
|
70
|
+
export function encodeHcPdb(centers) {
|
|
71
|
+
let mask = 0;
|
|
72
|
+
for (let i = 0; i < 24; i++) {
|
|
73
|
+
if (halfClass(centers[i]) === 1)
|
|
74
|
+
mask |= 1 << i;
|
|
75
|
+
}
|
|
76
|
+
return hcRank(mask);
|
|
77
|
+
}
|
|
78
|
+
/** Position permutation maps for the 17 moves (next[i] = prev[map[i]]). */
|
|
79
|
+
function buildMovePositionMaps() {
|
|
80
|
+
const maps = new Int32Array(HC_PDB_MOVES.length * 24);
|
|
81
|
+
for (let m = 0; m < HC_PDB_MOVES.length; m++) {
|
|
82
|
+
const { face, turns } = parseMove(HC_PDB_MOVES[m]);
|
|
83
|
+
const effect = FACE_MOVE_EFFECTS[face];
|
|
84
|
+
let pos = Array.from({ length: 24 }, (_, i) => i);
|
|
85
|
+
for (let t = 0; t < turns; t++) {
|
|
86
|
+
const next = new Array(24);
|
|
87
|
+
for (let i = 0; i < 24; i++)
|
|
88
|
+
next[i] = pos[effect.ctperm[i]];
|
|
89
|
+
pos = next;
|
|
90
|
+
}
|
|
91
|
+
for (let i = 0; i < 24; i++)
|
|
92
|
+
maps[m * 24 + i] = pos[i];
|
|
93
|
+
}
|
|
94
|
+
return maps;
|
|
95
|
+
}
|
|
96
|
+
let cachedMoveMaps = null;
|
|
97
|
+
export function getHcPdbMoveMaps() {
|
|
98
|
+
if (!cachedMoveMaps)
|
|
99
|
+
cachedMoveMaps = buildMovePositionMaps();
|
|
100
|
+
return cachedMoveMaps;
|
|
101
|
+
}
|
|
102
|
+
/** Nibble-packed lookup (high nibble = odd index). */
|
|
103
|
+
export function hcPdbDistance(packed, coordinate) {
|
|
104
|
+
const byte = packed[coordinate >> 1];
|
|
105
|
+
return (coordinate & 1) === 0 ? byte & 0x0f : byte >> 4;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* BFS from the solved half-class mask. Returns nibble-packed distances
|
|
109
|
+
* (0x0f = unreachable). ~2.7M states x 17 moves.
|
|
110
|
+
*/
|
|
111
|
+
export function generateHcPdb(onProgress) {
|
|
112
|
+
const maps = getHcPdbMoveMaps();
|
|
113
|
+
const packed = new Uint8Array((HC_PDB_STATES + 1) >> 1).fill(0xff);
|
|
114
|
+
const get = (c) => {
|
|
115
|
+
const b = packed[c >> 1];
|
|
116
|
+
return (c & 1) === 0 ? b & 0x0f : b >> 4;
|
|
117
|
+
};
|
|
118
|
+
const set = (c, d) => {
|
|
119
|
+
const i = c >> 1;
|
|
120
|
+
if ((c & 1) === 0)
|
|
121
|
+
packed[i] = (packed[i] & 0xf0) | (d & 0x0f);
|
|
122
|
+
else
|
|
123
|
+
packed[i] = (packed[i] & 0x0f) | ((d & 0x0f) << 4);
|
|
124
|
+
};
|
|
125
|
+
// Solved mask: U/R/F faces hold half-class 1.
|
|
126
|
+
let solvedMask = 0;
|
|
127
|
+
for (const i of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
|
|
128
|
+
solvedMask |= 1 << i;
|
|
129
|
+
const solved = hcRank(solvedMask);
|
|
130
|
+
if (solved !== 0)
|
|
131
|
+
throw new Error(`solved rank != 0 (${solved})`);
|
|
132
|
+
const queue = new Int32Array(HC_PDB_STATES);
|
|
133
|
+
let head = 0;
|
|
134
|
+
let tail = 0;
|
|
135
|
+
set(0, 0);
|
|
136
|
+
queue[tail++] = 0;
|
|
137
|
+
while (head < tail) {
|
|
138
|
+
const coord = queue[head++];
|
|
139
|
+
const d = get(coord);
|
|
140
|
+
const nd = d + 1;
|
|
141
|
+
if (nd > 14)
|
|
142
|
+
continue; // nibble range; 0x0f stays "unknown" (admissible)
|
|
143
|
+
const mask = hcUnrank(coord);
|
|
144
|
+
for (let m = 0; m < HC_PDB_MOVES.length; m++) {
|
|
145
|
+
const base = m * 24;
|
|
146
|
+
let nmask = 0;
|
|
147
|
+
for (let i = 0; i < 24; i++) {
|
|
148
|
+
if (mask & (1 << maps[base + i]))
|
|
149
|
+
nmask |= 1 << i;
|
|
150
|
+
}
|
|
151
|
+
const next = hcRank(nmask);
|
|
152
|
+
if (get(next) === 0x0f) {
|
|
153
|
+
set(next, nd);
|
|
154
|
+
queue[tail++] = next;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (onProgress && (head & 0x7ffff) === 0)
|
|
158
|
+
onProgress(head, HC_PDB_STATES);
|
|
159
|
+
}
|
|
160
|
+
if (onProgress)
|
|
161
|
+
onProgress(tail, HC_PDB_STATES);
|
|
162
|
+
return packed;
|
|
163
|
+
}
|
|
164
|
+
/** Number of reachable states (BFS orbit size). Fixed by the move set. */
|
|
165
|
+
export const HC_REACHABLE = 58800;
|
|
166
|
+
/** Build the compact coordinate list + unpacked distances from the packed PDB. */
|
|
167
|
+
export function buildHcDense(packed) {
|
|
168
|
+
let count = 0;
|
|
169
|
+
for (let c = 0; c < HC_PDB_STATES; c++) {
|
|
170
|
+
if (hcPdbDistance(packed, c) !== 0x0f)
|
|
171
|
+
count++;
|
|
172
|
+
}
|
|
173
|
+
const coordinates = new Uint32Array(count);
|
|
174
|
+
const distances = new Uint8Array(count).fill(0x0f);
|
|
175
|
+
let denseIndex = 0;
|
|
176
|
+
for (let c = 0; c < HC_PDB_STATES; c++) {
|
|
177
|
+
const distance = hcPdbDistance(packed, c);
|
|
178
|
+
if (distance === 0x0f)
|
|
179
|
+
continue;
|
|
180
|
+
coordinates[denseIndex] = c;
|
|
181
|
+
distances[denseIndex] = distance;
|
|
182
|
+
denseIndex++;
|
|
183
|
+
}
|
|
184
|
+
return { coordinates, moves: new Int32Array(0), distances, reachableCount: count };
|
|
185
|
+
}
|
|
186
|
+
/** Full HC coordinate -> dense index, or -1 when it is unreachable. */
|
|
187
|
+
export function hcDenseIndex(dense, coordinate) {
|
|
188
|
+
let low = 0;
|
|
189
|
+
let high = dense.coordinates.length - 1;
|
|
190
|
+
while (low <= high) {
|
|
191
|
+
const middle = (low + high) >>> 1;
|
|
192
|
+
const value = dense.coordinates[middle];
|
|
193
|
+
if (value === coordinate)
|
|
194
|
+
return middle;
|
|
195
|
+
if (value < coordinate)
|
|
196
|
+
low = middle + 1;
|
|
197
|
+
else
|
|
198
|
+
high = middle - 1;
|
|
199
|
+
}
|
|
200
|
+
return -1;
|
|
201
|
+
}
|
|
202
|
+
/** Dense transition table: for each reachable state and move, dense index (or -1). */
|
|
203
|
+
export function generateHcDenseMoves(dense) {
|
|
204
|
+
const maps = getHcPdbMoveMaps();
|
|
205
|
+
const moves = new Int32Array(dense.reachableCount * HC_PDB_MOVES.length).fill(-1);
|
|
206
|
+
for (let di = 0; di < dense.coordinates.length; di++) {
|
|
207
|
+
const c = dense.coordinates[di];
|
|
208
|
+
const mask = hcUnrank(c);
|
|
209
|
+
for (let m = 0; m < HC_PDB_MOVES.length; m++) {
|
|
210
|
+
const base = m * 24;
|
|
211
|
+
let nmask = 0;
|
|
212
|
+
for (let i = 0; i < 24; i++) {
|
|
213
|
+
if (mask & (1 << maps[base + i]))
|
|
214
|
+
nmask |= 1 << i;
|
|
215
|
+
}
|
|
216
|
+
moves[di * HC_PDB_MOVES.length + m] = hcDenseIndex(dense, hcRank(nmask));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return moves;
|
|
220
|
+
}
|
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node loader for the cube4 reduction tables (the committed .bin files).
|
|
3
|
-
* The manifests are per-phase (`center1-manifest.json`, ...)
|
|
3
|
+
* The manifests are per-phase (`center1-manifest.json`, ...) plus the shared
|
|
4
|
+
* `hc-manifest.json`, which also seals the optional `c3_pair_dist.bin`.
|
|
5
|
+
*
|
|
6
|
+
* Every .bin read is verified: sha256 checksum AND expected byte length
|
|
7
|
+
* (derived from the manifest `range` + the known packing of each table).
|
|
8
|
+
* All integrity errors name the file and the expected/actual sizes so a
|
|
9
|
+
* corrupted download fails loudly instead of silently producing divergent
|
|
10
|
+
* scrambles. browser-loader.ts performs the identical checks.
|
|
4
11
|
*/
|
|
5
12
|
import type { Center1Tables } from "./center1.js";
|
|
6
13
|
import type { Center2Tables } from "./center2.js";
|
|
7
14
|
import type { Center3Tables } from "./center3.js";
|
|
8
15
|
import type { Edge3Tables } from "./edge3.js";
|
|
16
|
+
import { type HcDenseTables } from "./hc-pdb.js";
|
|
17
|
+
/** Estimated committed table bytes per solver instance (excludes JS objects). */
|
|
18
|
+
export declare function estimateCube4ReductionTableBytes(tables: Cube4ReductionTables): number;
|
|
9
19
|
export interface Cube4ReductionTables {
|
|
10
20
|
center1: Center1Tables;
|
|
11
21
|
center2: Center2Tables;
|
|
12
22
|
center3: Center3Tables;
|
|
13
23
|
edge3: Edge3Tables;
|
|
24
|
+
/** Half-class exact PDB distances (optional; absent = bound disabled). */
|
|
25
|
+
hc?: HcDenseTables;
|
|
26
|
+
/** Joint Center3 x Single Pair PDB distances (optional). */
|
|
27
|
+
c3PairDist?: Uint8Array;
|
|
14
28
|
}
|
|
15
29
|
/** Load all cube4 reduction tables (throws if the generated assets are missing). */
|
|
16
30
|
export declare function loadCube4ReductionTables(): Cube4ReductionTables;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube4/reduction/node-loader.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"node-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube4/reduction/node-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG9C,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAI/D,iFAAiF;AACjF,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAgBrF;AAYD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,KAAK,EAAE,WAAW,CAAC;IACnB,0EAA0E;IAC1E,EAAE,CAAC,EAAE,aAAa,CAAC;IACnB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAuPD,oFAAoF;AACpF,wBAAgB,wBAAwB,IAAI,oBAAoB,CAqB/D"}
|
|
@@ -1,13 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node loader for the cube4 reduction tables (the committed .bin files).
|
|
3
|
-
* The manifests are per-phase (`center1-manifest.json`, ...)
|
|
3
|
+
* The manifests are per-phase (`center1-manifest.json`, ...) plus the shared
|
|
4
|
+
* `hc-manifest.json`, which also seals the optional `c3_pair_dist.bin`.
|
|
5
|
+
*
|
|
6
|
+
* Every .bin read is verified: sha256 checksum AND expected byte length
|
|
7
|
+
* (derived from the manifest `range` + the known packing of each table).
|
|
8
|
+
* All integrity errors name the file and the expected/actual sizes so a
|
|
9
|
+
* corrupted download fails loudly instead of silently producing divergent
|
|
10
|
+
* scrambles. browser-loader.ts performs the identical checks.
|
|
4
11
|
*/
|
|
5
12
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
13
|
import { createRequire } from "node:module";
|
|
7
14
|
import path from "node:path";
|
|
8
15
|
import { createHash } from "node:crypto";
|
|
16
|
+
import { CENTER3_STATES } from "./center3.js";
|
|
9
17
|
import { edge3DistanceLookup } from "./edge3.js";
|
|
18
|
+
import { buildHcDense } from "./hc-pdb.js";
|
|
19
|
+
const TABLE_MEMORY_BUDGET_BYTES = 150 * 1024 * 1024;
|
|
20
|
+
/** Estimated committed table bytes per solver instance (excludes JS objects). */
|
|
21
|
+
export function estimateCube4ReductionTableBytes(tables) {
|
|
22
|
+
return (tables.center1.distance.byteLength +
|
|
23
|
+
tables.center1.moveMaps.byteLength +
|
|
24
|
+
tables.center2.ctMove.byteLength +
|
|
25
|
+
tables.center2.rlMove.byteLength +
|
|
26
|
+
tables.center2.pruning.byteLength +
|
|
27
|
+
(tables.center2.pruningWide?.byteLength ?? 0) +
|
|
28
|
+
tables.center3.moveTable.byteLength +
|
|
29
|
+
tables.center3.pruning.byteLength +
|
|
30
|
+
tables.edge3.distances.byteLength +
|
|
31
|
+
(tables.hc?.moves.byteLength ?? 0) +
|
|
32
|
+
(tables.hc?.distances.byteLength ?? 0) +
|
|
33
|
+
(tables.hc?.coordinates.byteLength ?? 0) +
|
|
34
|
+
(tables.c3PairDist?.byteLength ?? 0));
|
|
35
|
+
}
|
|
36
|
+
function assertCube4ReductionTableBudget(tables) {
|
|
37
|
+
const bytes = estimateCube4ReductionTableBytes(tables);
|
|
38
|
+
if (bytes > TABLE_MEMORY_BUDGET_BYTES) {
|
|
39
|
+
throw new Error(`Cube4 reduction tables use ${(bytes / (1024 * 1024)).toFixed(1)} MiB, above the ` +
|
|
40
|
+
`${(TABLE_MEMORY_BUDGET_BYTES / (1024 * 1024)).toFixed(0)} MiB table budget.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
10
43
|
const GENERATED_DIRECTORY = "puzzles/cube4/reduction/generated";
|
|
44
|
+
/**
|
|
45
|
+
* Byte length of `c3_pair_dist.bin`: CENTER3_STATES x 144 nibble entries
|
|
46
|
+
* (index = ct3 * 144 + f * 12 + h, see phase3-search.ts), nibble-packed.
|
|
47
|
+
* The same constant is enforced in browser-loader.ts.
|
|
48
|
+
*/
|
|
49
|
+
const C3_PAIR_DIST_BYTES = (CENTER3_STATES * 144) / 2;
|
|
11
50
|
function resolveGeneratedAssetDirectory() {
|
|
12
51
|
const envDirectory = process.env.CUBINGCENTER_SCRAMBLER_TABLES_DIR;
|
|
13
52
|
if (envDirectory && existsSync(path.join(envDirectory, "center1-manifest.json"))) {
|
|
@@ -40,48 +79,172 @@ function getAssetDirectoryCandidates() {
|
|
|
40
79
|
function sha256(buffer) {
|
|
41
80
|
return createHash("sha256").update(buffer).digest("hex");
|
|
42
81
|
}
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
82
|
+
function readManifest(file) {
|
|
83
|
+
let text;
|
|
84
|
+
try {
|
|
85
|
+
text = readFileSync(file, "utf8");
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (error.code === "ENOENT") {
|
|
89
|
+
throw new Error(`Table manifest missing: ${file} — run pnpm cube4:center1:tables etc. to regenerate the tables.`);
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(text);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
// Always name the file: a bare JSON.parse error is unactionable.
|
|
98
|
+
throw new Error(`Invalid table manifest JSON: ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function requireEntry(manifest, manifestPath, label) {
|
|
102
|
+
const entry = manifest[label];
|
|
103
|
+
if (!entry || typeof entry.file !== "string" || typeof entry.checksum !== "string") {
|
|
104
|
+
throw new Error(`Invalid table manifest ${manifestPath}: entry "${label}" must declare { file, checksum } — ` +
|
|
105
|
+
"run pnpm cube4:hc:tables etc. to regenerate the manifest.");
|
|
106
|
+
}
|
|
107
|
+
return entry;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Expected byte length of a table file from its manifest entry.
|
|
111
|
+
* `packing` matches how each table is serialized (see the load* functions).
|
|
112
|
+
*/
|
|
113
|
+
function expectedBytes(entry, packing) {
|
|
114
|
+
if (typeof entry.bytes === "number")
|
|
115
|
+
return entry.bytes;
|
|
116
|
+
if (typeof entry.range !== "number")
|
|
117
|
+
return undefined;
|
|
118
|
+
if (packing === "byte")
|
|
119
|
+
return entry.range;
|
|
120
|
+
if (packing === "nibble")
|
|
121
|
+
return Math.ceil(entry.range / 2);
|
|
122
|
+
return entry.range * 4;
|
|
123
|
+
}
|
|
124
|
+
function readChecked(file, expectedChecksum, expectedLength) {
|
|
125
|
+
let buffer;
|
|
126
|
+
try {
|
|
127
|
+
buffer = readFileSync(file);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (error.code === "ENOENT") {
|
|
131
|
+
throw new Error(`Table file missing: ${file}` +
|
|
132
|
+
(expectedLength !== undefined ? ` (expected ${expectedLength} bytes)` : "") +
|
|
133
|
+
" — run pnpm cube4:center1:tables etc. to regenerate the tables.");
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
if (expectedLength !== undefined && buffer.length !== expectedLength) {
|
|
138
|
+
throw new Error(`Table size mismatch: ${file} (expected ${expectedLength} bytes, got ${buffer.length})`);
|
|
139
|
+
}
|
|
140
|
+
const actual = sha256(buffer);
|
|
141
|
+
if (actual !== expectedChecksum) {
|
|
142
|
+
throw new Error(`Table checksum mismatch: ${file} (expected sha256 ${expectedChecksum}, got ${actual}, ${buffer.length} bytes)`);
|
|
47
143
|
}
|
|
48
144
|
return buffer;
|
|
49
145
|
}
|
|
146
|
+
function readCheckedEntry(file, entry, packing) {
|
|
147
|
+
return readChecked(file, entry.checksum, expectedBytes(entry, packing));
|
|
148
|
+
}
|
|
50
149
|
function readInt32(buffer) {
|
|
51
150
|
return new Int32Array(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength));
|
|
52
151
|
}
|
|
53
152
|
function loadCenter1(assetDirectory) {
|
|
54
|
-
const
|
|
153
|
+
const manifestPath = path.join(assetDirectory, "center1-manifest.json");
|
|
154
|
+
const manifest = readManifest(manifestPath);
|
|
155
|
+
const distanceTable = requireEntry(manifest, manifestPath, "distanceTable");
|
|
156
|
+
const moveMaps = requireEntry(manifest, manifestPath, "moveMaps");
|
|
55
157
|
return {
|
|
56
|
-
distance: new Uint8Array(
|
|
57
|
-
moveMaps: readInt32(
|
|
158
|
+
distance: new Uint8Array(readCheckedEntry(path.join(assetDirectory, distanceTable.file), distanceTable, "nibble")),
|
|
159
|
+
moveMaps: readInt32(readCheckedEntry(path.join(assetDirectory, moveMaps.file), moveMaps, "int32"))
|
|
58
160
|
};
|
|
59
161
|
}
|
|
60
162
|
function loadCenter2(assetDirectory) {
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
163
|
+
const manifestPath = path.join(assetDirectory, "center2-manifest.json");
|
|
164
|
+
const manifest = readManifest(manifestPath);
|
|
165
|
+
const ctMove = requireEntry(manifest, manifestPath, "ctMove");
|
|
166
|
+
const rlMove = requireEntry(manifest, manifestPath, "rlMove");
|
|
167
|
+
const pruningTable = requireEntry(manifest, manifestPath, "pruningTable");
|
|
168
|
+
// Tolerates older assets without the wide table (wide searches throw
|
|
169
|
+
// a clear error only when actually requested).
|
|
170
|
+
const pruningWideEntry = manifest.pruningWideTable
|
|
171
|
+
? requireEntry(manifest, manifestPath, "pruningWideTable")
|
|
172
|
+
: undefined;
|
|
173
|
+
function unpackPruning(buffer) {
|
|
174
|
+
const pruning = new Uint8Array(450450);
|
|
175
|
+
for (let i = 0; i < 450450; i++) {
|
|
176
|
+
const byte = buffer[i >> 1];
|
|
177
|
+
pruning[i] = (i & 1) === 0 ? byte & 0x0f : byte >> 4;
|
|
178
|
+
}
|
|
179
|
+
return pruning;
|
|
67
180
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
181
|
+
const pruningBuffer = readCheckedEntry(path.join(assetDirectory, pruningTable.file), pruningTable, "nibble");
|
|
182
|
+
const tables = {
|
|
183
|
+
ctMove: readInt32(readCheckedEntry(path.join(assetDirectory, ctMove.file), ctMove, "int32")),
|
|
184
|
+
rlMove: readInt32(readCheckedEntry(path.join(assetDirectory, rlMove.file), rlMove, "int32")),
|
|
185
|
+
pruning: unpackPruning(pruningBuffer)
|
|
72
186
|
};
|
|
187
|
+
if (pruningWideEntry) {
|
|
188
|
+
const wideBuffer = readCheckedEntry(path.join(assetDirectory, pruningWideEntry.file), pruningWideEntry, "nibble");
|
|
189
|
+
tables.pruningWide = unpackPruning(wideBuffer);
|
|
190
|
+
}
|
|
191
|
+
return tables;
|
|
73
192
|
}
|
|
74
193
|
function loadCenter3(assetDirectory) {
|
|
75
|
-
const
|
|
194
|
+
const manifestPath = path.join(assetDirectory, "center3-manifest.json");
|
|
195
|
+
const manifest = readManifest(manifestPath);
|
|
196
|
+
const moveTable = requireEntry(manifest, manifestPath, "moveTable");
|
|
197
|
+
const pruningTable = requireEntry(manifest, manifestPath, "pruningTable");
|
|
76
198
|
return {
|
|
77
|
-
moveTable: readInt32(
|
|
78
|
-
pruning: new Uint8Array(
|
|
199
|
+
moveTable: readInt32(readCheckedEntry(path.join(assetDirectory, moveTable.file), moveTable, "int32")),
|
|
200
|
+
pruning: new Uint8Array(readCheckedEntry(path.join(assetDirectory, pruningTable.file), pruningTable, "byte"))
|
|
79
201
|
};
|
|
80
202
|
}
|
|
81
203
|
function loadEdge3(assetDirectory) {
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
204
|
+
const manifestPath = path.join(assetDirectory, "edge3-manifest.json");
|
|
205
|
+
const manifest = readManifest(manifestPath);
|
|
206
|
+
const distances = requireEntry(manifest, manifestPath, "distances");
|
|
207
|
+
// `range` here already counts packed nibble BYTES (classCount / 2), unlike
|
|
208
|
+
// the other manifests — derive the length from classCount instead.
|
|
209
|
+
const classCount = manifest.classCount;
|
|
210
|
+
const length = typeof classCount === "number" ? Math.ceil(classCount / 2) : expectedBytes(distances, "byte");
|
|
211
|
+
// Keep the nibble-packed distances as-is (15.5MB instead of 31MB): every
|
|
212
|
+
// reader (phase3 getPrun, solve.ts, edge3DistanceLookup) handles both
|
|
213
|
+
// formats, and the smaller working set halves edge-table DRAM traffic.
|
|
214
|
+
const buffer = readChecked(path.join(assetDirectory, distances.file), distances.checksum, length);
|
|
215
|
+
const table = new Uint8Array(buffer);
|
|
216
|
+
return { distances: table, distanceOfEdge: edge3DistanceLookup(table) };
|
|
217
|
+
}
|
|
218
|
+
function loadHc(assetDirectory, manifest) {
|
|
219
|
+
const manifestPath = path.join(assetDirectory, "hc-manifest.json");
|
|
220
|
+
const distances = requireEntry(manifest, manifestPath, "distances");
|
|
221
|
+
const packed = new Uint8Array(readCheckedEntry(path.join(assetDirectory, distances.file), distances, "nibble"));
|
|
222
|
+
const dense = buildHcDense(packed);
|
|
223
|
+
if (manifest.moveTable !== undefined) {
|
|
224
|
+
const moveTable = requireEntry(manifest, manifestPath, "moveTable");
|
|
225
|
+
const moveBuffer = readCheckedEntry(path.join(assetDirectory, moveTable.file), moveTable, "int32");
|
|
226
|
+
dense.moves = new Int32Array(moveBuffer.buffer.slice(moveBuffer.byteOffset, moveBuffer.byteOffset + moveBuffer.byteLength));
|
|
227
|
+
}
|
|
228
|
+
return dense;
|
|
229
|
+
}
|
|
230
|
+
function loadC3Pair(assetDirectory, hcManifest) {
|
|
231
|
+
const filePath = path.join(assetDirectory, "c3_pair_dist.bin");
|
|
232
|
+
const manifestPath = path.join(assetDirectory, "hc-manifest.json");
|
|
233
|
+
if (!hcManifest || hcManifest.c3PairDist === undefined) {
|
|
234
|
+
// Optional table: absent = not deployed (fine). Present but unsealed =
|
|
235
|
+
// the historical silent-corruption hole — refuse to load it unchecked.
|
|
236
|
+
if (existsSync(filePath)) {
|
|
237
|
+
throw new Error(`${filePath} exists but ${manifestPath} has no "c3PairDist" checksum entry — refusing to load it ` +
|
|
238
|
+
"unchecked. Regenerate the manifest: pnpm cube4:hc:tables.");
|
|
239
|
+
}
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
const checked = requireEntry(hcManifest, manifestPath, "c3PairDist");
|
|
243
|
+
if (typeof checked.bytes === "number" && checked.bytes !== C3_PAIR_DIST_BYTES) {
|
|
244
|
+
throw new Error(`Invalid table manifest ${manifestPath}: c3PairDist.bytes is ${checked.bytes}, expected ` +
|
|
245
|
+
`${C3_PAIR_DIST_BYTES} — run pnpm cube4:hc:tables to regenerate it.`);
|
|
246
|
+
}
|
|
247
|
+
return new Uint8Array(readChecked(path.join(assetDirectory, checked.file), checked.checksum, C3_PAIR_DIST_BYTES));
|
|
85
248
|
}
|
|
86
249
|
/** Load all cube4 reduction tables (throws if the generated assets are missing). */
|
|
87
250
|
export function loadCube4ReductionTables() {
|
|
@@ -89,10 +252,21 @@ export function loadCube4ReductionTables() {
|
|
|
89
252
|
if (!existsSync(path.join(assetDirectory, "center1-manifest.json"))) {
|
|
90
253
|
throw new Error("Cube4 reduction tables not generated — run pnpm cube4:center1:tables etc.");
|
|
91
254
|
}
|
|
92
|
-
|
|
255
|
+
const tables = {
|
|
93
256
|
center1: loadCenter1(assetDirectory),
|
|
94
257
|
center2: loadCenter2(assetDirectory),
|
|
95
258
|
center3: loadCenter3(assetDirectory),
|
|
96
259
|
edge3: loadEdge3(assetDirectory)
|
|
97
260
|
};
|
|
261
|
+
// Shared manifest for the two optional PDB tables; absent = not deployed.
|
|
262
|
+
const hcManifestPath = path.join(assetDirectory, "hc-manifest.json");
|
|
263
|
+
const hcManifest = existsSync(hcManifestPath) ? readManifest(hcManifestPath) : undefined;
|
|
264
|
+
if (hcManifest) {
|
|
265
|
+
tables.hc = loadHc(assetDirectory, hcManifest);
|
|
266
|
+
}
|
|
267
|
+
const c3Pair = loadC3Pair(assetDirectory, hcManifest);
|
|
268
|
+
if (c3Pair)
|
|
269
|
+
tables.c3PairDist = c3Pair;
|
|
270
|
+
assertCube4ReductionTableBudget(tables);
|
|
271
|
+
return tables;
|
|
98
272
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase-2 wing-transversal pruning (two-pair PDB).
|
|
3
|
+
*
|
|
4
|
+
* A phase-2 goal is only usable when the wings are "transversal": every
|
|
5
|
+
* pair has exactly one wing in the second-half positions
|
|
6
|
+
* (`PAIRING_SECOND_HALVES`). Only ~0.1-0.25% of center goals pass, so the
|
|
7
|
+
* phase-2 search used to enumerate millions of center goals to find the
|
|
8
|
+
* transversal ones. This table bounds how many phase-2 moves two given pairs
|
|
9
|
+
* need to become transversal, so the search can prune branches that cannot
|
|
10
|
+
* end transversal within the remaining depth.
|
|
11
|
+
*
|
|
12
|
+
* State: the unordered positions of the two wings of pair X and of pair Y
|
|
13
|
+
* (276 × 276 index; overlapping positions are unreachable). The predicate
|
|
14
|
+
* only depends on positions, so ONE table serves every pair-of-pairs.
|
|
15
|
+
* Exact BFS distances under the given phase-2 move prefix (the search set),
|
|
16
|
+
* hence admissible; generated at runtime (76,176 bytes, a few ms).
|
|
17
|
+
*/
|
|
18
|
+
export declare const PAIR_STATES = 276;
|
|
19
|
+
/** Unordered position pair (a != b) → 0..275, as a flat 24×24 table. */
|
|
20
|
+
export declare const PAIR_INDEX: Uint16Array;
|
|
21
|
+
/**
|
|
22
|
+
* Wing position maps: `posMove[m * 24 + p]` = where the wing at position p
|
|
23
|
+
* goes under CENTER2_MOVES[m] (inverse of the `wp` gather maps).
|
|
24
|
+
*/
|
|
25
|
+
export declare function getWingPositionMoves(): Uint8Array;
|
|
26
|
+
/** Two-pair transversal distances under the first `moveCount` CENTER2_MOVES. */
|
|
27
|
+
export declare function getTransversalPairPdb(moveCount: number): Uint8Array;
|
|
28
|
+
//# sourceMappingURL=pair-transversal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pair-transversal.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube4/reduction/pair-transversal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,eAAO,MAAM,WAAW,MAAM,CAAC;AAE/B,wEAAwE;AACxE,eAAO,MAAM,UAAU,EAAE,WAWrB,CAAC;AAeL;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,UAAU,CASjD;AAKD,gFAAgF;AAChF,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAsCnE"}
|