@cubingcenter/scrambler 0.6.3 → 0.7.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 (115) hide show
  1. package/README.md +2 -0
  2. package/dist/puzzles/cube2/solver/search.d.ts.map +1 -1
  3. package/dist/puzzles/cube2/solver/search.js +3 -3
  4. package/dist/puzzles/cube2/solver/tables.d.ts +6 -0
  5. package/dist/puzzles/cube2/solver/tables.d.ts.map +1 -1
  6. package/dist/puzzles/cube2/solver/tables.js +16 -2
  7. package/dist/puzzles/cube3/index.d.ts +1 -0
  8. package/dist/puzzles/cube3/index.d.ts.map +1 -1
  9. package/dist/puzzles/cube3/moves.d.ts.map +1 -1
  10. package/dist/puzzles/cube3/moves.js +104 -18
  11. package/dist/puzzles/cube3/scrambler.d.ts +16 -1
  12. package/dist/puzzles/cube3/scrambler.d.ts.map +1 -1
  13. package/dist/puzzles/cube3/scrambler.js +1 -1
  14. package/dist/puzzles/cube3/solver/browser-loader.js +6 -2
  15. package/dist/puzzles/cube3/solver/index.d.ts +5 -3
  16. package/dist/puzzles/cube3/solver/index.d.ts.map +1 -1
  17. package/dist/puzzles/cube3/solver/index.js +2 -1
  18. package/dist/puzzles/cube3/solver/node-loader.js +9 -2
  19. package/dist/puzzles/cube3/solver/phase1.d.ts +18 -0
  20. package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
  21. package/dist/puzzles/cube3/solver/phase1.js +132 -14
  22. package/dist/puzzles/cube3/solver/phase2.d.ts +47 -0
  23. package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
  24. package/dist/puzzles/cube3/solver/phase2.js +236 -66
  25. package/dist/puzzles/cube3/solver/search-utils.d.ts +1 -1
  26. package/dist/puzzles/cube3/solver/search-utils.d.ts.map +1 -1
  27. package/dist/puzzles/cube3/solver/symmetry.d.ts +24 -0
  28. package/dist/puzzles/cube3/solver/symmetry.d.ts.map +1 -0
  29. package/dist/puzzles/cube3/solver/symmetry.js +74 -0
  30. package/dist/puzzles/cube3/solver/tables.d.ts +6 -1
  31. package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
  32. package/dist/puzzles/cube3/validation.d.ts.map +1 -1
  33. package/dist/puzzles/cube3/validation.js +15 -7
  34. package/dist/puzzles/cube4/index.d.ts +3 -2
  35. package/dist/puzzles/cube4/index.d.ts.map +1 -1
  36. package/dist/puzzles/cube4/index.js +2 -2
  37. package/dist/puzzles/cube4/moves.d.ts +5 -0
  38. package/dist/puzzles/cube4/moves.d.ts.map +1 -1
  39. package/dist/puzzles/cube4/moves.js +120 -11
  40. package/dist/puzzles/cube4/reduction/browser-loader.d.ts +3 -0
  41. package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -1
  42. package/dist/puzzles/cube4/reduction/browser-loader.js +34 -3
  43. package/dist/puzzles/cube4/reduction/center2.d.ts +20 -3
  44. package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -1
  45. package/dist/puzzles/cube4/reduction/center2.js +53 -9
  46. package/dist/puzzles/cube4/reduction/edge3.d.ts +4 -0
  47. package/dist/puzzles/cube4/reduction/edge3.d.ts.map +1 -1
  48. package/dist/puzzles/cube4/reduction/edge3.js +23 -3
  49. package/dist/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
  50. package/dist/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
  51. package/dist/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
  52. package/dist/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
  53. package/dist/puzzles/cube4/reduction/generated/hc-manifest.json +38 -0
  54. package/dist/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
  55. package/dist/puzzles/cube4/reduction/hc-pdb.d.ts +50 -0
  56. package/dist/puzzles/cube4/reduction/hc-pdb.d.ts.map +1 -0
  57. package/dist/puzzles/cube4/reduction/hc-pdb.js +202 -0
  58. package/dist/puzzles/cube4/reduction/node-loader.d.ts +5 -0
  59. package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -1
  60. package/dist/puzzles/cube4/reduction/node-loader.js +48 -8
  61. package/dist/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
  62. package/dist/puzzles/cube4/reduction/phase3-search.d.ts +111 -5
  63. package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -1
  64. package/dist/puzzles/cube4/reduction/phase3-search.js +341 -22
  65. package/dist/puzzles/cube4/reduction/solve.d.ts +112 -1
  66. package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -1
  67. package/dist/puzzles/cube4/reduction/solve.js +920 -181
  68. package/dist/puzzles/cube4/scrambler.d.ts +105 -4
  69. package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
  70. package/dist/puzzles/cube4/scrambler.js +216 -166
  71. package/dist/puzzles/cube4/state.d.ts.map +1 -1
  72. package/dist/puzzles/cube4/state.js +4 -1
  73. package/dist/puzzles/pyraminx/solver/search.d.ts.map +1 -1
  74. package/dist/puzzles/pyraminx/solver/search.js +3 -6
  75. package/dist/puzzles/pyraminx/solver/tables.d.ts +6 -0
  76. package/dist/puzzles/pyraminx/solver/tables.d.ts.map +1 -1
  77. package/dist/puzzles/pyraminx/solver/tables.js +17 -0
  78. package/dist/puzzles/skewb/solver/search.js +3 -3
  79. package/dist/puzzles/skewb/solver/tables.d.ts +6 -0
  80. package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -1
  81. package/dist/puzzles/skewb/solver/tables.js +17 -0
  82. package/dist-cjs/puzzles/cube2/solver/search.js +2 -2
  83. package/dist-cjs/puzzles/cube2/solver/tables.js +17 -3
  84. package/dist-cjs/puzzles/cube3/moves.js +104 -18
  85. package/dist-cjs/puzzles/cube3/scrambler.js +1 -1
  86. package/dist-cjs/puzzles/cube3/solver/browser-loader.js +6 -2
  87. package/dist-cjs/puzzles/cube3/solver/index.js +8 -1
  88. package/dist-cjs/puzzles/cube3/solver/node-loader.js +9 -2
  89. package/dist-cjs/puzzles/cube3/solver/phase1.js +133 -14
  90. package/dist-cjs/puzzles/cube3/solver/phase2.js +233 -63
  91. package/dist-cjs/puzzles/cube3/solver/symmetry.js +80 -0
  92. package/dist-cjs/puzzles/cube3/validation.js +15 -7
  93. package/dist-cjs/puzzles/cube4/index.js +3 -1
  94. package/dist-cjs/puzzles/cube4/moves.js +121 -11
  95. package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +34 -3
  96. package/dist-cjs/puzzles/cube4/reduction/center2.js +54 -9
  97. package/dist-cjs/puzzles/cube4/reduction/edge3.js +24 -3
  98. package/dist-cjs/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
  99. package/dist-cjs/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
  100. package/dist-cjs/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
  101. package/dist-cjs/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
  102. package/dist-cjs/puzzles/cube4/reduction/generated/hc-manifest.json +38 -0
  103. package/dist-cjs/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
  104. package/dist-cjs/puzzles/cube4/reduction/hc-pdb.js +213 -0
  105. package/dist-cjs/puzzles/cube4/reduction/node-loader.js +48 -8
  106. package/dist-cjs/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
  107. package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +343 -23
  108. package/dist-cjs/puzzles/cube4/reduction/solve.js +918 -173
  109. package/dist-cjs/puzzles/cube4/scrambler.js +214 -163
  110. package/dist-cjs/puzzles/cube4/state.js +4 -1
  111. package/dist-cjs/puzzles/pyraminx/solver/search.js +2 -5
  112. package/dist-cjs/puzzles/pyraminx/solver/tables.js +18 -1
  113. package/dist-cjs/puzzles/skewb/solver/search.js +2 -2
  114. package/dist-cjs/puzzles/skewb/solver/tables.js +18 -1
  115. package/package.json +14 -5
