@cubingcenter/scrambler 0.5.3 → 0.6.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 (244) hide show
  1. package/README.md +24 -9
  2. package/dist/cli/index.js +0 -0
  3. package/dist/core/api.d.ts.map +1 -1
  4. package/dist/core/api.js +11 -1
  5. package/dist/core/types.d.ts +1 -1
  6. package/dist/core/types.d.ts.map +1 -1
  7. package/dist/puzzles/cube4/index.d.ts +4 -2
  8. package/dist/puzzles/cube4/index.d.ts.map +1 -1
  9. package/dist/puzzles/cube4/index.js +3 -1
  10. package/dist/puzzles/cube4/random-state.d.ts +16 -0
  11. package/dist/puzzles/cube4/random-state.d.ts.map +1 -0
  12. package/dist/puzzles/cube4/random-state.js +76 -0
  13. package/dist/puzzles/cube4/reduction/browser-loader.d.ts +18 -0
  14. package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -0
  15. package/dist/puzzles/cube4/reduction/browser-loader.js +95 -0
  16. package/dist/puzzles/cube4/reduction/center1.d.ts +65 -0
  17. package/dist/puzzles/cube4/reduction/center1.d.ts.map +1 -0
  18. package/dist/puzzles/cube4/reduction/center1.js +245 -0
  19. package/dist/puzzles/cube4/reduction/center2.d.ts +91 -0
  20. package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -0
  21. package/dist/puzzles/cube4/reduction/center2.js +449 -0
  22. package/dist/puzzles/cube4/reduction/center3.d.ts +47 -0
  23. package/dist/puzzles/cube4/reduction/center3.d.ts.map +1 -0
  24. package/dist/puzzles/cube4/reduction/center3.js +278 -0
  25. package/dist/puzzles/cube4/reduction/edge3.d.ts +100 -0
  26. package/dist/puzzles/cube4/reduction/edge3.d.ts.map +1 -0
  27. package/dist/puzzles/cube4/reduction/edge3.js +511 -0
  28. package/dist/puzzles/cube4/reduction/generated/center1-distance.bin +1 -0
  29. package/dist/puzzles/cube4/reduction/generated/center1-manifest.json +46 -0
  30. package/dist/puzzles/cube4/reduction/generated/center1-move-maps.bin +0 -0
  31. package/dist/puzzles/cube4/reduction/generated/center2-ct-move.bin +0 -0
  32. package/dist/puzzles/cube4/reduction/generated/center2-manifest.json +87 -0
  33. package/dist/puzzles/cube4/reduction/generated/center2-pruning.bin +1 -0
  34. package/dist/puzzles/cube4/reduction/generated/center2-rl-move.bin +0 -0
  35. package/dist/puzzles/cube4/reduction/generated/center3-manifest.json +40 -0
  36. package/dist/puzzles/cube4/reduction/generated/center3-move.bin +0 -0
  37. package/dist/puzzles/cube4/reduction/generated/center3-pruning.bin +0 -0
  38. package/dist/puzzles/cube4/reduction/generated/edge3-distances.bin +1 -0
  39. package/dist/puzzles/cube4/reduction/generated/edge3-manifest.json +12 -0
  40. package/dist/puzzles/cube4/reduction/geometry.d.ts +33 -0
  41. package/dist/puzzles/cube4/reduction/geometry.d.ts.map +1 -0
  42. package/dist/puzzles/cube4/reduction/geometry.js +302 -0
  43. package/dist/puzzles/cube4/reduction/node-loader.d.ts +17 -0
  44. package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -0
  45. package/dist/puzzles/cube4/reduction/node-loader.js +98 -0
  46. package/dist/puzzles/cube4/reduction/pairing-coordinate.d.ts +130 -0
  47. package/dist/puzzles/cube4/reduction/pairing-coordinate.d.ts.map +1 -0
  48. package/dist/puzzles/cube4/reduction/pairing-coordinate.js +281 -0
  49. package/dist/puzzles/cube4/reduction/phase3-search.d.ts +42 -0
  50. package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -0
  51. package/dist/puzzles/cube4/reduction/phase3-search.js +133 -0
  52. package/dist/puzzles/cube4/reduction/phase3.d.ts +34 -0
  53. package/dist/puzzles/cube4/reduction/phase3.d.ts.map +1 -0
  54. package/dist/puzzles/cube4/reduction/phase3.js +83 -0
  55. package/dist/puzzles/cube4/reduction/phase4.d.ts +35 -0
  56. package/dist/puzzles/cube4/reduction/phase4.d.ts.map +1 -0
  57. package/dist/puzzles/cube4/reduction/phase4.js +82 -0
  58. package/dist/puzzles/cube4/reduction/solve.d.ts +31 -0
  59. package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -0
  60. package/dist/puzzles/cube4/reduction/solve.js +311 -0
  61. package/dist/puzzles/cube4/scrambler.d.ts +9 -1
  62. package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
  63. package/dist/puzzles/cube4/scrambler.js +123 -81
  64. package/dist/puzzles/cube4/types.d.ts +6 -0
  65. package/dist/puzzles/cube4/types.d.ts.map +1 -1
  66. package/dist/puzzles/cube4/validation.d.ts +6 -0
  67. package/dist/puzzles/cube4/validation.d.ts.map +1 -0
  68. package/dist/puzzles/cube4/validation.js +91 -0
  69. package/dist/puzzles/fto/coordinates.d.ts +53 -0
  70. package/dist/puzzles/fto/coordinates.d.ts.map +1 -0
  71. package/dist/puzzles/fto/coordinates.js +228 -0
  72. package/dist/puzzles/fto/facelets.d.ts +6 -0
  73. package/dist/puzzles/fto/facelets.d.ts.map +1 -0
  74. package/dist/puzzles/fto/facelets.js +99 -0
  75. package/dist/puzzles/fto/index.d.ts +12 -0
  76. package/dist/puzzles/fto/index.d.ts.map +1 -0
  77. package/dist/puzzles/fto/index.js +13 -0
  78. package/dist/puzzles/fto/moves.d.ts +12 -0
  79. package/dist/puzzles/fto/moves.d.ts.map +1 -0
  80. package/dist/puzzles/fto/moves.js +115 -0
  81. package/dist/puzzles/fto/random-state.d.ts +17 -0
  82. package/dist/puzzles/fto/random-state.d.ts.map +1 -0
  83. package/dist/puzzles/fto/random-state.js +40 -0
  84. package/dist/puzzles/fto/scrambler.d.ts +6 -0
  85. package/dist/puzzles/fto/scrambler.d.ts.map +1 -0
  86. package/dist/puzzles/fto/scrambler.js +49 -0
  87. package/dist/puzzles/fto/solver/engine.d.ts +18 -0
  88. package/dist/puzzles/fto/solver/engine.d.ts.map +1 -0
  89. package/dist/puzzles/fto/solver/engine.js +1168 -0
  90. package/dist/puzzles/fto/solver/index.d.ts +2 -0
  91. package/dist/puzzles/fto/solver/index.d.ts.map +1 -0
  92. package/dist/puzzles/fto/solver/index.js +1 -0
  93. package/dist/puzzles/fto/state.d.ts +16 -0
  94. package/dist/puzzles/fto/state.d.ts.map +1 -0
  95. package/dist/puzzles/fto/state.js +61 -0
  96. package/dist/puzzles/fto/types.d.ts +32 -0
  97. package/dist/puzzles/fto/types.d.ts.map +1 -0
  98. package/dist/puzzles/fto/types.js +46 -0
  99. package/dist/puzzles/fto/validation.d.ts +19 -0
  100. package/dist/puzzles/fto/validation.d.ts.map +1 -0
  101. package/dist/puzzles/fto/validation.js +143 -0
  102. package/dist/puzzles/index.d.ts +2 -0
  103. package/dist/puzzles/index.d.ts.map +1 -1
  104. package/dist/puzzles/index.js +6 -0
  105. package/dist/puzzles/skewb/index.d.ts +1 -1
  106. package/dist/puzzles/skewb/index.d.ts.map +1 -1
  107. package/dist/puzzles/skewb/index.js +1 -1
  108. package/dist/puzzles/skewb/random-state.d.ts.map +1 -1
  109. package/dist/puzzles/skewb/random-state.js +4 -1
  110. package/dist/puzzles/sq1/facelets.d.ts +12 -0
  111. package/dist/puzzles/sq1/facelets.d.ts.map +1 -0
  112. package/dist/puzzles/sq1/facelets.js +26 -0
  113. package/dist/puzzles/sq1/index.d.ts +10 -0
  114. package/dist/puzzles/sq1/index.d.ts.map +1 -0
  115. package/dist/puzzles/sq1/index.js +11 -0
  116. package/dist/puzzles/sq1/moves.d.ts +55 -0
  117. package/dist/puzzles/sq1/moves.d.ts.map +1 -0
  118. package/dist/puzzles/sq1/moves.js +220 -0
  119. package/dist/puzzles/sq1/random-state.d.ts +6 -0
  120. package/dist/puzzles/sq1/random-state.d.ts.map +1 -0
  121. package/dist/puzzles/sq1/random-state.js +49 -0
  122. package/dist/puzzles/sq1/scrambler.d.ts +10 -0
  123. package/dist/puzzles/sq1/scrambler.d.ts.map +1 -0
  124. package/dist/puzzles/sq1/scrambler.js +90 -0
  125. package/dist/puzzles/sq1/solver/browser-loader.d.ts +5 -0
  126. package/dist/puzzles/sq1/solver/browser-loader.d.ts.map +1 -0
  127. package/dist/puzzles/sq1/solver/browser-loader.js +92 -0
  128. package/dist/puzzles/sq1/solver/full-cube.d.ts +53 -0
  129. package/dist/puzzles/sq1/solver/full-cube.d.ts.map +1 -0
  130. package/dist/puzzles/sq1/solver/full-cube.js +281 -0
  131. package/dist/puzzles/sq1/solver/generated/manifest.json +50 -0
  132. package/dist/puzzles/sq1/solver/generated/shape-bottom-move.bin +0 -0
  133. package/dist/puzzles/sq1/solver/generated/shape-idx.bin +0 -0
  134. package/dist/puzzles/sq1/solver/generated/shape-prun.bin +0 -0
  135. package/dist/puzzles/sq1/solver/generated/shape-top-move.bin +0 -0
  136. package/dist/puzzles/sq1/solver/generated/shape-twist-move.bin +0 -0
  137. package/dist/puzzles/sq1/solver/generated/square-bottom-move.bin +0 -0
  138. package/dist/puzzles/sq1/solver/generated/square-prun.bin +0 -0
  139. package/dist/puzzles/sq1/solver/generated/square-top-move.bin +0 -0
  140. package/dist/puzzles/sq1/solver/generated/square-twist-move.bin +0 -0
  141. package/dist/puzzles/sq1/solver/index.d.ts +5 -0
  142. package/dist/puzzles/sq1/solver/index.d.ts.map +1 -0
  143. package/dist/puzzles/sq1/solver/index.js +4 -0
  144. package/dist/puzzles/sq1/solver/node-loader.d.ts +4 -0
  145. package/dist/puzzles/sq1/solver/node-loader.d.ts.map +1 -0
  146. package/dist/puzzles/sq1/solver/node-loader.js +113 -0
  147. package/dist/puzzles/sq1/solver/search.d.ts +27 -0
  148. package/dist/puzzles/sq1/solver/search.d.ts.map +1 -0
  149. package/dist/puzzles/sq1/solver/search.js +225 -0
  150. package/dist/puzzles/sq1/solver/shape.d.ts +22 -0
  151. package/dist/puzzles/sq1/solver/shape.d.ts.map +1 -0
  152. package/dist/puzzles/sq1/solver/shape.js +185 -0
  153. package/dist/puzzles/sq1/solver/square.d.ts +10 -0
  154. package/dist/puzzles/sq1/solver/square.d.ts.map +1 -0
  155. package/dist/puzzles/sq1/solver/square.js +90 -0
  156. package/dist/puzzles/sq1/solver/table-metadata.d.ts +48 -0
  157. package/dist/puzzles/sq1/solver/table-metadata.d.ts.map +1 -0
  158. package/dist/puzzles/sq1/solver/table-metadata.js +47 -0
  159. package/dist/puzzles/sq1/solver/tables.d.ts +17 -0
  160. package/dist/puzzles/sq1/solver/tables.d.ts.map +1 -0
  161. package/dist/puzzles/sq1/solver/tables.js +66 -0
  162. package/dist/puzzles/sq1/state.d.ts +10 -0
  163. package/dist/puzzles/sq1/state.d.ts.map +1 -0
  164. package/dist/puzzles/sq1/state.js +74 -0
  165. package/dist/puzzles/sq1/types.d.ts +32 -0
  166. package/dist/puzzles/sq1/types.d.ts.map +1 -0
  167. package/dist/puzzles/sq1/types.js +1 -0
  168. package/dist/puzzles/sq1/validation.d.ts +7 -0
  169. package/dist/puzzles/sq1/validation.d.ts.map +1 -0
  170. package/dist/puzzles/sq1/validation.js +64 -0
  171. package/dist/random/prng.d.ts.map +1 -1
  172. package/dist/random/prng.js +9 -4
  173. package/dist-cjs/core/api.js +11 -1
  174. package/dist-cjs/puzzles/cube4/index.js +11 -4
  175. package/dist-cjs/puzzles/cube4/random-state.js +79 -0
  176. package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +100 -0
  177. package/dist-cjs/puzzles/cube4/reduction/center1.js +258 -0
  178. package/dist-cjs/puzzles/cube4/reduction/center2.js +462 -0
  179. package/dist-cjs/puzzles/cube4/reduction/center3.js +284 -0
  180. package/dist-cjs/puzzles/cube4/reduction/edge3.js +522 -0
  181. package/dist-cjs/puzzles/cube4/reduction/generated/center1-distance.bin +1 -0
  182. package/dist-cjs/puzzles/cube4/reduction/generated/center1-manifest.json +46 -0
  183. package/dist-cjs/puzzles/cube4/reduction/generated/center1-move-maps.bin +0 -0
  184. package/dist-cjs/puzzles/cube4/reduction/generated/center2-ct-move.bin +0 -0
  185. package/dist-cjs/puzzles/cube4/reduction/generated/center2-manifest.json +87 -0
  186. package/dist-cjs/puzzles/cube4/reduction/generated/center2-pruning.bin +1 -0
  187. package/dist-cjs/puzzles/cube4/reduction/generated/center2-rl-move.bin +0 -0
  188. package/dist-cjs/puzzles/cube4/reduction/generated/center3-manifest.json +40 -0
  189. package/dist-cjs/puzzles/cube4/reduction/generated/center3-move.bin +0 -0
  190. package/dist-cjs/puzzles/cube4/reduction/generated/center3-pruning.bin +0 -0
  191. package/dist-cjs/puzzles/cube4/reduction/generated/edge3-distances.bin +1 -0
  192. package/dist-cjs/puzzles/cube4/reduction/generated/edge3-manifest.json +12 -0
  193. package/dist-cjs/puzzles/cube4/reduction/geometry.js +308 -0
  194. package/dist-cjs/puzzles/cube4/reduction/node-loader.js +104 -0
  195. package/dist-cjs/puzzles/cube4/reduction/pairing-coordinate.js +294 -0
  196. package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +138 -0
  197. package/dist-cjs/puzzles/cube4/reduction/phase3.js +86 -0
  198. package/dist-cjs/puzzles/cube4/reduction/phase4.js +87 -0
  199. package/dist-cjs/puzzles/cube4/reduction/solve.js +314 -0
  200. package/dist-cjs/puzzles/cube4/scrambler.js +122 -79
  201. package/dist-cjs/puzzles/cube4/validation.js +97 -0
  202. package/dist-cjs/puzzles/fto/coordinates.js +240 -0
  203. package/dist-cjs/puzzles/fto/facelets.js +104 -0
  204. package/dist-cjs/puzzles/fto/index.js +68 -0
  205. package/dist-cjs/puzzles/fto/moves.js +126 -0
  206. package/dist-cjs/puzzles/fto/random-state.js +43 -0
  207. package/dist-cjs/puzzles/fto/scrambler.js +53 -0
  208. package/dist-cjs/puzzles/fto/solver/engine.js +1175 -0
  209. package/dist-cjs/puzzles/fto/solver/index.js +9 -0
  210. package/dist-cjs/puzzles/fto/state.js +69 -0
  211. package/dist-cjs/puzzles/fto/types.js +49 -0
  212. package/dist-cjs/puzzles/fto/validation.js +147 -0
  213. package/dist-cjs/puzzles/index.js +7 -1
  214. package/dist-cjs/puzzles/skewb/index.js +2 -1
  215. package/dist-cjs/puzzles/skewb/random-state.js +4 -1
  216. package/dist-cjs/puzzles/sq1/facelets.js +30 -0
  217. package/dist-cjs/puzzles/sq1/index.js +52 -0
  218. package/dist-cjs/puzzles/sq1/moves.js +234 -0
  219. package/dist-cjs/puzzles/sq1/random-state.js +52 -0
  220. package/dist-cjs/puzzles/sq1/scrambler.js +95 -0
  221. package/dist-cjs/puzzles/sq1/solver/browser-loader.js +97 -0
  222. package/dist-cjs/puzzles/sq1/solver/full-cube.js +289 -0
  223. package/dist-cjs/puzzles/sq1/solver/generated/manifest.json +50 -0
  224. package/dist-cjs/puzzles/sq1/solver/generated/shape-bottom-move.bin +0 -0
  225. package/dist-cjs/puzzles/sq1/solver/generated/shape-idx.bin +0 -0
  226. package/dist-cjs/puzzles/sq1/solver/generated/shape-prun.bin +0 -0
  227. package/dist-cjs/puzzles/sq1/solver/generated/shape-top-move.bin +0 -0
  228. package/dist-cjs/puzzles/sq1/solver/generated/shape-twist-move.bin +0 -0
  229. package/dist-cjs/puzzles/sq1/solver/generated/square-bottom-move.bin +0 -0
  230. package/dist-cjs/puzzles/sq1/solver/generated/square-prun.bin +0 -0
  231. package/dist-cjs/puzzles/sq1/solver/generated/square-top-move.bin +0 -0
  232. package/dist-cjs/puzzles/sq1/solver/generated/square-twist-move.bin +0 -0
  233. package/dist-cjs/puzzles/sq1/solver/index.js +18 -0
  234. package/dist-cjs/puzzles/sq1/solver/node-loader.js +120 -0
  235. package/dist-cjs/puzzles/sq1/solver/search.js +229 -0
  236. package/dist-cjs/puzzles/sq1/solver/shape.js +191 -0
  237. package/dist-cjs/puzzles/sq1/solver/square.js +93 -0
  238. package/dist-cjs/puzzles/sq1/solver/table-metadata.js +50 -0
  239. package/dist-cjs/puzzles/sq1/solver/tables.js +106 -0
  240. package/dist-cjs/puzzles/sq1/state.js +81 -0
  241. package/dist-cjs/puzzles/sq1/types.js +2 -0
  242. package/dist-cjs/puzzles/sq1/validation.js +68 -0
  243. package/dist-cjs/random/prng.js +9 -4
  244. package/package.json +43 -14
