@cubingcenter/scrambler 0.6.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +17 -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/cube2/solver/search.d.ts.map +1 -1
  10. package/dist/puzzles/cube2/solver/search.js +3 -3
  11. package/dist/puzzles/cube2/solver/tables.d.ts +6 -0
  12. package/dist/puzzles/cube2/solver/tables.d.ts.map +1 -1
  13. package/dist/puzzles/cube2/solver/tables.js +16 -2
  14. package/dist/puzzles/cube3/index.d.ts +1 -0
  15. package/dist/puzzles/cube3/index.d.ts.map +1 -1
  16. package/dist/puzzles/cube3/moves.d.ts.map +1 -1
  17. package/dist/puzzles/cube3/moves.js +104 -18
  18. package/dist/puzzles/cube3/scrambler.d.ts +16 -1
  19. package/dist/puzzles/cube3/scrambler.d.ts.map +1 -1
  20. package/dist/puzzles/cube3/scrambler.js +1 -1
  21. package/dist/puzzles/cube3/solver/browser-loader.d.ts.map +1 -1
  22. package/dist/puzzles/cube3/solver/browser-loader.js +48 -22
  23. package/dist/puzzles/cube3/solver/generated-assets.d.ts.map +1 -1
  24. package/dist/puzzles/cube3/solver/generated-assets.js +9 -3
  25. package/dist/puzzles/cube3/solver/index.d.ts +5 -3
  26. package/dist/puzzles/cube3/solver/index.d.ts.map +1 -1
  27. package/dist/puzzles/cube3/solver/index.js +2 -1
  28. package/dist/puzzles/cube3/solver/node-loader.js +9 -2
  29. package/dist/puzzles/cube3/solver/phase1.d.ts +37 -0
  30. package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
  31. package/dist/puzzles/cube3/solver/phase1.js +156 -15
  32. package/dist/puzzles/cube3/solver/phase2.d.ts +75 -0
  33. package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
  34. package/dist/puzzles/cube3/solver/phase2.js +279 -68
  35. package/dist/puzzles/cube3/solver/search-utils.d.ts +1 -1
  36. package/dist/puzzles/cube3/solver/search-utils.d.ts.map +1 -1
  37. package/dist/puzzles/cube3/solver/symmetry.d.ts +24 -0
  38. package/dist/puzzles/cube3/solver/symmetry.d.ts.map +1 -0
  39. package/dist/puzzles/cube3/solver/symmetry.js +74 -0
  40. package/dist/puzzles/cube3/solver/tables.d.ts +6 -1
  41. package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
  42. package/dist/puzzles/cube3/solver/tables.js +14 -12
  43. package/dist/puzzles/cube3/validation.d.ts.map +1 -1
  44. package/dist/puzzles/cube3/validation.js +15 -7
  45. package/dist/puzzles/cube4/browser-worker-pool.d.ts +82 -0
  46. package/dist/puzzles/cube4/browser-worker-pool.d.ts.map +1 -0
  47. package/dist/puzzles/cube4/browser-worker-pool.js +205 -0
  48. package/dist/puzzles/cube4/browser-worker.d.ts +2 -0
  49. package/dist/puzzles/cube4/browser-worker.d.ts.map +1 -0
  50. package/dist/puzzles/cube4/browser-worker.js +31 -0
  51. package/dist/puzzles/cube4/index.d.ts +10 -2
  52. package/dist/puzzles/cube4/index.d.ts.map +1 -1
  53. package/dist/puzzles/cube4/index.js +6 -2
  54. package/dist/puzzles/cube4/moves.d.ts +5 -0
  55. package/dist/puzzles/cube4/moves.d.ts.map +1 -1
  56. package/dist/puzzles/cube4/moves.js +212 -11
  57. package/dist/puzzles/cube4/reduction/browser-loader.d.ts +29 -1
  58. package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -1
  59. package/dist/puzzles/cube4/reduction/browser-loader.js +227 -32
  60. package/dist/puzzles/cube4/reduction/center2.d.ts +44 -3
  61. package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -1
  62. package/dist/puzzles/cube4/reduction/center2.js +152 -10
  63. package/dist/puzzles/cube4/reduction/edge3.d.ts +4 -0
  64. package/dist/puzzles/cube4/reduction/edge3.d.ts.map +1 -1
  65. package/dist/puzzles/cube4/reduction/edge3.js +23 -3
  66. package/dist/puzzles/cube4/reduction/finish3.d.ts +53 -0
  67. package/dist/puzzles/cube4/reduction/finish3.d.ts.map +1 -0
  68. package/dist/puzzles/cube4/reduction/finish3.js +393 -0
  69. package/dist/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
  70. package/dist/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
  71. package/dist/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
  72. package/dist/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
  73. package/dist/puzzles/cube4/reduction/generated/hc-manifest.json +44 -0
  74. package/dist/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
  75. package/dist/puzzles/cube4/reduction/hc-pdb.d.ts +52 -0
  76. package/dist/puzzles/cube4/reduction/hc-pdb.d.ts.map +1 -0
  77. package/dist/puzzles/cube4/reduction/hc-pdb.js +220 -0
  78. package/dist/puzzles/cube4/reduction/node-loader.d.ts +15 -1
  79. package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -1
  80. package/dist/puzzles/cube4/reduction/node-loader.js +199 -25
  81. package/dist/puzzles/cube4/reduction/pair-transversal.d.ts +28 -0
  82. package/dist/puzzles/cube4/reduction/pair-transversal.d.ts.map +1 -0
  83. package/dist/puzzles/cube4/reduction/pair-transversal.js +107 -0
  84. package/dist/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
  85. package/dist/puzzles/cube4/reduction/phase3-search.d.ts +116 -7
  86. package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -1
  87. package/dist/puzzles/cube4/reduction/phase3-search.js +458 -32
  88. package/dist/puzzles/cube4/reduction/rotation-fix.d.ts +28 -0
  89. package/dist/puzzles/cube4/reduction/rotation-fix.d.ts.map +1 -0
  90. package/dist/puzzles/cube4/reduction/rotation-fix.js +212 -0
  91. package/dist/puzzles/cube4/reduction/solve.d.ts +172 -2
  92. package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -1
  93. package/dist/puzzles/cube4/reduction/solve.js +1201 -180
  94. package/dist/puzzles/cube4/scramble-cache.d.ts +93 -0
  95. package/dist/puzzles/cube4/scramble-cache.d.ts.map +1 -0
  96. package/dist/puzzles/cube4/scramble-cache.js +150 -0
  97. package/dist/puzzles/cube4/scrambler.d.ts +201 -6
  98. package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
  99. package/dist/puzzles/cube4/scrambler.js +414 -176
  100. package/dist/puzzles/cube4/state.d.ts.map +1 -1
  101. package/dist/puzzles/cube4/state.js +4 -1
  102. package/dist/puzzles/cube4/worker-pool.d.ts +139 -0
  103. package/dist/puzzles/cube4/worker-pool.d.ts.map +1 -0
  104. package/dist/puzzles/cube4/worker-pool.js +405 -0
  105. package/dist/puzzles/cube4/worker-protocol.d.ts +58 -0
  106. package/dist/puzzles/cube4/worker-protocol.d.ts.map +1 -0
  107. package/dist/puzzles/cube4/worker-protocol.js +58 -0
  108. package/dist/puzzles/cube4/worker.d.ts +26 -0
  109. package/dist/puzzles/cube4/worker.d.ts.map +1 -0
  110. package/dist/puzzles/cube4/worker.js +49 -0
  111. package/dist/puzzles/pyraminx/solver/browser-loader.d.ts +4 -8
  112. package/dist/puzzles/pyraminx/solver/browser-loader.d.ts.map +1 -1
  113. package/dist/puzzles/pyraminx/solver/browser-loader.js +44 -15
  114. package/dist/puzzles/pyraminx/solver/generated-assets.d.ts.map +1 -1
  115. package/dist/puzzles/pyraminx/solver/generated-assets.js +8 -3
  116. package/dist/puzzles/pyraminx/solver/search.d.ts.map +1 -1
  117. package/dist/puzzles/pyraminx/solver/search.js +3 -6
  118. package/dist/puzzles/pyraminx/solver/tables.d.ts +6 -0
  119. package/dist/puzzles/pyraminx/solver/tables.d.ts.map +1 -1
  120. package/dist/puzzles/pyraminx/solver/tables.js +17 -0
  121. package/dist/puzzles/shared/compressed-fetch.d.ts +37 -1
  122. package/dist/puzzles/shared/compressed-fetch.d.ts.map +1 -1
  123. package/dist/puzzles/shared/compressed-fetch.js +226 -31
  124. package/dist/puzzles/shared/load-once.d.ts +16 -0
  125. package/dist/puzzles/shared/load-once.d.ts.map +1 -0
  126. package/dist/puzzles/shared/load-once.js +11 -0
  127. package/dist/puzzles/skewb/solver/browser-loader.d.ts +4 -11
  128. package/dist/puzzles/skewb/solver/browser-loader.d.ts.map +1 -1
  129. package/dist/puzzles/skewb/solver/browser-loader.js +44 -16
  130. package/dist/puzzles/skewb/solver/generated-assets.d.ts.map +1 -1
  131. package/dist/puzzles/skewb/solver/generated-assets.js +8 -3
  132. package/dist/puzzles/skewb/solver/search.js +3 -3
  133. package/dist/puzzles/skewb/solver/tables.d.ts +6 -0
  134. package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -1
  135. package/dist/puzzles/skewb/solver/tables.js +17 -0
  136. package/dist/puzzles/sq1/solver/browser-loader.d.ts.map +1 -1
  137. package/dist/puzzles/sq1/solver/browser-loader.js +58 -26
  138. package/dist-cjs/core/api.js +10 -2
  139. package/dist-cjs/puzzles/cube2/solver/browser-loader.js +42 -14
  140. package/dist-cjs/puzzles/cube2/solver/generated-assets.js +8 -3
  141. package/dist-cjs/puzzles/cube2/solver/search.js +2 -2
  142. package/dist-cjs/puzzles/cube2/solver/tables.js +17 -3
  143. package/dist-cjs/puzzles/cube3/moves.js +104 -18
  144. package/dist-cjs/puzzles/cube3/scrambler.js +1 -1
  145. package/dist-cjs/puzzles/cube3/solver/browser-loader.js +48 -22
  146. package/dist-cjs/puzzles/cube3/solver/generated-assets.js +9 -3
  147. package/dist-cjs/puzzles/cube3/solver/index.js +8 -1
  148. package/dist-cjs/puzzles/cube3/solver/node-loader.js +9 -2
  149. package/dist-cjs/puzzles/cube3/solver/phase1.js +157 -15
  150. package/dist-cjs/puzzles/cube3/solver/phase2.js +276 -65
  151. package/dist-cjs/puzzles/cube3/solver/symmetry.js +80 -0
  152. package/dist-cjs/puzzles/cube3/solver/tables.js +14 -12
  153. package/dist-cjs/puzzles/cube3/validation.js +15 -7
  154. package/dist-cjs/puzzles/cube4/browser-worker-pool.js +211 -0
  155. package/dist-cjs/puzzles/cube4/index.js +23 -1
  156. package/dist-cjs/puzzles/cube4/moves.js +213 -11
  157. package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +227 -32
  158. package/dist-cjs/puzzles/cube4/reduction/center2.js +155 -10
  159. package/dist-cjs/puzzles/cube4/reduction/edge3.js +24 -3
  160. package/dist-cjs/puzzles/cube4/reduction/finish3.js +398 -0
  161. package/dist-cjs/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
  162. package/dist-cjs/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
  163. package/dist-cjs/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
  164. package/dist-cjs/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
  165. package/dist-cjs/puzzles/cube4/reduction/generated/hc-manifest.json +44 -0
  166. package/dist-cjs/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
  167. package/dist-cjs/puzzles/cube4/reduction/hc-pdb.js +232 -0
  168. package/dist-cjs/puzzles/cube4/reduction/node-loader.js +200 -25
  169. package/dist-cjs/puzzles/cube4/reduction/pair-transversal.js +112 -0
  170. package/dist-cjs/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
  171. package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +460 -33
  172. package/dist-cjs/puzzles/cube4/reduction/rotation-fix.js +216 -0
  173. package/dist-cjs/puzzles/cube4/reduction/solve.js +1203 -172
  174. package/dist-cjs/puzzles/cube4/scramble-cache.js +156 -0
  175. package/dist-cjs/puzzles/cube4/scrambler.js +416 -174
  176. package/dist-cjs/puzzles/cube4/state.js +4 -1
  177. package/dist-cjs/puzzles/cube4/worker-pool.js +447 -0
  178. package/dist-cjs/puzzles/cube4/worker-protocol.js +62 -0
  179. package/dist-cjs/puzzles/pyraminx/solver/browser-loader.js +44 -15
  180. package/dist-cjs/puzzles/pyraminx/solver/generated-assets.js +8 -3
  181. package/dist-cjs/puzzles/pyraminx/solver/search.js +2 -5
  182. package/dist-cjs/puzzles/pyraminx/solver/tables.js +18 -1
  183. package/dist-cjs/puzzles/shared/compressed-fetch.js +229 -31
  184. package/dist-cjs/puzzles/shared/load-once.js +14 -0
  185. package/dist-cjs/puzzles/skewb/solver/browser-loader.js +44 -16
  186. package/dist-cjs/puzzles/skewb/solver/generated-assets.js +8 -3
  187. package/dist-cjs/puzzles/skewb/solver/search.js +2 -2
  188. package/dist-cjs/puzzles/skewb/solver/tables.js +18 -1
  189. package/dist-cjs/puzzles/sq1/solver/browser-loader.js +58 -26
  190. package/docs/development.md +19 -7
  191. package/package.json +17 -7