@@ -13,6 +13,14 @@ export interface Cube3Phase2Options {
13
13
  tables?: Cube3Phase2Tables;
14
14
  /** Combined (cp x uep) pair-distance pruning tables (experimental). */
15
15
  pairTables?: Cube3PairTables | undefined;
16
+ /**
17
+ * Skip the pair tables entirely (no lazy load). All pair-bound uses are
18
+ * already guarded, so the search runs without pair pruning. Used by the
19
+ * 4x4 finish, where pair pruning was measured to change nothing (identical
20
+ * lengths on a 44-state corpus, zero extra retries) while costing a 54MB
21
+ * table load.
22
+ */
23
+ skipPairTables?: boolean;
16
24
  previousFace?: number;
17
25
  previousPreviousFace?: number;
18
26
  }
@@ -23,9 +31,48 @@ export interface Cube3Phase2Result {
23
31
  coordinates: Cube3Phase2Coordinates;
24
32
  visitedNodes: number;
25
33
  }
34
+ export type Cube3SolveQuality = "fast" | "short" | "best";
26
35
  export interface Cube3SolveOptions {
27
36
  phase1?: Cube3Phase1Options;
28
37
  phase2?: Cube3Phase2Options;
38
+ targetLength?: number;
39
+ timeoutMs?: number;
40
+ /**
41
+ * Quality tier (defaults to `"short"`):
42
+ * - `"fast"`: legacy min-P1 + min-P2 (~14 ms, ~23.1 moves, browser tables).
43
+ * - `"short"`: best of several G1 landings (~36 ms, ~21.9 moves).
44
+ * - `"best"`: short x 3 orientations (~116 ms, ~20.8 moves, max 22).
45
+ * Explicit `phase1ExtraDepth` / `phase1SolutionLimit` / `orientations`
46
+ * always override the tier preset.
47
+ */
48
+ quality?: Cube3SolveQuality;
49
+ /**
50
+ * Extra phase-1 depths to explore beyond the minimal one, keeping the
51
+ * best phase-1+phase-2 total. A slightly longer phase 1 often collapses
52
+ * phase 2, shortening the total (and sometimes the search itself).
53
+ * Defaults to 0 (legacy min-P1 + min-P2 behavior). The `targetLength`
54
+ * path always explores up to 4 extra depths.
55
+ */
56
+ phase1ExtraDepth?: number;
57
+ /**
58
+ * Max phase-1 solutions enumerated per extra depth (most promising
59
+ * first). Defaults to 32.
60
+ */
61
+ phase1SolutionLimit?: number;
62
+ /**
63
+ * Per-candidate phase-2 budget (ms) inside the improvement loop.
64
+ * Defaults to `phase2.timeoutMs` when set, else 25. The initial solve
65
+ * always uses `phase2.timeoutMs` untouched, so this never starves it.
66
+ */
67
+ improvePhase2TimeoutMs?: number;
68
+ /**
69
+ * Solve rotated copies of the state and keep the shortest solution
70
+ * (1 = UD only, 3 = UD/x/z triple, up to 24 for the full rotation
71
+ * group). Each extra orientation buys roughly 0.3-0.5 moves for ~1x
72
+ * single-solve time. Composes with the other options (each orientation
73
+ * is solved with the same settings). Defaults to 1.
74
+ */
75
+ orientations?: number;
29
76
  }
