@cubingcenter/scrambler 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +15 -4
  2. package/dist/core/api.d.ts.map +1 -1
  3. package/dist/core/api.js +10 -2
  4. package/dist/puzzles/cube2/solver/browser-loader.d.ts +4 -7
  5. package/dist/puzzles/cube2/solver/browser-loader.d.ts.map +1 -1
  6. package/dist/puzzles/cube2/solver/browser-loader.js +42 -14
  7. package/dist/puzzles/cube2/solver/generated-assets.d.ts.map +1 -1
  8. package/dist/puzzles/cube2/solver/generated-assets.js +8 -3
  9. package/dist/puzzles/cube3/solver/browser-loader.d.ts.map +1 -1
  10. package/dist/puzzles/cube3/solver/browser-loader.js +42 -20
  11. package/dist/puzzles/cube3/solver/generated-assets.d.ts.map +1 -1
  12. package/dist/puzzles/cube3/solver/generated-assets.js +9 -3
  13. package/dist/puzzles/cube3/solver/phase1.d.ts +19 -0
  14. package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
  15. package/dist/puzzles/cube3/solver/phase1.js +29 -6
  16. package/dist/puzzles/cube3/solver/phase2.d.ts +28 -0
  17. package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
  18. package/dist/puzzles/cube3/solver/phase2.js +56 -15
  19. package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
  20. package/dist/puzzles/cube3/solver/tables.js +14 -12
  21. package/dist/puzzles/cube4/browser-worker-pool.d.ts +82 -0
  22. package/dist/puzzles/cube4/browser-worker-pool.d.ts.map +1 -0
  23. package/dist/puzzles/cube4/browser-worker-pool.js +205 -0
  24. package/dist/puzzles/cube4/browser-worker.d.ts +2 -0
  25. package/dist/puzzles/cube4/browser-worker.d.ts.map +1 -0
  26. package/dist/puzzles/cube4/browser-worker.js +31 -0
  27. package/dist/puzzles/cube4/index.d.ts +7 -0
  28. package/dist/puzzles/cube4/index.d.ts.map +1 -1
  29. package/dist/puzzles/cube4/index.js +4 -0
  30. package/dist/puzzles/cube4/moves.d.ts.map +1 -1
  31. package/dist/puzzles/cube4/moves.js +122 -30
  32. package/dist/puzzles/cube4/reduction/browser-loader.d.ts +26 -1
  33. package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -1
  34. package/dist/puzzles/cube4/reduction/browser-loader.js +212 -48
  35. package/dist/puzzles/cube4/reduction/center2.d.ts +24 -0
  36. package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -1
  37. package/dist/puzzles/cube4/reduction/center2.js +99 -1
  38. package/dist/puzzles/cube4/reduction/finish3.d.ts +53 -0
  39. package/dist/puzzles/cube4/reduction/finish3.d.ts.map +1 -0
  40. package/dist/puzzles/cube4/reduction/finish3.js +393 -0
  41. package/dist/puzzles/cube4/reduction/generated/hc-manifest.json +6 -0
  42. package/dist/puzzles/cube4/reduction/hc-pdb.d.ts +5 -3
  43. package/dist/puzzles/cube4/reduction/hc-pdb.d.ts.map +1 -1
  44. package/dist/puzzles/cube4/reduction/hc-pdb.js +30 -12
  45. package/dist/puzzles/cube4/reduction/node-loader.d.ts +10 -1
  46. package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -1
  47. package/dist/puzzles/cube4/reduction/node-loader.js +170 -36
  48. package/dist/puzzles/cube4/reduction/pair-transversal.d.ts +28 -0
  49. package/dist/puzzles/cube4/reduction/pair-transversal.d.ts.map +1 -0
  50. package/dist/puzzles/cube4/reduction/pair-transversal.js +107 -0
  51. package/dist/puzzles/cube4/reduction/phase3-search.d.ts +33 -30
  52. package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -1
  53. package/dist/puzzles/cube4/reduction/phase3-search.js +211 -104
  54. package/dist/puzzles/cube4/reduction/rotation-fix.d.ts +28 -0
  55. package/dist/puzzles/cube4/reduction/rotation-fix.d.ts.map +1 -0
  56. package/dist/puzzles/cube4/reduction/rotation-fix.js +212 -0
  57. package/dist/puzzles/cube4/reduction/solve.d.ts +72 -13
  58. package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -1
  59. package/dist/puzzles/cube4/reduction/solve.js +374 -92
  60. package/dist/puzzles/cube4/scramble-cache.d.ts +93 -0
  61. package/dist/puzzles/cube4/scramble-cache.d.ts.map +1 -0
  62. package/dist/puzzles/cube4/scramble-cache.js +150 -0
  63. package/dist/puzzles/cube4/scrambler.d.ts +112 -18
  64. package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
  65. package/dist/puzzles/cube4/scrambler.js +271 -83
  66. package/dist/puzzles/cube4/worker-pool.d.ts +139 -0
  67. package/dist/puzzles/cube4/worker-pool.d.ts.map +1 -0
  68. package/dist/puzzles/cube4/worker-pool.js +405 -0
  69. package/dist/puzzles/cube4/worker-protocol.d.ts +58 -0
  70. package/dist/puzzles/cube4/worker-protocol.d.ts.map +1 -0
  71. package/dist/puzzles/cube4/worker-protocol.js +58 -0
  72. package/dist/puzzles/cube4/worker.d.ts +26 -0
  73. package/dist/puzzles/cube4/worker.d.ts.map +1 -0
  74. package/dist/puzzles/cube4/worker.js +49 -0
  75. package/dist/puzzles/pyraminx/solver/browser-loader.d.ts +4 -8
  76. package/dist/puzzles/pyraminx/solver/browser-loader.d.ts.map +1 -1
  77. package/dist/puzzles/pyraminx/solver/browser-loader.js +44 -15
  78. package/dist/puzzles/pyraminx/solver/generated-assets.d.ts.map +1 -1
  79. package/dist/puzzles/pyraminx/solver/generated-assets.js +8 -3
  80. package/dist/puzzles/shared/compressed-fetch.d.ts +37 -1
  81. package/dist/puzzles/shared/compressed-fetch.d.ts.map +1 -1
  82. package/dist/puzzles/shared/compressed-fetch.js +226 -31
  83. package/dist/puzzles/shared/load-once.d.ts +16 -0
  84. package/dist/puzzles/shared/load-once.d.ts.map +1 -0
  85. package/dist/puzzles/shared/load-once.js +11 -0
  86. package/dist/puzzles/skewb/solver/browser-loader.d.ts +4 -11
  87. package/dist/puzzles/skewb/solver/browser-loader.d.ts.map +1 -1
  88. package/dist/puzzles/skewb/solver/browser-loader.js +44 -16
  89. package/dist/puzzles/skewb/solver/generated-assets.d.ts.map +1 -1
  90. package/dist/puzzles/skewb/solver/generated-assets.js +8 -3
  91. package/dist/puzzles/sq1/solver/browser-loader.d.ts.map +1 -1
  92. package/dist/puzzles/sq1/solver/browser-loader.js +58 -26
  93. package/dist-cjs/core/api.js +10 -2
  94. package/dist-cjs/puzzles/cube2/solver/browser-loader.js +42 -14
  95. package/dist-cjs/puzzles/cube2/solver/generated-assets.js +8 -3
  96. package/dist-cjs/puzzles/cube3/solver/browser-loader.js +42 -20
  97. package/dist-cjs/puzzles/cube3/solver/generated-assets.js +9 -3
  98. package/dist-cjs/puzzles/cube3/solver/phase1.js +29 -6
  99. package/dist-cjs/puzzles/cube3/solver/phase2.js +56 -15
  100. package/dist-cjs/puzzles/cube3/solver/tables.js +14 -12
  101. package/dist-cjs/puzzles/cube4/browser-worker-pool.js +211 -0
  102. package/dist-cjs/puzzles/cube4/index.js +21 -1
  103. package/dist-cjs/puzzles/cube4/moves.js +122 -30
  104. package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +212 -48
  105. package/dist-cjs/puzzles/cube4/reduction/center2.js +102 -2
  106. package/dist-cjs/puzzles/cube4/reduction/finish3.js +398 -0
  107. package/dist-cjs/puzzles/cube4/reduction/generated/hc-manifest.json +6 -0
  108. package/dist-cjs/puzzles/cube4/reduction/hc-pdb.js +31 -12
  109. package/dist-cjs/puzzles/cube4/reduction/node-loader.js +171 -36
  110. package/dist-cjs/puzzles/cube4/reduction/pair-transversal.js +112 -0
  111. package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +211 -104
  112. package/dist-cjs/puzzles/cube4/reduction/rotation-fix.js +216 -0
  113. package/dist-cjs/puzzles/cube4/reduction/solve.js +374 -88
  114. package/dist-cjs/puzzles/cube4/scramble-cache.js +156 -0
  115. package/dist-cjs/puzzles/cube4/scrambler.js +271 -80
  116. package/dist-cjs/puzzles/cube4/worker-pool.js +447 -0
  117. package/dist-cjs/puzzles/cube4/worker-protocol.js +62 -0
  118. package/dist-cjs/puzzles/pyraminx/solver/browser-loader.js +44 -15
  119. package/dist-cjs/puzzles/pyraminx/solver/generated-assets.js +8 -3
  120. package/dist-cjs/puzzles/shared/compressed-fetch.js +229 -31
  121. package/dist-cjs/puzzles/shared/load-once.js +14 -0
  122. package/dist-cjs/puzzles/skewb/solver/browser-loader.js +44 -16
  123. package/dist-cjs/puzzles/skewb/solver/generated-assets.js +8 -3
  124. package/dist-cjs/puzzles/sq1/solver/browser-loader.js +58 -26
  125. package/docs/development.md +19 -7
  126. package/package.json +4 -3