@@ -0,0 +1,216 @@
1
+ "use strict";
2
+ /**
3
+ * Placement of the whole-cube rotation fix inside a 4x4 solution.
4
+ *
5
+ * The reduction ends with the cube solved in some orientation, so the solve
6
+ * is `P ++ W(R) ++ Q` where `W(R)` are the 2-4 wide moves of the rotation R
7
+ * (`x` = `Rw Lw'`, ...). A rotation commutes with any move sequence once the
8
+ * sequence's faces are re-mapped:
9
+ *
10
+ * X ++ W(R) ≡ W(R) ++ rotateAlgorithm(X, R)
11
+ *
12
+ * so the fix can sit at ANY point of the solution (with the moves after it
13
+ * re-mapped), and a two-rotation fix can be split across two points. The
14
+ * total is unchanged except at the insertion junctions, where the fix can
15
+ * cancel against same-axis wide moves (`Uw2 · y2` = `Uw2 · Uw2 Dw2` = `Dw2`).
16
+ * `placeRotationFix` picks the placement with the shortest simplified total.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.ROTATION_WORDS = void 0;
20
+ exports.placeRotationFix = placeRotationFix;
21
+ const moves_js_1 = require("../moves.js");
22
+ const state_js_1 = require("../state.js");
23
+ /** Wide-move words of the 9 single rotations. */
24
+ exports.ROTATION_WORDS = {
25
+ "x": ["Rw", "Lw'"],
26
+ "y": ["Uw", "Dw'"],
27
+ "z": ["Fw", "Bw'"],
28
+ "x'": ["Rw'", "Lw"],
29
+ "y'": ["Uw'", "Dw"],
30
+ "z'": ["Fw'", "Bw"],
31
+ "x2": ["Rw2", "Lw2"],
32
+ "y2": ["Uw2", "Dw2"],
33
+ "z2": ["Fw2", "Bw2"]
34
+ };
35
+ const FACE_MAPS = {
36
+ "x": { U: "B", R: "R", F: "U", D: "F", L: "L", B: "D" },
37
+ "x'": { U: "F", R: "R", F: "D", D: "B", L: "L", B: "U" },
38
+ "y": { U: "U", R: "F", F: "L", D: "D", L: "B", B: "R" },
39
+ "y'": { U: "U", R: "B", F: "R", D: "D", L: "F", B: "L" },
40
+ "z": { U: "R", R: "D", F: "F", D: "L", L: "U", B: "B" },
41
+ "z'": { U: "L", R: "U", F: "F", D: "R", L: "D", B: "B" },
42
+ "x2": { U: "D", R: "R", F: "B", D: "U", L: "L", B: "F" },
43
+ "y2": { U: "U", R: "L", F: "B", D: "D", L: "R", B: "F" },
44
+ "z2": { U: "D", R: "L", F: "F", D: "U", L: "R", B: "B" }
45
+ };
46
+ const FACES = ["U", "R", "F", "D", "L", "B"];
47
+ /** Composite face map of a rotation sequence (applied left to right). */
48
+ function faceMapOf(rotations) {
49
+ const map = {};
50
+ for (const face of FACES) {
51
+ let current = face;
52
+ for (const rotation of rotations)
53
+ current = FACE_MAPS[rotation][current];
54
+ map[face] = current;
55
+ }
56
+ return map;
57
+ }
58
+ function remap(moves, map) {
59
+ const out = new Array(moves.length);
60
+ for (let i = 0; i < moves.length; i++) {
61
+ const move = moves[i];
62
+ out[i] = (map[move[0]] + move.slice(1));
63
+ }
64
+ return out;
65
+ }
66
+ const rotationKey = (rotations) => (0, moves_js_1.applyCube4Algorithm)((0, state_js_1.createSolvedCube4State)(), rotations.flatMap((rotation) => exports.ROTATION_WORDS[rotation])).centers.join(",");
67
+ /**
68
+ * Every factorization of each rotation element into 1 or 2 single rotations
69
+ * (keyed by the rotated solved cube's centers). Non-minimal ones are kept on
70
+ * purpose: `x2` as `x · x` costs 4 moves but can cancel at two junctions.
71
+ */
72
+ const FACTORIZATIONS = (() => {
73
+ const names = Object.keys(exports.ROTATION_WORDS);
74
+ const byKey = new Map();
75
+ const add = (sequence) => {
76
+ const key = rotationKey(sequence);
77
+ const list = byKey.get(key);
78
+ if (!list)
79
+ byKey.set(key, [sequence]);
80
+ else
81
+ list.push(sequence);
82
+ };
83
+ for (const a of names)
84
+ add([a]);
85
+ for (const a of names)
86
+ for (const b of names)
87
+ add([a, b]);
88
+ return byKey;
89
+ })();
90
+ const AXIS_OF_FACE = { U: 0, D: 0, R: 1, L: 1, F: 2, B: 2 };
91
+ /** Segment boundaries (maximal same-axis runs) of a sequence: 0, ..., n. */
92
+ function runBoundaries(moves) {
93
+ const bounds = [0];
94
+ for (let i = 1; i < moves.length; i++) {
95
+ if (AXIS_OF_FACE[moves[i][0]] !== AXIS_OF_FACE[moves[i - 1][0]])
96
+ bounds.push(i);
97
+ }
98
+ if (moves.length > 0)
99
+ bounds.push(moves.length);
100
+ return bounds;
101
+ }
102
+ /** Moves saved by simplifying across one inserted word, judged on a window of 2 runs per side. */
103
+ function junctionSaving(moves, bounds, b, word, leftMap, rightMap) {
104
+ const lo = bounds[Math.max(0, b - 2)];
105
+ const hi = bounds[Math.min(bounds.length - 1, b + 2)];
106
+ const at = bounds[b];
107
+ const left = moves.slice(lo, at);
108
+ const window = [...(leftMap ? remap(left, leftMap) : left), ...word, ...remap(moves.slice(at, hi), rightMap)];
109
+ return window.length - (0, moves_js_1.simplifyCube4Algorithm)(window).length;
110
+ }
111
+ /**
112
+ * Shortest simplified solution equivalent to `prefix ++ W(rotation) ++
113
+ * suffix` (same effect on every state), over every run-boundary insertion
114
+ * point of the fix and every minimal factorization of the rotation. Only
115
+ * run boundaries matter: the fix can only merge with runs on its own axis,
116
+ * and re-mapping preserves the run structure of what follows it.
117
+ */
118
+ function placeRotationFix(prefix, rotation, suffix) {
119
+ const fixed = (0, moves_js_1.simplifyCube4Algorithm)([
120
+ ...prefix,
121
+ ...rotation.flatMap((r) => exports.ROTATION_WORDS[r]),
122
+ ...suffix
123
+ ]);
124
+ if (rotation.length === 0)
125
+ return fixed;
126
+ let best = fixed;
127
+ const consider = (candidate) => {
128
+ const simplified = (0, moves_js_1.simplifyCube4Algorithm)(candidate);
129
+ if (simplified.length < best.length)
130
+ best = simplified;
131
+ };
132
+ // A = prefix ++ Q' with W(R) ++ suffix ≡ Q' ++ W(R): Q' = remap(suffix, R⁻¹).
133
+ const forward = faceMapOf(rotation);
134
+ const inverseMap = {};
135
+ for (const face of FACES)
136
+ inverseMap[forward[face]] = face;
137
+ const all = (0, moves_js_1.simplifyCube4Algorithm)([...prefix, ...remap(suffix, inverseMap)]);
138
+ const bounds = runBoundaries(all);
139
+ const m = bounds.length;
140
+ const identity = faceMapOf([]);
141
+ for (const factors of FACTORIZATIONS.get(rotationKey(rotation)) ?? [rotation]) {
142
+ if (factors.length === 1) {
143
+ const map = faceMapOf(factors);
144
+ const word = exports.ROTATION_WORDS[factors[0]];
145
+ let bestB = -1;
146
+ let bestSaving = 0;
147
+ for (let b = 0; b < m; b++) {
148
+ const saving = junctionSaving(all, bounds, b, word, null, map);
149
+ if (saving > bestSaving) {
150
+ bestSaving = saving;
151
+ bestB = b;
152
+ }
153
+ }
154
+ if (bestB >= 0) {
155
+ const k = bounds[bestB];
156
+ consider([...all.slice(0, k), ...word, ...remap(all.slice(k), map)]);
157
+ }
158
+ continue;
159
+ }
160
+ const map1 = faceMapOf([factors[0]]);
161
+ const map12 = faceMapOf(factors);
162
+ const word1 = exports.ROTATION_WORDS[factors[0]];
163
+ const word2 = exports.ROTATION_WORDS[factors[1]];
164
+ const build = (b1, b2) => {
165
+ const k1 = bounds[b1];
166
+ const k2 = bounds[b2];
167
+ return [
168
+ ...all.slice(0, k1),
169
+ ...word1,
170
+ ...remap(all.slice(k1, k2), map1),
171
+ ...word2,
172
+ ...remap(all.slice(k2), map12)
173
+ ];
174
+ };
175
+ // Far-apart junctions save independently: s1 (unmapped | map1) at b1,
176
+ // s2 (map1 | map12) at b2 >= b1 + 3, combined with a suffix maximum.
177
+ // Both words at one point (b1 == b2) is a single junction (s12); nearby
178
+ // pairs interact, so the best few estimates are re-scored exactly.
179
+ const s1 = new Int32Array(m);
180
+ const s2 = new Int32Array(m);
181
+ const word12 = [...word1, ...word2];
182
+ const shortlist = [];
183
+ for (let b = 0; b < m; b++) {
184
+ s1[b] = junctionSaving(all, bounds, b, word1, identity, map1);
185
+ s2[b] = junctionSaving(all, bounds, b, word2, map1, map12);
186
+ const s12 = junctionSaving(all, bounds, b, word12, identity, map12);
187
+ if (s12 > 0)
188
+ shortlist.push([s12, b, b]);
189
+ }
190
+ let suffixBest = -1;
191
+ for (let b1 = m - 4; b1 >= 0; b1--) {
192
+ const b2Candidate = b1 + 3;
193
+ if (suffixBest < 0 || s2[b2Candidate] > s2[suffixBest])
194
+ suffixBest = b2Candidate;
195
+ const saving = s1[b1] + s2[suffixBest];
196
+ if (saving > 0)
197
+ shortlist.push([saving, b1, suffixBest]);
198
+ for (let b2 = b1 + 1; b2 < Math.min(m, b1 + 3); b2++) {
199
+ if (s1[b1] + s2[b2] > 0)
200
+ shortlist.push([s1[b1] + s2[b2], b1, b2]);
201
+ }
202
+ }
203
+ shortlist.sort((a, b) => b[0] - a[0] || a[1] - b[1] || a[2] - b[2]);
204
+ const seen = new Set();
205
+ for (const [, b1, b2] of shortlist) {
206
+ if (seen.size >= 4)
207
+ break;
208
+ const key = b1 * 256 + b2;
209
+ if (seen.has(key))
210
+ continue;
211
+ seen.add(key);
212
+ consider(build(b1, b2));
213
+ }
214
+ }
215
+ return best;
216
+ }