30
77
  export interface Cube3SolveResult {
31
78
  phase1: Cube3Phase1Result;
@@ -1 +1 @@
1
- {"version":3,"file":"phase2.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/phase2.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAczD,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAarD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,uEAAuE;IACvE,UAAU,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,sBAAsB,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,MAAM,CAAC,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;gBAElB,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;CAKlD;AA8BD,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAuF5B;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CA+B3B;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,UAAU,GAAG,sBAAsB,CAMtF;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,sBAAsB,GAAG,OAAO,CAQvF;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,SAAS,SAAS,EAAE,GAC1B,UAAU,CAEZ"}
1
+ {"version":3,"file":"phase2.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/phase2.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAezD,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAarD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAQ9D,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,uEAAuE;IACvE,UAAU,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IACzC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,sBAAsB,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;gBAElB,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;CAKlD;AAyCD,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAiF5B;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAmK3B;AA8CD,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,UAAU,GAAG,sBAAsB,CAMtF;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,sBAAsB,GAAG,OAAO,CAQvF;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,SAAS,SAAS,EAAE,GAC1B,UAAU,CAEZ"}
@@ -1,10 +1,11 @@
1
- import { applyCube3Algorithm } from "../moves.js";
2
- import { isSolvedCube3State } from "../state.js";
1
+ import { applyCube3Algorithm, invertCube3Algorithm } from "../moves.js";
2
+ import { createSolvedCube3State, isSolvedCube3State } from "../state.js";
3
3
  import { encodePermutation, encodeSlicePermutation, encodeUdEdgePermutation } from "./coordinates.js";
4
- import { isCube3Phase1Solved, solveCube3Phase1 } from "./phase1.js";
4
+ import { isCube3Phase1Solved, solveCube3Phase1, collectCube3Phase1Solutions, encodeCube3Phase1Coordinates } from "./phase1.js";
5
5
  import { loadCube3Phase1Tables, loadCube3Phase2Tables, CUBE3_PHASE1_MOVES, CUBE3_PHASE2_MOVES } from "./tables.js";
6
6
  import { getMoveFace, getMoveFaceByIndex, shouldSkipCube3SearchMove } from "./search-utils.js";
7
7
  import { loadCube3PairTables, pairBound } from "./pair-tables.js";
8
+ import { CUBE3_ORIENTATIONS_24, invertCube3RotationSequence, rotateCube3AlgorithmBySequence } from "./symmetry.js";
8
9
  export class Cube3Phase2SearchError extends Error {
9
10
  visitedNodes;
10
11
  constructor(message, visitedNodes) {
@@ -41,17 +42,11 @@ export async function solveCube3Phase2(state, options = {}) {
41
42
  throw new Cube3Phase2SearchError("3x3 Phase 2 requires a state already in G1.", 0);
42
43
  }
43
44
  const tables = options.tables ?? await loadCube3Phase2Tables();
44
- const pairTables = options.pairTables ?? await loadCube3PairTables();
45
+ const pairTables = options.pairTables ?? (options.skipPairTables ? undefined : await loadCube3PairTables());
45
46
  const maxDepth = options.maxDepth ?? 18;
46
47
  const start = encodeCube3Phase2Coordinates(state);
47
48
  const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
48
- const context = {
49
- tables,
50
- pairTables,
51
- deadline,
52
- visitedNodes: 0,
53
- timedOut: false
54
- };
49
+ const context = createPhase2SearchContext(tables, pairTables, maxDepth, deadline);
55
50
  const spRange = tables.moveTables.slicePermutation.range;
56
51
  const cpspTable = tables.pruningTables.cornerPermutationSlicePermutation.values;
57
52
  const epspTable = tables.pruningTables.udEdgePermutationSlicePermutation.values;
@@ -89,6 +84,11 @@ export async function solveCube3Phase2(state, options = {}) {
89
84
  throw new Cube3Phase2SearchError(`3x3 Phase 2 solution not found within depth ${maxDepth}.`, context.visitedNodes);
90
85
  }
91
86
  export async function solveCube3State(state, options = {}) {
87
+ const quality = options.quality ?? "short";
88
+ const orientations = options.orientations ?? (quality === "best" ? 3 : 1);
89
+ if (orientations >= 3) {
90
+ return solveCube3TripleOrientation(state, { ...options, orientations });
91
+ }
92
92
  const phase1 = await solveCube3Phase1(state, options.phase1);
93
93
  let previousFace = -1;
94
94
  let previousPreviousFace = -1;
@@ -107,6 +107,127 @@ export async function solveCube3State(state, options = {}) {
107
107
  };
108
108
  const phase2 = await solveCube3Phase2(phase1.state, phase2Options);
109
109
  const moves = [...phase1.moves, ...phase2.moves];
110
+ const targetLength = options.targetLength;
111
+ const extraDepth = options.phase1ExtraDepth ?? (quality === "fast" ? 0 : 1);
112
+ const solutionLimit = options.phase1SolutionLimit ?? (targetLength !== undefined ? 32 : 8);
113
+ const improveDepths = targetLength !== undefined ? Math.max(4, extraDepth) : extraDepth;
114
+ const needsTarget = targetLength !== undefined && moves.length > targetLength;
115
+ if (needsTarget || (targetLength === undefined && extraDepth > 0)) {
116
+ const p1Tables = options.phase1?.tables ?? await loadCube3Phase1Tables();
117
+ const p2Tables = options.phase2?.tables ?? await loadCube3Phase2Tables();
118
+ const scoredPairTables = options.phase2?.pairTables ??
119
+ (options.phase2?.skipPairTables ? undefined : await loadCube3PairTables());
120
+ const deadline = performance.now() + (options.timeoutMs ?? 800);
121
+ const p2SpRange = p2Tables.moveTables.slicePermutation.range;
122
+ const p2cpsp = p2Tables.pruningTables.cornerPermutationSlicePermutation.values;
123
+ const p2uepsp = p2Tables.pruningTables.udEdgePermutationSlicePermutation.values;
124
+ const seenG1 = new Set();
125
+ let bestP1 = phase1;
126
+ let bestP2 = phase2;
127
+ let bestMoves = moves;
128
+ let improvementNodes = 0;
129
+ const minDepth = phase1.depth;
130
+ const scored = [];
131
+ for (let d = minDepth; d <= minDepth + improveDepths && d < bestMoves.length; d++) {
132
+ if (performance.now() > deadline)
133
+ break;
134
+ const { solutions: p1Solutions, visitedNodes } = await collectCube3Phase1Solutions(state, d, {
135
+ tables: p1Tables,
136
+ limit: solutionLimit,
137
+ timeoutMs: Math.max(1, deadline - performance.now())
138
+ });
139
+ improvementNodes += visitedNodes;
140
+ if (performance.now() > deadline)
141
+ break;
142
+ for (const p1Moves of p1Solutions) {
143
+ if (performance.now() > deadline)
144
+ break;
145
+ if (p1Moves.length >= bestMoves.length)
146
+ continue;
147
+ // Skip the phase-1 solution the initial search already completed.
148
+ if (p1Moves.length === phase1.moves.length &&
149
+ p1Moves.every((move, index) => move === phase1.moves[index])) {
150
+ continue;
151
+ }
152
+ const s1 = applyCube3Algorithm(state, p1Moves);
153
+ const coords = encodeCube3Phase2Coordinates(s1);
154
+ const estKey = `${coords.cornerPermutation},${coords.udEdgePermutation},${coords.slicePermutation}`;
155
+ if (seenG1.has(estKey))
156
+ continue;
157
+ seenG1.add(estKey);
158
+ const cpIndex = coords.cornerPermutation * p2SpRange + coords.slicePermutation;
159
+ const epIndex = coords.udEdgePermutation * p2SpRange + coords.slicePermutation;
160
+ const cpDist = (p2cpsp[cpIndex >> 1] >> ((cpIndex & 1) << 2)) & 0x0f;
161
+ const epDist = (p2uepsp[epIndex >> 1] >> ((epIndex & 1) << 2)) & 0x0f;
162
+ let est = p1Moves.length + (cpDist > epDist ? cpDist : epDist);
163
+ if (scoredPairTables !== undefined) {
164
+ const pair = pairBound(scoredPairTables, coords.cornerPermutation, coords.udEdgePermutation);
165
+ const pairEst = p1Moves.length + pair;
166
+ if (pairEst > est)
167
+ est = pairEst;
168
+ }
169
+ // A lower bound at or above the best total can never strictly improve it.
170
+ if (est >= bestMoves.length)
171
+ continue;
172
+ const pFace = p1Moves.length > 0 ? getMoveFace(p1Moves[p1Moves.length - 1]) : -1;
173
+ const ppFace = p1Moves.length > 1 ? getMoveFace(p1Moves[p1Moves.length - 2]) : -1;
174
+ scored.push({ moves: p1Moves, state: s1, est, pFace, ppFace });
175
+ }
176
+ }
177
+ // Sort ALL candidates globally across depths by admissible total lower bound
178
+ scored.sort((left, right) => left.est - right.est);
179
+ for (const cand of scored) {
180
+ if (performance.now() > deadline)
181
+ break;
182
+ // Prune: since candidates are sorted by est, no subsequent candidate can beat bestMoves.length
183
+ if (cand.est >= bestMoves.length)
184
+ break;
185
+ const maxP2 = Math.min(bestMoves.length - cand.moves.length - 1, 15);
186
+ if (maxP2 <= 0)
187
+ continue;
188
+ try {
189
+ const remainingBudget = Math.max(1, deadline - performance.now());
190
+ const attemptTimeout = options.improvePhase2TimeoutMs ??
191
+ options.phase2?.timeoutMs ?? 25;
192
+ const candP2 = await solveCube3Phase2(cand.state, {
193
+ ...options.phase2,
194
+ pairTables: scoredPairTables,
195
+ tables: p2Tables,
196
+ maxDepth: maxP2,
197
+ previousFace: cand.pFace,
198
+ previousPreviousFace: cand.ppFace,
199
+ timeoutMs: Math.min(attemptTimeout, remainingBudget)
200
+ });
201
+ improvementNodes += candP2.visitedNodes;
202
+ const candTotal = cand.moves.length + candP2.moves.length;
203
+ if (candTotal < bestMoves.length) {
204
+ bestMoves = [...cand.moves, ...candP2.moves];
205
+ bestP1 = {
206
+ moves: cand.moves,
207
+ depth: cand.moves.length,
208
+ state: cand.state,
209
+ coordinates: encodeCube3Phase1Coordinates(cand.state),
210
+ visitedNodes: 0
211
+ };
212
+ bestP2 = candP2;
213
+ if (targetLength !== undefined && bestMoves.length <= targetLength) {
214
+ break;
215
+ }
216
+ }
217
+ }
218
+ catch {
219
+ // continue
220
+ }
221
+ }
222
+ return {
223
+ phase1: bestP1,
224
+ phase2: bestP2,
225
+ moves: bestMoves,
226
+ depth: bestMoves.length,
227
+ state: bestP2.state,
228
+ visitedNodes: phase1.visitedNodes + phase2.visitedNodes + improvementNodes
229
+ };
230
+ }
110
231
  return {
111
232
  phase1,
112
233
  phase2,
@@ -116,6 +237,39 @@ export async function solveCube3State(state, options = {}) {
116
237
  visitedNodes: phase1.visitedNodes + phase2.visitedNodes
117
238
  };
118
239
  }
240
+ /**
241
+ * Best-of-N-orientations solve: UD plus rotated copies, mapped back with
242
+ * the inverse rotation. Rotating a solution conjugates the whole picture,
243
+ * so the mapped-back moves solve the original state with their length
244
+ * preserved. Deterministic: ties keep the earliest orientation (UD first).
245
+ */
246
+ async function solveCube3TripleOrientation(state, options) {
247
+ const count = Math.min(Math.max(options.orientations ?? 1, 1), CUBE3_ORIENTATIONS_24.length);
248
+ const singleOptions = { ...options, orientations: 1 };
249
+ let best = await solveCube3State(state, singleOptions);
250
+ let visitedNodes = best.visitedNodes;
251
+ const scramble = invertCube3Algorithm(best.moves);
252
+ const sequences = CUBE3_ORIENTATIONS_24.slice(1, count);
253
+ for (const sequence of sequences) {
254
+ try {
255
+ const rotatedState = applyCube3Algorithm(createSolvedCube3State(), rotateCube3AlgorithmBySequence(scramble, sequence));
256
+ const candidate = await solveCube3State(rotatedState, singleOptions);
257
+ visitedNodes += candidate.visitedNodes;
258
+ if (candidate.moves.length < best.moves.length) {
259
+ best = {
260
+ ...candidate,
261
+ moves: rotateCube3AlgorithmBySequence(candidate.moves, invertCube3RotationSequence(sequence)),
262
+ depth: candidate.moves.length,
263
+ visitedNodes
264
+ };
265
+ }
266
+ }
267
+ catch {
268
+ // A flaky orientation must never fail the whole solve.
269
+ }
270
+ }
271
+ return { ...best, visitedNodes };
272
+ }
119
273
  export function encodeCube3Phase2Coordinates(state) {
120
274
  return {
121
275
  cornerPermutation: encodeCornerPermutationForPhase2(state),
@@ -138,6 +292,23 @@ const PHASE2_MOVE_FACES = CUBE3_PHASE2_MOVES.map((move) => {
138
292
  const char = move[0];
139
293
  return char === "U" ? 0 : char === "D" ? 1 : char === "R" ? 2 : char === "L" ? 3 : char === "F" ? 4 : 5;
140
294
  });
295
+ function createPhase2SearchContext(tables, pairTables, maxDepth, deadline) {
296
+ const scratchLevels = maxDepth + 1;
297
+ const scratchSize = scratchLevels * CUBE3_PHASE2_MOVES.length;
298
+ return {
299
+ tables,
300
+ pairTables,
301
+ maxDepth,
302
+ deadline,
303
+ visitedNodes: 0,
304
+ timedOut: false,
305
+ scratchMove: new Int32Array(scratchSize),
306
+ scratchBound: new Int32Array(scratchSize),
307
+ scratchCp: new Int32Array(scratchSize),
308
+ scratchUep: new Int32Array(scratchSize),
309
+ scratchSp: new Int32Array(scratchSize)
310
+ };
311
+ }
141
312
  function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, previousFace, previousPreviousFace, remainingDepth, path, context) {
142
313
  context.visitedNodes += 1;
143
314
  if (context.deadline !== undefined && (context.visitedNodes & 1023) === 0 && performance.now() > context.deadline) {
@@ -157,41 +328,23 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
157
328
  const spRange = context.tables.moveTables.slicePermutation.range;
158
329
  const cpspTable = context.tables.pruningTables.cornerPermutationSlicePermutation.values;
159
330
  const epspTable = context.tables.pruningTables.udEdgePermutationSlicePermutation.values;
331
+ const pairTables = context.pairTables;
160
332
  const cpOffset = cornerPermutation * moveCount;
161
333
  const epOffset = udEdgePermutation * moveCount;
162
334
  const spOffset = slicePermutation * moveCount;
163
- // Without the pair tables (the default npm package), keep the exact
164
- // reference traversal zero overhead. With them (turbo tier), collect the
165
- // children with the pair-distance bound and explore in ascending bound
166
- // order (stable: ties keep the reference move order): same depth-minimal
167
- // solutions, far fewer nodes.
168
- if (context.pairTables === undefined) {
169
- for (let i = 0; i < validMoves.length; i += 1) {
170
- const moveIndex = validMoves[i];
171
- const nextCornerPermutation = cpTable[cpOffset + moveIndex];
172
- const nextUdEdgePermutation = epTable[epOffset + moveIndex];
173
- const nextSlicePermutation = spTable[spOffset + moveIndex];
174
- const combinedCPIndex = nextCornerPermutation * spRange + nextSlicePermutation;
175
- const combinedEPIndex = nextUdEdgePermutation * spRange + nextSlicePermutation;
176
- const cpDist = (cpspTable[combinedCPIndex >> 1] >> ((combinedCPIndex & 1) << 2)) & 0x0f;
177
- const epDist = (epspTable[combinedEPIndex >> 1] >> ((combinedEPIndex & 1) << 2)) & 0x0f;
178
- const nextLowerBound = cpDist > epDist ? cpDist : epDist;
179
- if (nextLowerBound > remainingDepth - 1) {
180
- continue;
181
- }
182
- path.push(context.tables.phase2Moves[moveIndex]);
183
- if (searchPhase2(nextCornerPermutation, nextUdEdgePermutation, nextSlicePermutation, PHASE2_MOVE_FACES[moveIndex], previousFace, remainingDepth - 1, path, context)) {
184
- return true;
185
- }
186
- path.pop();
187
- if (context.timedOut) {
188
- return false;
189
- }
190
- }
191
- return false;
192
- }
193
- const pairTables = context.pairTables;
194
- const children = [];
335
+ // Collect the surviving children with the pair-tightened lower bound and
336
+ // explore them in ascending bound order (stable: ties keep the reference
337
+ // move order). Without the pair tables this reduces to ordering by
338
+ // max(cpsp, uepsp) same depth-minimal solutions, far fewer nodes than
339
+ // the reference traversal. With them, same set and order as before but
340
+ // without per-node allocation.
341
+ const slot = remainingDepth * moveCount;
342
+ const scratchMove = context.scratchMove;
343
+ const scratchBound = context.scratchBound;
344
+ const scratchCp = context.scratchCp;
345
+ const scratchUep = context.scratchUep;
346
+ const scratchSp = context.scratchSp;
347
+ let childCount = 0;
195
348
  for (let i = 0; i < validMoves.length; i += 1) {
196
349
  const moveIndex = validMoves[i];
197
350
  const nextCornerPermutation = cpTable[cpOffset + moveIndex];
@@ -205,25 +358,48 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
205
358
  if (nextLowerBound > remainingDepth - 1) {
206
359
  continue;
207
360
  }
208
- const pair = pairBound(pairTables, nextCornerPermutation, nextUdEdgePermutation);
209
- if (pair > nextLowerBound) {
210
- nextLowerBound = pair;
361
+ if (pairTables !== undefined) {
362
+ const pair = pairBound(pairTables, nextCornerPermutation, nextUdEdgePermutation);
363
+ if (pair > nextLowerBound) {
364
+ nextLowerBound = pair;
365
+ }
366
+ if (nextLowerBound > remainingDepth - 1) {
367
+ continue;
368
+ }
211
369
  }
212
- if (nextLowerBound > remainingDepth - 1) {
213
- continue;
370
+ scratchMove[slot + childCount] = moveIndex;
371
+ scratchBound[slot + childCount] = nextLowerBound;
372
+ scratchCp[slot + childCount] = nextCornerPermutation;
373
+ scratchUep[slot + childCount] = nextUdEdgePermutation;
374
+ scratchSp[slot + childCount] = nextSlicePermutation;
375
+ childCount += 1;
376
+ }
377
+ // Stable insertion sort by bound (n <= 10; ties keep reference order).
378
+ for (let i = 1; i < childCount; i += 1) {
379
+ const move = scratchMove[slot + i];
380
+ const bound = scratchBound[slot + i];
381
+ const cp = scratchCp[slot + i];
382
+ const uep = scratchUep[slot + i];
383
+ const sp = scratchSp[slot + i];
384
+ let j = i - 1;
385
+ while (j >= 0 && scratchBound[slot + j] > bound) {
386
+ scratchMove[slot + j + 1] = scratchMove[slot + j];
387
+ scratchBound[slot + j + 1] = scratchBound[slot + j];
388
+ scratchCp[slot + j + 1] = scratchCp[slot + j];
389
+ scratchUep[slot + j + 1] = scratchUep[slot + j];
390
+ scratchSp[slot + j + 1] = scratchSp[slot + j];
391
+ j -= 1;
214
392
  }
215
- children.push({
216
- moveIndex,
217
- lowerBound: nextLowerBound,
218
- cornerPermutation: nextCornerPermutation,
219
- udEdgePermutation: nextUdEdgePermutation,
220
- slicePermutation: nextSlicePermutation
221
- });
393
+ scratchMove[slot + j + 1] = move;
394
+ scratchBound[slot + j + 1] = bound;
395
+ scratchCp[slot + j + 1] = cp;
396
+ scratchUep[slot + j + 1] = uep;
397
+ scratchSp[slot + j + 1] = sp;
222
398
  }
223
- children.sort((left, right) => left.lowerBound - right.lowerBound);
224
- for (const child of children) {
225
- path.push(context.tables.phase2Moves[child.moveIndex]);
226
- if (searchPhase2(child.cornerPermutation, child.udEdgePermutation, child.slicePermutation, PHASE2_MOVE_FACES[child.moveIndex], previousFace, remainingDepth - 1, path, context)) {
399
+ for (let i = 0; i < childCount; i += 1) {
400
+ const moveIndex = scratchMove[slot + i];
401
+ path.push(context.tables.phase2Moves[moveIndex]);
402
+ if (searchPhase2(scratchCp[slot + i], scratchUep[slot + i], scratchSp[slot + i], PHASE2_MOVE_FACES[moveIndex], previousFace, remainingDepth - 1, path, context)) {
227
403
  return true;
228
404
  }
229
405
  path.pop();
@@ -272,13 +448,7 @@ function searchPhase1Interleaved(cornerOrientation, edgeOrientation, udSlice, pr
272
448
  const g1State = applyCube3Phase2Moves(context.originalState, path);
273
449
  const start2 = encodeCube3Phase2Coordinates(g1State);
274
450
  const maxPhase2Depth = 6;
275
- const phase2Context = {
276
- tables: context.phase2Tables,
277
- pairTables: undefined,
278
- deadline: context.deadline,
279
- visitedNodes: 0,
280
- timedOut: false
281
- };
451
+ const phase2Context = createPhase2SearchContext(context.phase2Tables, undefined, maxPhase2Depth, context.deadline);
282
452
  for (let d2 = 0; d2 <= maxPhase2Depth; d2 += 1) {
283
453
  const path2 = [];
284
454
  const solved2 = searchPhase2(start2.cornerPermutation, start2.udEdgePermutation, start2.slicePermutation, -1, -1, d2, path2, phase2Context);
@@ -5,5 +5,5 @@ export declare function getMoveFaceByIndex(moveIndex: number, moves: readonly Cu
5
5
  export declare const ARE_OPPOSITE_FACES: readonly boolean[];
6
6
  export declare function areOppositeFaces(left: Cube3SearchFace, right: Cube3SearchFace): boolean;
7
7
  export declare function shouldSkipCube3SearchMove(face: Cube3SearchFace, previousFace: Cube3SearchFace | undefined, previousPreviousFace: Cube3SearchFace | undefined): boolean;
8
- export declare function lookupMoveTableByIndex(values: Int32Array, coordinate: number, moveIndex: number, moveCount: number): number;
8
+ export declare function lookupMoveTableByIndex(values: Int32Array | Uint16Array, coordinate: number, moveIndex: number, moveCount: number): number;
9
9
  //# sourceMappingURL=search-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"search-utils.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/search-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAYrC,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,CAE5D;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,eAAe,CAElG;AAED,eAAO,MAAM,kBAAkB,EAAE,SAAS,OAAO,EAOhD,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAEvF;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,eAAe,EACrB,YAAY,EAAE,eAAe,GAAG,SAAS,EACzC,oBAAoB,EAAE,eAAe,GAAG,SAAS,GAChD,OAAO,CAcT;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,MAAM,CAER"}
1
+ {"version":3,"file":"search-utils.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/search-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAYrC,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,CAE5D;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,eAAe,CAElG;AAED,eAAO,MAAM,kBAAkB,EAAE,SAAS,OAAO,EAOhD,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAEvF;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,eAAe,EACrB,YAAY,EAAE,eAAe,GAAG,SAAS,EACzC,oBAAoB,EAAE,eAAe,GAAG,SAAS,GAChD,OAAO,CAcT;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,UAAU,GAAG,WAAW,EAChC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,MAAM,CAER"}
@@ -0,0 +1,24 @@
1
+ import type { Cube3Move } from "../types.js";
2
+ /**
3
+ * Whole-cube rotations for 3x3 move sequences.
4
+ *
5
+ * Face maps ported from the 4x4 solver's `ROT_MAP`
6
+ * (`puzzles/cube4/reduction/solve.ts`), restricted to the six basic faces —
7
+ * kept cube3-local so `solver/` never imports from `cube4` (which itself
8
+ * depends on this solver).
9
+ */
10
+ export type Cube3Rotation = "x" | "x'" | "z" | "z'";
11
+ export declare const CUBE3_ROTATION_INVERSE: Record<Cube3Rotation, Cube3Rotation>;
12
+ export type Cube3RotationSequence = readonly string[];
13
+ /**
14
+ * 24 whole-cube orientations as rotation sequences, discovered by BFS over
15
+ * the group generated by x/y/z (canonicalized by face map, so all 24 are
16
+ * geometrically distinct by construction).
17
+ * The first three are identity, x, z — so `orientations: 3` keeps the
18
+ * legacy UD/x/z triple exactly.
19
+ */
20
+ export declare const CUBE3_ORIENTATIONS_24: readonly Cube3RotationSequence[];
21
+ export declare function invertCube3RotationSequence(sequence: Cube3RotationSequence): string[];
22
+ export declare function rotateCube3AlgorithmBySequence(moves: readonly Cube3Move[], sequence: Cube3RotationSequence): Cube3Move[];
23
+ export declare function rotateCube3Algorithm(moves: readonly Cube3Move[], rotation: Cube3Rotation): Cube3Move[];
24
+ //# sourceMappingURL=symmetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symmetry.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/symmetry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC;AAqBpD,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,aAAa,EAAE,aAAa,CAKvE,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,SAAS,MAAM,EAAE,CAAC;AAEtD;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,qBAAqB,EA4B9D,CAAC;AAEL,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,EAAE,CAErF;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,QAAQ,EAAE,qBAAqB,GAC9B,SAAS,EAAE,CAOb;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,QAAQ,EAAE,aAAa,GACtB,SAAS,EAAE,CAGb"}
@@ -0,0 +1,74 @@
1
+ const ROT_FACE = {
2
+ "x": { "U": "B", "R": "R", "F": "U", "D": "F", "L": "L", "B": "D" },
3
+ "x'": { "U": "F", "R": "R", "F": "D", "D": "B", "L": "L", "B": "U" },
4
+ "y": { "U": "U", "R": "F", "F": "L", "D": "D", "L": "B", "B": "R" },
5
+ "y'": { "U": "U", "R": "B", "F": "R", "D": "D", "L": "F", "B": "L" },
6
+ "z": { "U": "R", "R": "D", "F": "F", "D": "L", "L": "U", "B": "B" },
7
+ "z'": { "U": "L", "R": "U", "F": "F", "D": "R", "L": "D", "B": "B" },
8
+ "x2": { "U": "D", "R": "R", "F": "B", "D": "U", "L": "L", "B": "F" },
9
+ "y2": { "U": "U", "R": "L", "F": "B", "D": "D", "L": "R", "B": "F" },
10
+ "z2": { "U": "D", "R": "L", "F": "F", "D": "U", "L": "R", "B": "B" }
11
+ };
12
+ const INVERT_ROTATION = {
13
+ "x": "x'", "x'": "x",
14
+ "y": "y'", "y'": "y",
15
+ "z": "z'", "z'": "z",
16
+ "x2": "x2", "y2": "y2", "z2": "z2"
17
+ };
18
+ export const CUBE3_ROTATION_INVERSE = {
19
+ "x": "x'",
20
+ "x'": "x",
21
+ "z": "z'",
22
+ "z'": "z"
23
+ };
24
+ /**
25
+ * 24 whole-cube orientations as rotation sequences, discovered by BFS over
26
+ * the group generated by x/y/z (canonicalized by face map, so all 24 are
27
+ * geometrically distinct by construction).
28
+ * The first three are identity, x, z — so `orientations: 3` keeps the
29
+ * legacy UD/x/z triple exactly.
30
+ */
31
+ export const CUBE3_ORIENTATIONS_24 = (() => {
32
+ const faces = ["U", "R", "F", "D", "L", "B"];
33
+ const identity = "URFDLB";
34
+ const composeKey = (key, gen) => {
35
+ const map = ROT_FACE[gen];
36
+ return faces.map((face, index) => map[key[index]]).join("");
37
+ };
38
+ const seen = new Map([[identity, []]]);
39
+ const queue = [identity];
40
+ while (queue.length > 0) {
41
+ const key = queue.shift();
42
+ const sequence = seen.get(key);
43
+ for (const gen of ["x", "y", "z"]) {
44
+ const next = composeKey(key, gen);
45
+ if (!seen.has(next)) {
46
+ seen.set(next, [...sequence, gen]);
47
+ queue.push(next);
48
+ }
49
+ }
50
+ }
51
+ const isLegacyTriple = (sequence) => sequence.length === 0 ||
52
+ (sequence.length === 1 && (sequence[0] === "x" || sequence[0] === "z"));
53
+ const rest = [];
54
+ for (const sequence of seen.values()) {
55
+ if (!isLegacyTriple(sequence))
56
+ rest.push(sequence);
57
+ }
58
+ return [[], ["x"], ["z"], ...rest];
59
+ })();
60
+ export function invertCube3RotationSequence(sequence) {
61
+ return [...sequence].reverse().map((rotation) => INVERT_ROTATION[rotation]);
62
+ }
63
+ export function rotateCube3AlgorithmBySequence(moves, sequence) {
64
+ let result = [...moves];
65
+ for (const rotation of sequence) {
66
+ const map = ROT_FACE[rotation];
67
+ result = result.map((move) => `${map[move[0]]}${move.slice(1)}`);
68
+ }
69
+ return result;
70
+ }
71
+ export function rotateCube3Algorithm(moves, rotation) {
72
+ const map = ROT_FACE[rotation];
73
+ return moves.map((move) => `${map[move[0]]}${move.slice(1)}`);
74
+ }
@@ -7,7 +7,12 @@ export interface Cube3MoveTable {
7
7
  moves: readonly Cube3Move[];
8
8
  range: number;
9
9
  solvedCoordinate: number;
10
- values: Int32Array;
10
+ /**
11
+ * Move-table values. Every coordinate fits in 16 bits (max range 40320),
12
+ * so loaders store `Uint16Array` (half the RAM/cache of the int32 wire
13
+ * format); table generation still builds `Int32Array`.
14
+ */
15
+ values: Int32Array | Uint16Array;
11
16
  checksum: number;
12
17
  }
13
18
  export interface Cube3PruningTable {
@@ -1 +1 @@
1
- {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/tables.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAM7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAQ7D,MAAM,MAAM,mBAAmB,GAAG,MAAM,oBAAoB,CAAC;AAE7D,MAAM,MAAM,qBAAqB,GAC7B,mBAAmB,GACnB,kCAAkC,GAClC,mCAAmC,GACnC,mCAAmC,CAAC;AAExC,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,mBAAmB,CAAC;IAChC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,SAAS,SAAS,EAAE,CAAC;IAClC,WAAW,EAAE,SAAS,SAAS,EAAE,CAAC;IAClC,UAAU,EAAE;QACV,iBAAiB,EAAE,cAAc,CAAC;QAClC,eAAe,EAAE,cAAc,CAAC;QAChC,OAAO,EAAE,cAAc,CAAC;QACxB,iBAAiB,EAAE,cAAc,CAAC;QAClC,iBAAiB,EAAE,cAAc,CAAC;QAClC,gBAAgB,EAAE,cAAc,CAAC;KAClC,CAAC;IACF,aAAa,EAAE;QACb,iBAAiB,EAAE,iBAAiB,CAAC;QACrC,eAAe,EAAE,iBAAiB,CAAC;QACnC,OAAO,EAAE,iBAAiB,CAAC;QAC3B,gCAAgC,EAAE,iBAAiB,CAAC;QACpD,iBAAiB,EAAE,iBAAiB,CAAC;QACrC,iBAAiB,EAAE,iBAAiB,CAAC;QACrC,gBAAgB,EAAE,iBAAiB,CAAC;QACpC,iCAAiC,EAAE,iBAAiB,CAAC;QACrD,iCAAiC,EAAE,iBAAiB,CAAC;KACtD,CAAC;IACF,WAAW,EAAE,gBAAgB,GAAG,SAAS,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,SAAS,EAAE,UAAU,CAAC;IACtB,qEAAqE;IACrE,SAAS,EAAE,UAAU,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,SAAS,SAAS,EAAE,CAAC;IAClC,UAAU,EAAE,IAAI,CACd,iBAAiB,CAAC,YAAY,CAAC,EAC/B,mBAAmB,GAAG,iBAAiB,GAAG,SAAS,CACpD,CAAC;IACF,aAAa,EAAE,IAAI,CACjB,iBAAiB,CAAC,eAAe,CAAC,EAClC,mBAAmB,GAAG,iBAAiB,GAAG,SAAS,CACpD,GAAG;QACF,gCAAgC,EAAE,iBAAiB,CAAC;KACrD,CAAC;IACF,kEAAkE;IAClE,WAAW,EAAE,gBAAgB,GAAG,SAAS,CAAC;CAC3C;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,SAAS,SAAS,EAAE,CAAC;IAClC,UAAU,EAAE,IAAI,CACd,iBAAiB,CAAC,YAAY,CAAC,EAC/B,mBAAmB,GAAG,mBAAmB,GAAG,kBAAkB,CAC/D,CAAC;IACF,aAAa,EAAE,IAAI,CACjB,iBAAiB,CAAC,eAAe,CAAC,EAClC,mBAAmB,GAAG,mBAAmB,GAAG,kBAAkB,CAC/D,GAAG;QACF,iCAAiC,EAAE,iBAAiB,CAAC;QACrD,iCAAiC,EAAE,iBAAiB,CAAC;KACtD,CAAC;CACH;AAED,eAAO,MAAM,kBAAkB,oIAAc,CAAC;AAC9C,eAAO,MAAM,kBAAkB,EAAE,SAAS,SAAS,EAWlD,CAAC;AAMF,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAGxE;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAGxE;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAGxE;AAED,wBAAgB,yBAAyB,IAAI,iBAAiB,CAmC7D;AAED,wBAAgB,yBAAyB,IAAI,iBAAiB,CA4B7D;AAED,wBAAgB,yBAAyB,IAAI,iBAAiB,CAsC7D;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,mBAAmB,EAC/B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,SAAS,SAAS,EAAE,GAC1B,cAAc,CAqBhB;AAED,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,cAAc,GAAG,iBAAiB,CAsCtF;AAED,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,cAAc,GACrB,iBAAiB,CAgDnB;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,cAAc,EACrB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,SAAS,GACd,MAAM,CAQR;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,MAAM,CAS1D"}
1
+ {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/tables.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube3/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAIpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,qBAAqB,CA+B3E;AASD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAExE;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAIpE;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAYvE"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube3/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAIpE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,qBAAqB,CA+B3E;AASD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAExE;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAIpE;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAmBvE"}
@@ -41,15 +41,23 @@ export function isAllowedRandomCube3State(state) {
41
41
  !isOneMoveFromSolvedCube3State(state);
42
42
  }
43
43
  export function permutationParity(permutation) {
44
- let inversions = 0;
45
- for (let left = 0; left < permutation.length; left += 1) {
46
- for (let right = left + 1; right < permutation.length; right += 1) {
47
- if (permutation[left] > permutation[right]) {
48
- inversions += 1;
49
- }
44
+ // Cycle-decomposition parity in O(n) instead of O(n²) inversion counting
45
+ // (identical result: parity = (n cycles) mod 2).
46
+ const visited = new Array(permutation.length).fill(false);
47
+ let parity = 0;
48
+ for (let start = 0; start < permutation.length; start += 1) {
49
+ if (visited[start] || permutation[start] === start)
50
+ continue;
51
+ let length = 0;
52
+ let current = start;
53
+ while (!visited[current]) {
54
+ visited[current] = true;
55
+ current = permutation[current];
56
+ length += 1;
50
57
  }
58
+ parity ^= (length - 1) & 1;
51
59
  }
52
- return (inversions % 2);
60
+ return parity;
53
61
  }
54
62
  function validatePermutation(label, permutation, length) {
55
63
  const errors = [];