@@ -0,0 +1,398 @@
1
+ "use strict";
2
+ /**
3
+ * Continuous two-phase 3x3 solver for the 4x4 finish (Kociemba-style).
4
+ *
5
+ * Unlike the batch solver in `cube3/solver` (collect phase-1 solutions,
6
+ * then solve phase 2 for each), this is the classic continuous search: an
7
+ * iterative-deepening phase 1 where EVERY phase-1 solution immediately runs
8
+ * phase 2 under the remaining length budget, and every solution found
9
+ * tightens the bound for the rest of the search. Optional legal views are
10
+ * searched interleaved by phase-1 depth — up to 3 relabelled axes
11
+ * (identity, x, z) × {state, inverse} — which is what makes short solutions
12
+ * cheap to find (the existing cube3 solver's rotation/inverse trick).
13
+ *
14
+ * Tables: the already-loaded cube3 tables (phase-1: twist/flip/slice move
15
+ * tables + twist×flip, slice×twist, slice×flip pruning; phase-2: cp/ep/sp
16
+ * move tables + cp×sp, ep×sp pruning). Nothing is generated here.
17
+ *
18
+ * Deterministic: the only stopping rules are node counts and lengths, so
19
+ * the result is a pure function of (state, options).
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.Finish3SearchError = void 0;
23
+ exports.solveFinish3 = solveFinish3;
24
+ const types_js_1 = require("../../cube3/types.js");
25
+ const moves_js_1 = require("../../cube3/moves.js");
26
+ const state_js_1 = require("../../cube3/state.js");
27
+ const moves_js_2 = require("../moves.js");
28
+ const state_js_2 = require("../state.js");
29
+ const phase4_js_1 = require("./phase4.js");
30
+ const coordinates_js_1 = require("../../cube3/solver/coordinates.js");
31
+ const tables_js_1 = require("../../cube3/solver/tables.js");
32
+ const symmetry_js_1 = require("../../cube3/solver/symmetry.js");
33
+ class Finish3SearchError extends Error {
34
+ code = "cube4-finish3-cap";
35
+ constructor(message) {
36
+ super(message);
37
+ this.name = "Finish3SearchError";
38
+ }
39
+ }
40
+ exports.Finish3SearchError = Finish3SearchError;
41
+ function toCubie(state) {
42
+ return {
43
+ cp: Int8Array.from(state.corners.permutation),
44
+ co: Int8Array.from(state.corners.orientation),
45
+ ep: Int8Array.from(state.edges.permutation),
46
+ eo: Int8Array.from(state.edges.orientation)
47
+ };
48
+ }
49
+ function toState(cubie) {
50
+ return {
51
+ corners: { permutation: Array.from(cubie.cp), orientation: Array.from(cubie.co) },
52
+ edges: { permutation: Array.from(cubie.ep), orientation: Array.from(cubie.eo) }
53
+ };
54
+ }
55
+ function inverse(a) {
56
+ const out = { cp: new Int8Array(8), co: new Int8Array(8), ep: new Int8Array(12), eo: new Int8Array(12) };
57
+ for (let i = 0; i < 8; i++) {
58
+ out.cp[a.cp[i]] = i;
59
+ out.co[a.cp[i]] = (3 - a.co[i]) % 3;
60
+ }
61
+ for (let i = 0; i < 12; i++) {
62
+ out.ep[a.ep[i]] = i;
63
+ out.eo[a.ep[i]] = a.eo[i];
64
+ }
65
+ return out;
66
+ }
67
+ /** Per-move permutation effects, as the solved state with the move applied. */
68
+ const MOVE_CUBIES = types_js_1.CUBE3_MOVES.map((move) => toCubie((0, moves_js_1.applyCube3Move)((0, state_js_1.createSolvedCube3State)(), move)));
69
+ const MOVE_CP = Int8Array.from(MOVE_CUBIES.flatMap((c) => Array.from(c.cp)));
70
+ const MOVE_EP = Int8Array.from(MOVE_CUBIES.flatMap((c) => Array.from(c.ep)));
71
+ /** Face (U0 D1 R2 L3 F4 B5) of each phase-1 move index. */
72
+ const FACE = Int8Array.from(types_js_1.CUBE3_MOVES.map((_, m) => Math.floor(m / 3)));
73
+ /** Phase-1 move index of each phase-2 move index. */
74
+ const P2_TO_P1 = Int8Array.from(tables_js_1.CUBE3_PHASE2_MOVES.map((move) => types_js_1.CUBE3_MOVES.indexOf(move)));
75
+ /** Phase-1 moves that keep G1 (U, D, R2, L2, F2, B2). */
76
+ const IS_P2_MOVE = new Uint8Array(18);
77
+ for (const m of P2_TO_P1)
78
+ IS_P2_MOVE[m] = 1;
79
+ /** Skip `face` after `last`: same face, or same axis in descending order. */
80
+ const SKIP = new Uint8Array(7 * 6);
81
+ for (let last = -1; last < 6; last++) {
82
+ for (let face = 0; face < 6; face++) {
83
+ SKIP[(last + 1) * 6 + face] =
84
+ last >= 0 && (face === last || ((face >> 1) === (last >> 1) && face < last)) ? 1 : 0;
85
+ }
86
+ }
87
+ const FACT = [1, 1, 2, 6, 24, 120, 720, 5040, 40320];
88
+ /** Lehmer rank of values[offset..offset+n) (same order as `encodePermutation`). */
89
+ function lehmer(values, offset, n, base) {
90
+ let rank = 0;
91
+ for (let i = 0; i < n; i++) {
92
+ const v = values[offset + i];
93
+ let smaller = 0;
94
+ for (let j = i + 1; j < n; j++)
95
+ if (values[offset + j] < v)
96
+ smaller++;
97
+ rank += smaller * FACT[n - 1 - i];
98
+ }
99
+ return rank - base * 0;
100
+ }
101
+ // ---------------------------------------------------------------------------
102
+ // Views (relabelled scrambles and inverses)
103
+ // ---------------------------------------------------------------------------
104
+ const VIEW_ROTATIONS = [[], ["x"], ["z"]];
105
+ function invertMoves(moves) {
106
+ return [...moves].reverse().map((move) => {
107
+ if (move.endsWith("2"))
108
+ return move;
109
+ if (move.endsWith("'"))
110
+ return move.slice(0, -1);
111
+ return `${move}'`;
112
+ });
113
+ }
114
+ function multiply(a, b) {
115
+ const out = { cp: new Int8Array(8), co: new Int8Array(8), ep: new Int8Array(12), eo: new Int8Array(12) };
116
+ for (let i = 0; i < 8; i++) {
117
+ out.cp[i] = a.cp[b.cp[i]];
118
+ out.co[i] = (a.co[b.cp[i]] + b.co[i]) % 3;
119
+ }
120
+ for (let i = 0; i < 12; i++) {
121
+ out.ep[i] = a.ep[b.ep[i]];
122
+ out.eo[i] = (a.eo[b.ep[i]] + b.eo[i]) % 2;
123
+ }
124
+ return out;
125
+ }
126
+ /** 3x3 cubies of the whole-cube rotations, read off the rotated 4x4 model. */
127
+ const ROTATION_WORDS = {
128
+ x: ["Rw", "Lw'"],
129
+ z: ["Fw", "Bw'"]
130
+ };
131
+ const ROTATION_CUBIES = new Map(Object.entries(ROTATION_WORDS).map(([name, word]) => [
132
+ name,
133
+ toCubie((0, phase4_js_1.toCube3State)((0, moves_js_2.applyCube4Algorithm)((0, state_js_2.createSolvedCube4State)(), word)))
134
+ ]));
135
+ /**
136
+ * Views of the state: conjugations by x / z and inverses. The x-view of S
137
+ * is R⁻¹·S·R (R = the rotation's cubie) — the same state the relabelled
138
+ * scramble `rotate(scramble(S), [x])` reaches (verified in
139
+ * test/cube4-finish3.test.ts), so a view solution maps back by relabelling
140
+ * with the inverse rotation. Conjugation preserves parity and legality.
141
+ */
142
+ function buildViews(state, count) {
143
+ if (count !== 1 && count !== 2 && count !== 6) {
144
+ throw new RangeError(`finish3: unsupported view count ${count}`);
145
+ }
146
+ const base = toCubie(state);
147
+ const views = [];
148
+ const axes = count === 6 ? VIEW_ROTATIONS : [[]];
149
+ for (const rotation of axes) {
150
+ let viewCubie = base;
151
+ for (const r of rotation) {
152
+ const rc = ROTATION_CUBIES.get(r);
153
+ viewCubie = multiply(multiply(inverse(rc), viewCubie), rc);
154
+ }
155
+ const makeView = (inverted) => {
156
+ views.push({
157
+ cubie: inverted ? inverse(viewCubie) : viewCubie,
158
+ mapSolution: (moves) => {
159
+ const direct = inverted ? invertMoves(moves) : [...moves];
160
+ return rotation.length === 0
161
+ ? direct
162
+ : (0, symmetry_js_1.rotateCube3AlgorithmBySequence)(direct, (0, symmetry_js_1.invertCube3RotationSequence)(rotation));
163
+ }
164
+ });
165
+ };
166
+ makeView(false);
167
+ if (count >= 2)
168
+ makeView(true);
169
+ }
170
+ return views;
171
+ }
172
+ // ---------------------------------------------------------------------------
173
+ // Search
174
+ // ---------------------------------------------------------------------------
175
+ function solveFinish3(state, tables, options = {}) {
176
+ const targetLength = options.targetLength ?? 0;
177
+ const maxNodes = options.maxNodes ?? 200_000;
178
+ const hardMaxNodes = options.hardMaxNodes ?? 20_000_000;
179
+ const maxP2 = options.maxPhase2Length ?? 12;
180
+ const views = buildViews(state, options.views ?? 1);
181
+ const p1 = tables.phase1;
182
+ const coMove = p1.moveTables.cornerOrientation.values;
183
+ const eoMove = p1.moveTables.edgeOrientation.values;
184
+ const udMove = p1.moveTables.udSlice.values;
185
+ const udSolved = p1.moveTables.udSlice.solvedCoordinate;
186
+ const coeo = p1.pruningTables.cornerOrientationEdgeOrientation.values;
187
+ const udPrun = p1.pruningTables.udSlice.values;
188
+ const jointUdCo = p1.jointTables?.jointUdCo;
189
+ const jointUdEo = p1.jointTables?.jointUdEo;
190
+ const p2 = tables.phase2;
191
+ if (p2.phase2Moves.join(" ") !== tables_js_1.CUBE3_PHASE2_MOVES.join(" ")) {
192
+ throw new Error("finish3: unexpected phase-2 move order");
193
+ }
194
+ const cpMove = p2.moveTables.cornerPermutation.values;
195
+ const epMove = p2.moveTables.udEdgePermutation.values;
196
+ const spMove = p2.moveTables.slicePermutation.values;
197
+ const cpsp = p2.pruningTables.cornerPermutationSlicePermutation.values;
198
+ const epsp = p2.pruningTables.udEdgePermutationSlicePermutation.values;
199
+ const nib = (table, index) => (table[index >> 1] >> ((index & 1) << 2)) & 0x0f;
200
+ const h1 = (co, eo, ud) => {
201
+ let h = nib(coeo, (co << 11) + eo);
202
+ if (jointUdCo !== undefined && jointUdEo !== undefined) {
203
+ const a = nib(jointUdCo, ud * 2187 + co);
204
+ if (a > h)
205
+ h = a;
206
+ const b = nib(jointUdEo, ud * 2048 + eo);
207
+ if (b > h)
208
+ h = b;
209
+ }
210
+ else {
211
+ const u = nib(udPrun, ud);
212
+ if (u > h)
213
+ h = u;
214
+ }
215
+ return h;
216
+ };
217
+ const h2 = (cp, ep, sp) => {
218
+ const a = nib(cpsp, cp * 24 + sp);
219
+ const b = nib(epsp, ep * 24 + sp);
220
+ return a > b ? a : b;
221
+ };
222
+ const path1 = new Int8Array(32);
223
+ const path2 = new Int8Array(32);
224
+ const cpScratch = new Int8Array(8);
225
+ const epScratch = new Int8Array(12);
226
+ const cpTmp = new Int8Array(8);
227
+ const epTmp = new Int8Array(12);
228
+ let nodes = 0;
229
+ let bound = 99; // best length found so far (solutions must be shorter)
230
+ let best = null;
231
+ let stop = false;
232
+ let view = views[0];
233
+ // Hot-path guard: `shouldStop` can only change its answer once `nodes`
234
+ // passes this limit (the other conditions are re-checked in `leaf`).
235
+ let nodeLimit = hardMaxNodes;
236
+ const hasJoint = jointUdCo !== undefined && jointUdEo !== undefined;
237
+ const shouldStop = () => {
238
+ if (nodes > hardMaxNodes) {
239
+ if (best === null) {
240
+ throw new Finish3SearchError("finish3: node cap exceeded without a solution");
241
+ }
242
+ stop = true;
243
+ return true;
244
+ }
245
+ if (best !== null)
246
+ nodeLimit = Math.min(maxNodes, hardMaxNodes);
247
+ if (best !== null && (nodes > maxNodes || bound <= targetLength)) {
248
+ stop = true;
249
+ return true;
250
+ }
251
+ return false;
252
+ };
253
+ const phase2 = (cp, ep, sp, depth, togo, last) => {
254
+ nodes++;
255
+ if (nodes > nodeLimit && shouldStop())
256
+ return false;
257
+ if (togo === 0)
258
+ return cp === 0 && ep === 0 && sp === 0;
259
+ const skipRow = (last + 1) * 6;
260
+ const limit = togo - 1;
261
+ const cpRow = cp * 10;
262
+ const epRow = ep * 10;
263
+ const spRow = sp * 10;
264
+ for (let i = 0; i < 10; i++) {
265
+ if (stop)
266
+ return false;
267
+ const m = P2_TO_P1[i];
268
+ if (SKIP[skipRow + FACE[m]])
269
+ continue;
270
+ const nsp = spMove[spRow + i];
271
+ const ncp = cpMove[cpRow + i];
272
+ const ia = ncp * 24 + nsp;
273
+ if (((cpsp[ia >> 1] >> ((ia & 1) << 2)) & 0x0f) > limit)
274
+ continue;
275
+ const nep = epMove[epRow + i];
276
+ const ib = nep * 24 + nsp;
277
+ if (((epsp[ib >> 1] >> ((ib & 1) << 2)) & 0x0f) > limit)
278
+ continue;
279
+ path2[depth] = m;
280
+ if (phase2(ncp, nep, nsp, depth + 1, togo - 1, FACE[m]))
281
+ return true;
282
+ }
283
+ return false;
284
+ };
285
+ /** Runs phase 2 on a phase-1 solution of length d1; true = stop searching. */
286
+ const leaf = (d1) => {
287
+ const base = view.cubie;
288
+ cpScratch.set(base.cp);
289
+ epScratch.set(base.ep);
290
+ for (let k = 0; k < d1; k++) {
291
+ const off = path1[k];
292
+ for (let i = 0; i < 8; i++)
293
+ cpTmp[i] = cpScratch[MOVE_CP[off * 8 + i]];
294
+ for (let i = 0; i < 12; i++)
295
+ epTmp[i] = epScratch[MOVE_EP[off * 12 + i]];
296
+ cpScratch.set(cpTmp);
297
+ epScratch.set(epTmp);
298
+ }
299
+ const cp = lehmer(cpScratch, 0, 8, 0);
300
+ const ep = lehmer(epScratch, 0, 8, 0);
301
+ for (let i = 8; i < 12; i++)
302
+ epTmp[i] = epScratch[i] - 8;
303
+ const sp = lehmer(epTmp, 8, 4, 0);
304
+ const lb = h2(cp, ep, sp);
305
+ const maxD2 = Math.min(bound - 1 - d1, maxP2);
306
+ if (lb > maxD2)
307
+ return false;
308
+ const last = d1 > 0 ? FACE[path1[d1 - 1]] : -1;
309
+ for (let d2 = lb; d2 <= maxD2; d2++) {
310
+ if (phase2(cp, ep, sp, 0, d2, last)) {
311
+ const moves = [];
312
+ for (let k = 0; k < d1; k++)
313
+ moves.push(types_js_1.CUBE3_MOVES[path1[k]]);
314
+ for (let k = 0; k < d2; k++)
315
+ moves.push(types_js_1.CUBE3_MOVES[path2[k]]);
316
+ best = view.mapSolution(moves);
317
+ bound = d1 + d2;
318
+ break;
319
+ }
320
+ }
321
+ return shouldStop();
322
+ };
323
+ const phase1 = (co, eo, ud, depth, togo, last) => {
324
+ nodes++;
325
+ if (togo === 0) {
326
+ // Only phase-1 solutions ending in a quarter turn of R/L/F/B: one
327
+ // ending in a G1 move is a shorter phase-1 solution plus phase 2.
328
+ if (depth > 0 && IS_P2_MOVE[path1[depth - 1]])
329
+ return false;
330
+ return leaf(depth);
331
+ }
332
+ if ((nodes & 1023) === 0 && shouldStop())
333
+ return true;
334
+ const skipRow = (last + 1) * 6;
335
+ const limit = togo - 1;
336
+ const lastLevel = togo === 1;
337
+ const coRow = co * 18;
338
+ const eoRow = eo * 18;
339
+ const udRow = ud * 18;
340
+ for (let m = 0; m < 18; m++) {
341
+ if (stop)
342
+ return true;
343
+ if (SKIP[skipRow + FACE[m]])
344
+ continue;
345
+ if (lastLevel && IS_P2_MOVE[m])
346
+ continue;
347
+ const nud = udMove[udRow + m];
348
+ const nco = coMove[coRow + m];
349
+ const neo = eoMove[eoRow + m];
350
+ if (hasJoint) {
351
+ const ia = nud * 2187 + nco;
352
+ if (((jointUdCo[ia >> 1] >> ((ia & 1) << 2)) & 0x0f) > limit)
353
+ continue;
354
+ const ib = nud * 2048 + neo;
355
+ if (((jointUdEo[ib >> 1] >> ((ib & 1) << 2)) & 0x0f) > limit)
356
+ continue;
357
+ const ic = (nco << 11) + neo;
358
+ if (((coeo[ic >> 1] >> ((ic & 1) << 2)) & 0x0f) > limit)
359
+ continue;
360
+ }
361
+ else if (h1(nco, neo, nud) > limit)
362
+ continue;
363
+ path1[depth] = m;
364
+ if (phase1(nco, neo, nud, depth + 1, togo - 1, FACE[m]))
365
+ return true;
366
+ }
367
+ return false;
368
+ };
369
+ const roots = views.map((v) => {
370
+ const s = toState(v.cubie);
371
+ const co = (0, coordinates_js_1.encodeCornerOrientation)(s);
372
+ const eo = (0, coordinates_js_1.encodeEdgeOrientation)(s);
373
+ const ud = (0, coordinates_js_1.encodeUdSlice)(s);
374
+ return { co, eo, ud, h: h1(co, eo, ud), solvedG1: co === 0 && eo === 0 && ud === udSolved };
375
+ });
376
+ let minDepth = 99;
377
+ for (const r of roots)
378
+ if (r.h < minDepth)
379
+ minDepth = r.h;
380
+ for (let d1 = minDepth; d1 < bound && !stop; d1++) {
381
+ for (let v = 0; v < views.length && !stop; v++) {
382
+ const r = roots[v];
383
+ if (r.h > d1)
384
+ continue;
385
+ view = views[v];
386
+ if (d1 === 0) {
387
+ if (r.solvedG1 && leaf(0))
388
+ stop = true;
389
+ continue;
390
+ }
391
+ if (phase1(r.co, r.eo, r.ud, 0, d1, -1))
392
+ stop = true;
393
+ }
394
+ }
395
+ if (best === null)
396
+ throw new Finish3SearchError("finish3: no solution found");
397
+ return { moves: best, nodes };
398
+ }
@@ -34,5 +34,11 @@
34
34
  "file": "hc-move.bin",