@@ -0,0 +1,449 @@
1
+ /**
2
+ * Center2 (phase 2 of the 4x4 reduction solver): from the phase-1 output
3
+ * (axis colors on a face-pair), reach the phase-2 goal: the U/D/F/B faces
4
+ * uniform in color-class and the R/L faces in one of the 6 goal patterns.
5
+ *
6
+ * Ported from the reference's `Center2` class, adapted to this repo's layout:
7
+ * - ct part: the 16 positions of the U, D, F, B faces, storing color % 3
8
+ * (the color-class), ranked as C(15,8) vs ct[15] = 6,435 states;
9
+ * - rl part: the 8 positions of the R, L faces, storing the actual colors,
10
+ * ranked as C(7,4) vs rl[7] × 2 + wing-parity = 70 states;
11
+ * - full coordinate: ct × 70 = 450,450;
12
+ * - goal: ct == 0 and rl in the 6 reference goal patterns (even parity);
13
+ * - the 28-move set keeps the ct and rl position sets separate, so the move
14
+ * tables factor: ctmv (6,435×28) and rlmv (70×28);
15
+ * - the search explores the first 23 moves (face moves + Uw2/Rw/Rw2/Rw').
16
+ *
17
+ * The phase-1 → phase-2 bridge (alignment + encode) is in `reduction.ts`.
18
+ */
19
+ import { FACE_MOVE_EFFECTS, applyCube4Algorithm, parseMove } from "../moves.js";
20
+ // ---------------------------------------------------------------------------
21
+ // Constants
22
+ // ---------------------------------------------------------------------------
23
+ /** The phase-2 move set (28 real moves + the reference's trailing no-op). */
24
+ export const CENTER2_MOVES = [
25
+ "U", "U2", "U'", "R", "R2", "R'", "F", "F2", "F'", "D", "D2", "D'",
26
+ "L", "L2", "L'", "B", "B2", "B'",
27
+ "Uw2", "Rw", "Rw2", "Rw'", "Fw2", "Dw2", "Lw", "Lw2", "Lw'", "Bw2"
28
+ ];
29
+ /** The search's exploration set (the first 23 of CENTER2_MOVES). */
30
+ export const CENTER2_SEARCH_MOVES = CENTER2_MOVES.slice(0, 23);
31
+ /** The 6 reference goal rl patterns (full 70-space values, even parity). */
32
+ export const CENTER2_GOAL_RL = [0, 18, 28, 46, 54, 56];
33
+ export const CENTER2_CT_STATES = 6435;
34
+ export const CENTER2_RL_STATES = 70;
35
+ export const CENTER2_STATES = CENTER2_CT_STATES * CENTER2_RL_STATES;
36
+ // ct positions: U(0-3), D(12-15), F(8-11), B(20-23) — the reference's face
37
+ // order {U, D, F, B}, in this repo's face-major numbering.
38
+ const CT_POS = [0, 1, 2, 3, 12, 13, 14, 15, 8, 9, 10, 11, 20, 21, 22, 23];
39
+ // rl positions: R(4-7), L(16-19).
40
+ const RL_POS = [4, 5, 6, 7, 16, 17, 18, 19];
41
+ /** Wide quarter turns flip the wing permutation parity. */
42
+ const PARITY_FLIP = new Set(["Rw", "Rw'", "Lw", "Lw'"]);
43
+ // Cnk table up to n=24.
44
+ const CNK = (() => {
45
+ const table = [];
46
+ for (let n = 0; n <= 24; n++) {
47
+ table[n] = [];
48
+ for (let k = 0; k <= 24; k++) {
49
+ if (k === 0 || k === n)
50
+ table[n][k] = 1;
51
+ else if (k > n)
52
+ table[n][k] = 0;
53
+ else
54
+ table[n][k] = table[n - 1][k - 1] + table[n - 1][k];
55
+ }
56
+ }
57
+ return table;
58
+ })();
59
+ function rankVsLast(values, size, ones) {
60
+ const last = values[size - 1];
61
+ let idx = 0;
62
+ let r = ones;
63
+ for (let i = size - 2; i >= 0; i--) {
64
+ if (values[i] !== last)
65
+ idx += CNK[i][r--];
66
+ }
67
+ return idx;
68
+ }
69
+ export function wingPermutationParity(wp) {
70
+ let parity = 0;
71
+ const seen = new Array(24).fill(false);
72
+ for (let start = 0; start < 24; start++) {
73
+ if (seen[start] || wp[start] === start)
74
+ continue;
75
+ let length = 0;
76
+ let current = start;
77
+ while (!seen[current]) {
78
+ seen[current] = true;
79
+ current = wp[current];
80
+ length++;
81
+ }
82
+ parity ^= (length - 1) & 1;
83
+ }
84
+ return parity;
85
+ }
86
+ /** Encode a full 4x4 state to the (ct, rl) coordinate (0..450,449). */
87
+ export function encodeCenter2(state) {
88
+ const ct = new Uint8Array(16);
89
+ const rl = new Uint8Array(8);
90
+ for (let i = 0; i < 16; i++)
91
+ ct[i] = state.centers[CT_POS[i]] % 3;
92
+ for (let i = 0; i < 8; i++)
93
+ rl[i] = state.centers[RL_POS[i]];
94
+ const ctRank = rankVsLast(ct, 16, 8);
95
+ const rlRank = rankVsLast(rl, 8, 4) * 2 + wingPermutationParity(state.wp);
96
+ return { ct: ctRank, rl: rlRank };
97
+ }
98
+ export function isCenter2Goal(ct, rl) {
99
+ return ct === 0 && CENTER2_GOAL_RL.includes(rl);
100
+ }
101
+ /**
102
+ * The phase-2 coordinate is only valid for states where exactly 8 of the
103
+ * first 15 ct entries differ from ct[15] and exactly 4 of the first 7 rl
104
+ * entries differ from rl[7]. Phase-1 outputs don't always satisfy this; the
105
+ * phase-1 → phase-2 bridge applies a short alignment sequence (0-2 moves)
106
+ * that re-categorizes the axis colors onto the R/L face-pair, which is
107
+ * always invariant (verified over random states).
108
+ */
109
+ const ALIGNMENT_CANDIDATES = [
110
+ [],
111
+ ["Fw", "Bw'"],
112
+ ["Uw", "Dw'"]
113
+ ];
114
+ function center2InvariantDiffs(centers) {
115
+ let ctDiff = 0;
116
+ let rlDiff = 0;
117
+ for (let i = 0; i < 15; i++) {
118
+ if (centers[CT_POS[i]] % 3 !== centers[CT_POS[15]] % 3)
119
+ ctDiff++;
120
+ }
121
+ for (let i = 0; i < 7; i++) {
122
+ if (centers[RL_POS[i]] !== centers[RL_POS[7]])
123
+ rlDiff++;
124
+ }
125
+ return { ctDiff, rlDiff };
126
+ }
127
+ /**
128
+ * Phase-1 → phase-2 bridge: apply the shortest alignment candidate that
129
+ * makes the state coordinate-invariant, then encode.
130
+ */
131
+ export function center2Bridge(state) {
132
+ for (const alignment of ALIGNMENT_CANDIDATES) {
133
+ const aligned = alignment.length === 0
134
+ ? state
135
+ : applyCube4Algorithm(state, alignment);
136
+ const { ctDiff, rlDiff } = center2InvariantDiffs(aligned.centers);
137
+ if (ctDiff === 8 && rlDiff === 4) {
138
+ return {
139
+ alignment: [...alignment],
140
+ state: aligned,
141
+ encoded: encodeCenter2(aligned)
142
+ };
143
+ }
144
+ }
145
+ return null;
146
+ }
147
+ // ---------------------------------------------------------------------------
148
+ // Move tables
149
+ // ---------------------------------------------------------------------------
150
+ function buildMovePositionMaps() {
151
+ const maps = new Int32Array(CENTER2_MOVES.length * 24);
152
+ for (let m = 0; m < CENTER2_MOVES.length; m++) {
153
+ const { face, turns } = parseMove(CENTER2_MOVES[m]);
154
+ const effect = FACE_MOVE_EFFECTS[face];
155
+ let pos = Array.from({ length: 24 }, (_, i) => i);
156
+ for (let t = 0; t < turns; t++) {
157
+ const next = new Array(24);
158
+ for (let i = 0; i < 24; i++)
159
+ next[i] = pos[effect.ctperm[i]];
160
+ pos = next;
161
+ }
162
+ for (let i = 0; i < 24; i++)
163
+ maps[m * 24 + i] = pos[i];
164
+ }
165
+ return maps;
166
+ }
167
+ function unrankVsLast(idx, size, ones) {
168
+ const values = new Uint8Array(size);
169
+ let r = ones;
170
+ for (let i = size - 2; i >= 0; i--) {
171
+ if (idx >= CNK[i][r]) {
172
+ idx -= CNK[i][r];
173
+ r--;
174
+ values[i] = 1;
175
+ }
176
+ }
177
+ return values;
178
+ }
179
+ export function generateCenter2Tables() {
180
+ const moveMaps = buildMovePositionMaps();
181
+ const ctMove = new Int32Array(CENTER2_CT_STATES * CENTER2_MOVES.length);
182
+ const rlMove = new Int32Array(CENTER2_RL_STATES * CENTER2_MOVES.length);
183
+ // canonical full arrangements: ct classes {0,1} (ct[15]=0), rl colors {0,1} (rl[7]=0)
184
+ const full = new Uint8Array(24);
185
+ const moved = new Uint8Array(24);
186
+ for (let ctRank = 0; ctRank < CENTER2_CT_STATES; ctRank++) {
187
+ const pattern = unrankVsLast(ctRank, 16, 8);
188
+ for (let i = 0; i < 24; i++)
189
+ full[i] = 0;
190
+ for (let i = 0; i < 16; i++) {
191
+ if (pattern[i] === 1)
192
+ full[CT_POS[i]] = 1;
193
+ }
194
+ for (let m = 0; m < CENTER2_MOVES.length; m++) {
195
+ const map = moveMaps;
196
+ for (let i = 0; i < 24; i++)
197
+ moved[i] = full[map[m * 24 + i]];
198
+ const ct = new Uint8Array(16);
199
+ for (let i = 0; i < 16; i++)
200
+ ct[i] = moved[CT_POS[i]] % 3;
201
+ ctMove[ctRank * CENTER2_MOVES.length + m] = rankVsLast(ct, 16, 8);
202
+ }
203
+ }
204
+ for (let rlRankBase = 0; rlRankBase < 35; rlRankBase++) {
205
+ for (const parity of [0, 1]) {
206
+ const rlRank = rlRankBase * 2 + parity;
207
+ const pattern = unrankVsLast(rlRankBase, 8, 4);
208
+ for (let i = 0; i < 24; i++)
209
+ full[i] = 0;
210
+ for (let i = 0; i < 8; i++) {
211
+ if (pattern[i] === 1)
212
+ full[RL_POS[i]] = 1;
213
+ }
214
+ for (let m = 0; m < CENTER2_MOVES.length; m++) {
215
+ const map = moveMaps;
216
+ for (let i = 0; i < 24; i++)
217
+ moved[i] = full[map[m * 24 + i]];
218
+ const rl = new Uint8Array(8);
219
+ for (let i = 0; i < 8; i++)
220
+ rl[i] = moved[RL_POS[i]];
221
+ const nextParity = (parity ^ (PARITY_FLIP.has(CENTER2_MOVES[m]) ? 1 : 0));
222
+ rlMove[rlRank * CENTER2_MOVES.length + m] = rankVsLast(rl, 8, 4) * 2 + nextParity;
223
+ }
224
+ }
225
+ }
226
+ // pruning BFS from the 6 goal states, with the 23 exploration moves
227
+ const pruning = new Uint8Array(CENTER2_STATES).fill(0xff);
228
+ const queue = [];
229
+ for (const rl of CENTER2_GOAL_RL) {
230
+ pruning[rl] = 0; // ct == 0 → index == rl
231
+ queue.push(rl);
232
+ }
233
+ let head = 0;
234
+ while (head < queue.length) {
235
+ const idx = queue[head++];
236
+ const ct = Math.floor(idx / CENTER2_RL_STATES);
237
+ const rl = idx % CENTER2_RL_STATES;
238
+ const d = pruning[idx];
239
+ for (let m = 0; m < CENTER2_SEARCH_MOVES.length; m++) {
240
+ const nextCt = ctMove[ct * CENTER2_MOVES.length + m];
241
+ const nextRl = rlMove[rl * CENTER2_MOVES.length + m];
242
+ const next = nextCt * CENTER2_RL_STATES + nextRl;
243
+ if (pruning[next] === 0xff) {
244
+ pruning[next] = d + 1;
245
+ queue.push(next);
246
+ }
247
+ }
248
+ }
249
+ return { ctMove, rlMove, pruning };
250
+ }
251
+ // The reference's cancellation rule (ckmv over move2std): skip the same base
252
+ // move, or the same axis-class when the previous move's standard index is
253
+ // larger than the current's.
254
+ const STANDARD_ORDER = [
255
+ "U", "U2", "U'", "R", "R2", "R'", "F", "F2", "F'", "D", "D2", "D'",
256
+ "L", "L2", "L'", "B", "B2", "B'",
257
+ "Uw", "Uw2", "Uw'", "Rw", "Rw2", "Rw'", "Fw", "Fw2", "Fw'",
258
+ "Dw", "Dw2", "Dw'", "Lw", "Lw2", "Lw'", "Bw", "Bw2", "Bw'"
259
+ ];
260
+ const STD_INDEX = new Map(STANDARD_ORDER.map((m, i) => [m, i]));
261
+ function baseOf(move) {
262
+ return move.replace(/[2']$/, "");
263
+ }
264
+ function axisClass(base) {
265
+ return { U: 0, D: 0, R: 1, L: 1, F: 2, B: 2 }[base];
266
+ }
267
+ function isCancelled(previous, current) {
268
+ const pb = baseOf(previous);
269
+ const cb = baseOf(current);
270
+ if (pb === cb)
271
+ return true;
272
+ if (axisClass(pb) === axisClass(cb)) {
273
+ return STD_INDEX.get(previous) > STD_INDEX.get(current);
274
+ }
275
+ return false;
276
+ }
277
+ export function solveCenter2(startCt, startRl, tables, maxLength = 40) {
278
+ const solutions = solveCenter2All(startCt, startRl, tables, maxLength, 1);
279
+ return solutions.length > 0 ? solutions[0] : null;
280
+ }
281
+ /**
282
+ * Enumerate phase-2 solutions (shortest first). The phase-2 → phase-3 bridge
283
+ * needs multiple candidates: the reference filters them by the pairing gate
284
+ * (transversal + even wing permutation), which most states fail.
285
+ */
286
+ export function solveCenter2All(startCt, startRl, tables, maxLength = 40, cap = 200) {
287
+ const solutions = [];
288
+ const moveLog = new Array(maxLength);
289
+ for (let length = tables.pruning[startCt * CENTER2_RL_STATES + startRl]; length <= maxLength; length++) {
290
+ if (search(startCt, startRl, length, -1, 0))
291
+ break;
292
+ }
293
+ return solutions;
294
+ function search(c, r, maxl, lm, depth) {
295
+ if (isCenter2Goal(c, r)) {
296
+ if (maxl === 0) {
297
+ solutions.push({
298
+ moves: moveLog.slice(0, depth).map((m) => CENTER2_MOVES[m]),
299
+ ct: c,
300
+ rl: r
301
+ });
302
+ return solutions.length >= cap;
303
+ }
304
+ return false;
305
+ }
306
+ for (let m = 0; m < CENTER2_SEARCH_MOVES.length; m++) {
307
+ if (lm >= 0 && isCancelled(CENTER2_SEARCH_MOVES[lm], CENTER2_SEARCH_MOVES[m])) {
308
+ continue;
309
+ }
310
+ const nc = tables.ctMove[c * CENTER2_MOVES.length + m];
311
+ const nr = tables.rlMove[r * CENTER2_MOVES.length + m];
312
+ const prun = tables.pruning[nc * CENTER2_RL_STATES + nr];
313
+ if (prun >= maxl)
314
+ continue;
315
+ moveLog[depth] = m;
316
+ if (search(nc, nr, maxl - 1, m, depth + 1))
317
+ return true;
318
+ }
319
+ return false;
320
+ }
321
+ }
322
+ let cachedWingMaps = null;
323
+ function getWingMoveMaps() {
324
+ if (!cachedWingMaps) {
325
+ cachedWingMaps = buildWingMoveMaps();
326
+ }
327
+ return cachedWingMaps;
328
+ }
329
+ const tempWpPool = Array.from({ length: 5 }, () => new Uint8Array(24));
330
+ function replayWp(startWp, moves, depth, wingMaps, outWp) {
331
+ outWp.set(startWp);
332
+ const temp = tempWpPool[0];
333
+ for (let d = 0; d < depth; d++) {
334
+ const m = moves[d];
335
+ temp.set(outWp);
336
+ for (let i = 0; i < 24; i++) {
337
+ outWp[i] = temp[wingMaps[m * 24 + i]];
338
+ }
339
+ }
340
+ }
341
+ /**
342
+ * Solve Center 2 gated for an exact target length.
343
+ */
344
+ export function solveCenter2GatedLength(startCt, startRl, startWp, tables, isGateEligible, targetLength, moveLog = new Array(targetLength)) {
345
+ const wingMaps = getWingMoveMaps();
346
+ let finalWp = null;
347
+ let finalCt = -1;
348
+ let finalRl = -1;
349
+ if (search(startCt, startRl, targetLength, -1, 0)) {
350
+ return {
351
+ moves: moveLog.slice(0, targetLength).map((m) => CENTER2_MOVES[m]),
352
+ ct: finalCt,
353
+ rl: finalRl,
354
+ wp: finalWp
355
+ };
356
+ }
357
+ return null;
358
+ function search(c, r, maxl, lm, depth) {
359
+ if (isCenter2Goal(c, r)) {
360
+ if (maxl === 0) {
361
+ replayWp(startWp, moveLog, depth, wingMaps, tempWpPool[1]);
362
+ if (isGateEligible(tempWpPool[1])) {
363
+ finalWp = tempWpPool[1].slice();
364
+ finalCt = c;
365
+ finalRl = r;
366
+ return true;
367
+ }
368
+ }
369
+ return false;
370
+ }
371
+ if (maxl === 0)
372
+ return false;
373
+ for (let m = 0; m < CENTER2_SEARCH_MOVES.length; m++) {
374
+ if (lm >= 0 && isCancelled(CENTER2_SEARCH_MOVES[lm], CENTER2_SEARCH_MOVES[m])) {
375
+ continue;
376
+ }
377
+ const nc = tables.ctMove[c * CENTER2_MOVES.length + m];
378
+ const nr = tables.rlMove[r * CENTER2_MOVES.length + m];
379
+ const prun = tables.pruning[nc * CENTER2_RL_STATES + nr];
380
+ if (prun >= maxl)
381
+ continue;
382
+ moveLog[depth] = m;
383
+ if (search(nc, nr, maxl - 1, m, depth + 1))
384
+ return true;
385
+ }
386
+ return false;
387
+ }
388
+ }
389
+ /**
390
+ * Phase-2 search that tracks the wing permutation alongside the (ct, rl)
391
+ * coordinate and only accepts goal states whose wings satisfy the pairing
392
+ * transversal (the phase-3 entry gate).
393
+ */
394
+ export function solveCenter2Gated(startCt, startRl, startWp, tables, isGateEligible, maxLength = 40) {
395
+ const moveLog = new Array(maxLength);
396
+ const minLength = tables.pruning[startCt * CENTER2_RL_STATES + startRl];
397
+ for (let length = minLength; length <= maxLength; length++) {
398
+ const result = solveCenter2GatedLength(startCt, startRl, startWp, tables, isGateEligible, length, moveLog);
399
+ if (result)
400
+ return result;
401
+ }
402
+ return null;
403
+ }
404
+ function buildWingMoveMaps() {
405
+ const maps = new Int32Array(CENTER2_MOVES.length * 24);
406
+ for (let m = 0; m < CENTER2_MOVES.length; m++) {
407
+ const { face, turns } = parseMove(CENTER2_MOVES[m]);
408
+ const effect = FACE_MOVE_EFFECTS[face];
409
+ let pos = Array.from({ length: 24 }, (_, i) => i);
410
+ for (let t = 0; t < turns; t++) {
411
+ const next = new Array(24);
412
+ for (let i = 0; i < 24; i++)
413
+ next[i] = pos[effect.wperm[i]];
414
+ pos = next;
415
+ }
416
+ for (let i = 0; i < 24; i++)
417
+ maps[m * 24 + i] = pos[i];
418
+ }
419
+ return maps;
420
+ }
421
+ // ---------------------------------------------------------------------------
422
+ // Verification gates
423
+ // ---------------------------------------------------------------------------
424
+ export function verifyCenter2(tables) {
425
+ const errors = [];
426
+ for (const rl of CENTER2_GOAL_RL) {
427
+ if (tables.pruning[rl] !== 0) {
428
+ errors.push(`Goal rl ${rl} must have pruning 0.`);
429
+ }
430
+ }
431
+ let maxDepth = 0;
432
+ for (let idx = 0; idx < CENTER2_STATES; idx++) {
433
+ const d = tables.pruning[idx];
434
+ if (d > maxDepth)
435
+ maxDepth = d;
436
+ const ct = Math.floor(idx / CENTER2_RL_STATES);
437
+ const rl = idx % CENTER2_RL_STATES;
438
+ for (let m = 0; m < CENTER2_SEARCH_MOVES.length; m++) {
439
+ const nc = tables.ctMove[ct * CENTER2_MOVES.length + m];
440
+ const nr = tables.rlMove[rl * CENTER2_MOVES.length + m];
441
+ const nd = tables.pruning[nc * CENTER2_RL_STATES + nr];
442
+ if (nd < d - 1 || nd > d + 1) {
443
+ errors.push(`Pruning inconsistency at ${idx} move ${CENTER2_MOVES[m]}: ${d} -> ${nd}.`);
444
+ break;
445
+ }
446
+ }
447
+ }
448
+ return errors;
449
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Center3 (the center part of the phase-3 search): track the center
3
+ * arrangement through the phase-3 moves (which disturb the solved centers
4
+ * with the wide doubles) and reach the fully-solved centers.
5
+ *
6
+ * Ported from the reference's `Center3` class, adapted to this repo's
7
+ * layout. The state is three 8-entry arrays of "half-class" bits
8
+ * ((color / 3) ^ 1) per face-pair, plus a parity bit:
9
+ * - ud: the U, D face positions (this repo: U = 0-3, D = 12-15)
10
+ * - fb: the F, B face positions (8-11, 20-23)
11
+ * - rl: the R, L face positions (4-7, 16-19)
12
+ *
13
+ * Coordinate (reference `$getct_0`): C(7,4) ranks of ud and fb vs their 8th
14
+ * entry (35 each), the rl C(8,4) rank reduced to one of 12 standard values,
15
+ * then ×2 + parity = 29,400 × 2. The rank-0 pattern is "the first four
16
+ * entries differ from the last four" — i.e. each face-pair uniform in half
17
+ * — which is exactly the solved-cube arrangement (verified: the solved cube
18
+ * encodes to coordinate 0). The parity bit absorbs the wing-pairing parity
19
+ * XOR the corner parity at the phase-2 → phase-3 bridge and flips on the
20
+ * wide doubles (`pmove`).
21
+ */
22
+ import type { Cube4State } from "../types.js";
23
+ export declare const CENTER3_MOVES: readonly ["U", "U2", "U'", "R2", "F", "F2", "F'", "D", "D2", "D'", "L2", "B", "B2", "B'", "Uw2", "Rw2", "Fw2", "Dw2", "Lw2", "Bw2"];
24
+ /** The 17-move BFS subset (the reference's pruning loop uses m < 17). */
25
+ export declare const CENTER3_BFS_MOVES: ("U" | "D" | "F" | "B" | "U2" | "U'" | "D2" | "D'" | "R2" | "L2" | "F2" | "F'" | "B2" | "B'" | "Uw2" | "Dw2" | "Rw2" | "Lw2" | "Fw2" | "Bw2")[];
26
+ export declare const CENTER3_STATES = 29400;
27
+ export interface Center3Encoded {
28
+ /** Full coordinate 0..29,399 (parity included). */
29
+ coordinate: number;
30
+ parity: 0 | 1;
31
+ }
32
+ /**
33
+ * Encode a full cube state to the Center3 coordinate.
34
+ *
35
+ * `edgeXorCornerParity` = the $set_6 pairing parity XOR the corner
36
+ * permutation parity, computed at the phase-2 → phase-3 bridge.
37
+ */
38
+ export declare function encodeCenter3(state: Cube4State, edgeXorCornerParity: 0 | 1): Center3Encoded;
39
+ export interface Center3Tables {
40
+ /** 29400 × 20 — full coordinate (parity included) after each move. */
41
+ moveTable: Int32Array;
42
+ /** 29400 BFS distances. */
43
+ pruning: Uint8Array;
44
+ }
45
+ export declare function generateCenter3Tables(): Center3Tables;
46
+ export declare function verifyCenter3(tables: Center3Tables): string[];
47
+ //# sourceMappingURL=center3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"center3.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube4/reduction/center3.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAO9C,eAAO,MAAM,aAAa,qIAGhB,CAAC;AAEX,yEAAyE;AACzE,eAAO,MAAM,iBAAiB,iJAA6B,CAAC;AAa5D,eAAO,MAAM,cAAc,QAAQ,CAAC;AAiDpC,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;CACf;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,UAAU,EACjB,mBAAmB,EAAE,CAAC,GAAG,CAAC,GACzB,cAAc,CA8BhB;AAsBD,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,SAAS,EAAE,UAAU,CAAC;IACtB,2BAA2B;IAC3B,OAAO,EAAE,UAAU,CAAC;CACrB;AAoDD,wBAAgB,qBAAqB,IAAI,aAAa,CAgErD;AAMD,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,EAAE,CAwB7D"}