35
35
  "range": 999600,
36
36
  "checksum": "6bd1d8f36b77e0863425bced1dfb69df7a13dde4fb18ceeaeba00abf14519922"
37
+ },
38
+ "c3PairDist": {
39
+ "file": "c3_pair_dist.bin",
40
+ "range": 4233600,
41
+ "bytes": 2116800,
42
+ "checksum": "2d070dcd9cfa8ede09cf0def93b3b2ba05a7e7e5acf631d6bf25a65bf0057035"
37
43
  }
38
44
  }
@@ -25,6 +25,7 @@ exports.getHcPdbMoveMaps = getHcPdbMoveMaps;
25
25
  exports.hcPdbDistance = hcPdbDistance;
26
26
  exports.generateHcPdb = generateHcPdb;
27
27
  exports.buildHcDense = buildHcDense;
28
+ exports.hcDenseIndex = hcDenseIndex;
28
29
  exports.generateHcDenseMoves = generateHcDenseMoves;
29
30
  const center3_js_1 = require("./center3.js");
30
31
  const moves_js_1 = require("../moves.js");
@@ -174,30 +175,48 @@ function generateHcPdb(onProgress) {
174
175
  }
175
176
  /** Number of reachable states (BFS orbit size). Fixed by the move set. */
176
177
  exports.HC_REACHABLE = 58800;
177
- /** Build the dense index + unpacked distances from nibble-packed BFS output. */
178
+ /** Build the compact coordinate list + unpacked distances from the packed PDB. */
178
179
  function buildHcDense(packed) {
179
- const index = new Int32Array(exports.HC_PDB_STATES).fill(-1);
180
180
  let count = 0;
181
181
  for (let c = 0; c < exports.HC_PDB_STATES; c++) {
182
182
  if (hcPdbDistance(packed, c) !== 0x0f)
183
- index[c] = count++;
183
+ count++;
184
184
  }
185
+ const coordinates = new Uint32Array(count);
185
186
  const distances = new Uint8Array(count).fill(0x0f);
187
+ let denseIndex = 0;
186
188
  for (let c = 0; c < exports.HC_PDB_STATES; c++) {
187
- const di = index[c];
188
- if (di >= 0)
189
- distances[di] = hcPdbDistance(packed, c);
189
+ const distance = hcPdbDistance(packed, c);
190
+ if (distance === 0x0f)
191
+ continue;
192
+ coordinates[denseIndex] = c;
193
+ distances[denseIndex] = distance;
194
+ denseIndex++;
195
+ }
196
+ return { coordinates, moves: new Int32Array(0), distances, reachableCount: count };
197
+ }
198
+ /** Full HC coordinate -> dense index, or -1 when it is unreachable. */
199
+ function hcDenseIndex(dense, coordinate) {
200
+ let low = 0;
201
+ let high = dense.coordinates.length - 1;
202
+ while (low <= high) {
203
+ const middle = (low + high) >>> 1;
204
+ const value = dense.coordinates[middle];
205
+ if (value === coordinate)
206
+ return middle;
207
+ if (value < coordinate)
208
+ low = middle + 1;
209
+ else
210
+ high = middle - 1;
190
211
  }
191
- return { index, moves: new Int32Array(0), distances, reachableCount: count };
212
+ return -1;
192
213
  }
193
214
  /** Dense transition table: for each reachable state and move, dense index (or -1). */
194
215
  function generateHcDenseMoves(dense) {
195
216
  const maps = getHcPdbMoveMaps();
196
217
  const moves = new Int32Array(dense.reachableCount * exports.HC_PDB_MOVES.length).fill(-1);
197
- for (let c = 0; c < exports.HC_PDB_STATES; c++) {
198
- const di = dense.index[c];
199
- if (di < 0)
200
- continue;
218
+ for (let di = 0; di < dense.coordinates.length; di++) {
219
+ const c = dense.coordinates[di];
201
220
  const mask = hcUnrank(c);
202
221
  for (let m = 0; m < exports.HC_PDB_MOVES.length; m++) {
203
222
  const base = m * 24;
@@ -206,7 +225,7 @@ function generateHcDenseMoves(dense) {
206
225
  if (mask & (1 << maps[base + i]))
207
226
  nmask |= 1 << i;
208
227
  }
209
- moves[di * exports.HC_PDB_MOVES.length + m] = dense.index[hcRank(nmask)];
228
+ moves[di * exports.HC_PDB_MOVES.length + m] = hcDenseIndex(dense, hcRank(nmask));
210
229
  }
211
230
  }
212
231
  return moves;