@cubingcenter/scrambler 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +15 -4
  2. package/dist/core/api.d.ts.map +1 -1
  3. package/dist/core/api.js +10 -2
  4. package/dist/puzzles/cube2/solver/browser-loader.d.ts +4 -7
  5. package/dist/puzzles/cube2/solver/browser-loader.d.ts.map +1 -1
  6. package/dist/puzzles/cube2/solver/browser-loader.js +42 -14
  7. package/dist/puzzles/cube2/solver/generated-assets.d.ts.map +1 -1
  8. package/dist/puzzles/cube2/solver/generated-assets.js +8 -3
  9. package/dist/puzzles/cube3/solver/browser-loader.d.ts.map +1 -1
  10. package/dist/puzzles/cube3/solver/browser-loader.js +42 -20
  11. package/dist/puzzles/cube3/solver/generated-assets.d.ts.map +1 -1
  12. package/dist/puzzles/cube3/solver/generated-assets.js +9 -3
  13. package/dist/puzzles/cube3/solver/phase1.d.ts +19 -0
  14. package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
  15. package/dist/puzzles/cube3/solver/phase1.js +29 -6
  16. package/dist/puzzles/cube3/solver/phase2.d.ts +28 -0
  17. package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
  18. package/dist/puzzles/cube3/solver/phase2.js +56 -15
  19. package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
  20. package/dist/puzzles/cube3/solver/tables.js +14 -12
  21. package/dist/puzzles/cube4/browser-worker-pool.d.ts +82 -0
  22. package/dist/puzzles/cube4/browser-worker-pool.d.ts.map +1 -0
  23. package/dist/puzzles/cube4/browser-worker-pool.js +205 -0
  24. package/dist/puzzles/cube4/browser-worker.d.ts +2 -0
  25. package/dist/puzzles/cube4/browser-worker.d.ts.map +1 -0
  26. package/dist/puzzles/cube4/browser-worker.js +31 -0
  27. package/dist/puzzles/cube4/index.d.ts +7 -0
  28. package/dist/puzzles/cube4/index.d.ts.map +1 -1
  29. package/dist/puzzles/cube4/index.js +4 -0
  30. package/dist/puzzles/cube4/moves.d.ts.map +1 -1
  31. package/dist/puzzles/cube4/moves.js +122 -30
  32. package/dist/puzzles/cube4/reduction/browser-loader.d.ts +26 -1
  33. package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -1
  34. package/dist/puzzles/cube4/reduction/browser-loader.js +212 -48
  35. package/dist/puzzles/cube4/reduction/center2.d.ts +24 -0
  36. package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -1
  37. package/dist/puzzles/cube4/reduction/center2.js +99 -1
  38. package/dist/puzzles/cube4/reduction/finish3.d.ts +53 -0
  39. package/dist/puzzles/cube4/reduction/finish3.d.ts.map +1 -0
  40. package/dist/puzzles/cube4/reduction/finish3.js +393 -0
  41. package/dist/puzzles/cube4/reduction/generated/hc-manifest.json +6 -0
  42. package/dist/puzzles/cube4/reduction/hc-pdb.d.ts +5 -3
  43. package/dist/puzzles/cube4/reduction/hc-pdb.d.ts.map +1 -1
  44. package/dist/puzzles/cube4/reduction/hc-pdb.js +30 -12
  45. package/dist/puzzles/cube4/reduction/node-loader.d.ts +10 -1
  46. package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -1
  47. package/dist/puzzles/cube4/reduction/node-loader.js +170 -36
  48. package/dist/puzzles/cube4/reduction/pair-transversal.d.ts +28 -0
  49. package/dist/puzzles/cube4/reduction/pair-transversal.d.ts.map +1 -0
  50. package/dist/puzzles/cube4/reduction/pair-transversal.js +107 -0
  51. package/dist/puzzles/cube4/reduction/phase3-search.d.ts +33 -30
  52. package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -1
  53. package/dist/puzzles/cube4/reduction/phase3-search.js +211 -104
  54. package/dist/puzzles/cube4/reduction/rotation-fix.d.ts +28 -0
  55. package/dist/puzzles/cube4/reduction/rotation-fix.d.ts.map +1 -0
  56. package/dist/puzzles/cube4/reduction/rotation-fix.js +212 -0
  57. package/dist/puzzles/cube4/reduction/solve.d.ts +72 -13
  58. package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -1
  59. package/dist/puzzles/cube4/reduction/solve.js +374 -92
  60. package/dist/puzzles/cube4/scramble-cache.d.ts +93 -0
  61. package/dist/puzzles/cube4/scramble-cache.d.ts.map +1 -0
  62. package/dist/puzzles/cube4/scramble-cache.js +150 -0
  63. package/dist/puzzles/cube4/scrambler.d.ts +112 -18
  64. package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
  65. package/dist/puzzles/cube4/scrambler.js +271 -83
  66. package/dist/puzzles/cube4/worker-pool.d.ts +139 -0
  67. package/dist/puzzles/cube4/worker-pool.d.ts.map +1 -0
  68. package/dist/puzzles/cube4/worker-pool.js +405 -0
  69. package/dist/puzzles/cube4/worker-protocol.d.ts +58 -0
  70. package/dist/puzzles/cube4/worker-protocol.d.ts.map +1 -0
  71. package/dist/puzzles/cube4/worker-protocol.js +58 -0
  72. package/dist/puzzles/cube4/worker.d.ts +26 -0
  73. package/dist/puzzles/cube4/worker.d.ts.map +1 -0
  74. package/dist/puzzles/cube4/worker.js +49 -0
  75. package/dist/puzzles/pyraminx/solver/browser-loader.d.ts +4 -8
  76. package/dist/puzzles/pyraminx/solver/browser-loader.d.ts.map +1 -1
  77. package/dist/puzzles/pyraminx/solver/browser-loader.js +44 -15
  78. package/dist/puzzles/pyraminx/solver/generated-assets.d.ts.map +1 -1
  79. package/dist/puzzles/pyraminx/solver/generated-assets.js +8 -3
  80. package/dist/puzzles/shared/compressed-fetch.d.ts +37 -1
  81. package/dist/puzzles/shared/compressed-fetch.d.ts.map +1 -1
  82. package/dist/puzzles/shared/compressed-fetch.js +226 -31
  83. package/dist/puzzles/shared/load-once.d.ts +16 -0
  84. package/dist/puzzles/shared/load-once.d.ts.map +1 -0
  85. package/dist/puzzles/shared/load-once.js +11 -0
  86. package/dist/puzzles/skewb/solver/browser-loader.d.ts +4 -11
  87. package/dist/puzzles/skewb/solver/browser-loader.d.ts.map +1 -1
  88. package/dist/puzzles/skewb/solver/browser-loader.js +44 -16
  89. package/dist/puzzles/skewb/solver/generated-assets.d.ts.map +1 -1
  90. package/dist/puzzles/skewb/solver/generated-assets.js +8 -3
  91. package/dist/puzzles/sq1/solver/browser-loader.d.ts.map +1 -1
  92. package/dist/puzzles/sq1/solver/browser-loader.js +58 -26
  93. package/dist-cjs/core/api.js +10 -2
  94. package/dist-cjs/puzzles/cube2/solver/browser-loader.js +42 -14
  95. package/dist-cjs/puzzles/cube2/solver/generated-assets.js +8 -3
  96. package/dist-cjs/puzzles/cube3/solver/browser-loader.js +42 -20
  97. package/dist-cjs/puzzles/cube3/solver/generated-assets.js +9 -3
  98. package/dist-cjs/puzzles/cube3/solver/phase1.js +29 -6
  99. package/dist-cjs/puzzles/cube3/solver/phase2.js +56 -15
  100. package/dist-cjs/puzzles/cube3/solver/tables.js +14 -12
  101. package/dist-cjs/puzzles/cube4/browser-worker-pool.js +211 -0
  102. package/dist-cjs/puzzles/cube4/index.js +21 -1
  103. package/dist-cjs/puzzles/cube4/moves.js +122 -30
  104. package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +212 -48
  105. package/dist-cjs/puzzles/cube4/reduction/center2.js +102 -2
  106. package/dist-cjs/puzzles/cube4/reduction/finish3.js +398 -0
  107. package/dist-cjs/puzzles/cube4/reduction/generated/hc-manifest.json +6 -0
  108. package/dist-cjs/puzzles/cube4/reduction/hc-pdb.js +31 -12
  109. package/dist-cjs/puzzles/cube4/reduction/node-loader.js +171 -36
  110. package/dist-cjs/puzzles/cube4/reduction/pair-transversal.js +112 -0
  111. package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +211 -104
  112. package/dist-cjs/puzzles/cube4/reduction/rotation-fix.js +216 -0
  113. package/dist-cjs/puzzles/cube4/reduction/solve.js +374 -88
  114. package/dist-cjs/puzzles/cube4/scramble-cache.js +156 -0
  115. package/dist-cjs/puzzles/cube4/scrambler.js +271 -80
  116. package/dist-cjs/puzzles/cube4/worker-pool.js +447 -0
  117. package/dist-cjs/puzzles/cube4/worker-protocol.js +62 -0
  118. package/dist-cjs/puzzles/pyraminx/solver/browser-loader.js +44 -15
  119. package/dist-cjs/puzzles/pyraminx/solver/generated-assets.js +8 -3
  120. package/dist-cjs/puzzles/shared/compressed-fetch.js +229 -31
  121. package/dist-cjs/puzzles/shared/load-once.js +14 -0
  122. package/dist-cjs/puzzles/skewb/solver/browser-loader.js +44 -16
  123. package/dist-cjs/puzzles/skewb/solver/generated-assets.js +8 -3
  124. package/dist-cjs/puzzles/sq1/solver/browser-loader.js +58 -26
  125. package/docs/development.md +19 -7
  126. package/package.json +4 -3
@@ -46,7 +46,7 @@ export async function solveCube3Phase2(state, options = {}) {
46
46
  const maxDepth = options.maxDepth ?? 18;
47
47
  const start = encodeCube3Phase2Coordinates(state);
48
48
  const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
49
- const context = createPhase2SearchContext(tables, pairTables, maxDepth, deadline);
49
+ const context = createPhase2SearchContext(tables, pairTables, maxDepth, deadline, options.maxNodes);
50
50
  const spRange = tables.moveTables.slicePermutation.range;
51
51
  const cpspTable = tables.pruningTables.cornerPermutationSlicePermutation.values;
52
52
  const epspTable = tables.pruningTables.udEdgePermutationSlicePermutation.values;
@@ -77,6 +77,9 @@ export async function solveCube3Phase2(state, options = {}) {
77
77
  visitedNodes: context.visitedNodes
78
78
  };
79
79
  }
80
+ if (context.nodesExceeded) {
81
+ throw new Cube3Phase2SearchError(`3x3 Phase 2 node budget exceeded (${options.maxNodes} nodes).`, context.visitedNodes);
82
+ }
80
83
  if (context.timedOut) {
81
84
  throw new Cube3Phase2SearchError(`3x3 Phase 2 search timed out after ${options.timeoutMs}ms.`, context.visitedNodes);
82
85
  }
@@ -117,7 +120,19 @@ export async function solveCube3State(state, options = {}) {
117
120
  const p2Tables = options.phase2?.tables ?? await loadCube3Phase2Tables();
118
121
  const scoredPairTables = options.phase2?.pairTables ??
119
122
  (options.phase2?.skipPairTables ? undefined : await loadCube3PairTables());
120
- const deadline = performance.now() + (options.timeoutMs ?? 800);
123
+ // Work budget for the improvement loop. Preferred: `improveMaxNodes`
124
+ // (deterministic node counter — the wall deadline stays unarmed so the
125
+ // outcome cannot depend on CPU load/clock). Legacy: `timeoutMs ?? 800`
126
+ // wall deadline, kept for existing callers.
127
+ const nodeMode = options.improveMaxNodes !== undefined;
128
+ let remainingNodes = options.improveMaxNodes ?? 0;
129
+ const deadline = nodeMode ? undefined : performance.now() + (options.timeoutMs ?? 800);
130
+ const budgetExhausted = () => nodeMode ? remainingNodes <= 0 : performance.now() > deadline;
131
+ const spendNodes = (used) => {
132
+ if (nodeMode) {
133
+ remainingNodes = Math.max(0, remainingNodes - used);
134
+ }
135
+ };
121
136
  const p2SpRange = p2Tables.moveTables.slicePermutation.range;
122
137
  const p2cpsp = p2Tables.pruningTables.cornerPermutationSlicePermutation.values;
123
138
  const p2uepsp = p2Tables.pruningTables.udEdgePermutationSlicePermutation.values;
@@ -129,18 +144,21 @@ export async function solveCube3State(state, options = {}) {
129
144
  const minDepth = phase1.depth;
130
145
  const scored = [];
131
146
  for (let d = minDepth; d <= minDepth + improveDepths && d < bestMoves.length; d++) {
132
- if (performance.now() > deadline)
147
+ if (budgetExhausted())
133
148
  break;
134
149
  const { solutions: p1Solutions, visitedNodes } = await collectCube3Phase1Solutions(state, d, {
135
150
  tables: p1Tables,
136
151
  limit: solutionLimit,
137
- timeoutMs: Math.max(1, deadline - performance.now())
152
+ ...(nodeMode
153
+ ? { maxNodes: Math.max(1, remainingNodes) }
154
+ : { timeoutMs: Math.max(1, deadline - performance.now()) })
138
155
  });
139
156
  improvementNodes += visitedNodes;
140
- if (performance.now() > deadline)
157
+ spendNodes(visitedNodes);
158
+ if (budgetExhausted())
141
159
  break;
142
160
  for (const p1Moves of p1Solutions) {
143
- if (performance.now() > deadline)
161
+ if (budgetExhausted())
144
162
  break;
145
163
  if (p1Moves.length >= bestMoves.length)
146
164
  continue;
@@ -177,7 +195,7 @@ export async function solveCube3State(state, options = {}) {
177
195
  // Sort ALL candidates globally across depths by admissible total lower bound
178
196
  scored.sort((left, right) => left.est - right.est);
179
197
  for (const cand of scored) {
180
- if (performance.now() > deadline)
198
+ if (budgetExhausted())
181
199
  break;
182
200
  // Prune: since candidates are sorted by est, no subsequent candidate can beat bestMoves.length
183
201
  if (cand.est >= bestMoves.length)
@@ -186,19 +204,31 @@ export async function solveCube3State(state, options = {}) {
186
204
  if (maxP2 <= 0)
187
205
  continue;
188
206
  try {
189
- const remainingBudget = Math.max(1, deadline - performance.now());
190
- const attemptTimeout = options.improvePhase2TimeoutMs ??
191
- options.phase2?.timeoutMs ?? 25;
207
+ // Strip any legacy wall `timeoutMs` from the spread: in node mode
208
+ // the wall deadline must not be armed at all (pure function of
209
+ // budgets); in wall mode the per-candidate cap below re-adds it.
210
+ const phase2Source = options.phase2 ?? {};
211
+ const { timeoutMs: legacyPhase2Wall, ...phase2CandidateBase } = phase2Source;
212
+ // Per-candidate budget: node cap (deterministic) when the loop runs
213
+ // on `improveMaxNodes`, else the legacy wall cap.
214
+ const candidateCap = nodeMode
215
+ ? {
216
+ maxNodes: Math.max(1, Math.min(options.improvePhase2MaxNodes ?? phase2CandidateBase.maxNodes ?? remainingNodes, remainingNodes))
217
+ }
218
+ : {
219
+ timeoutMs: Math.min(options.improvePhase2TimeoutMs ?? legacyPhase2Wall ?? 25, Math.max(1, deadline - performance.now()))
220
+ };
192
221
  const candP2 = await solveCube3Phase2(cand.state, {
193
- ...options.phase2,
222
+ ...phase2CandidateBase,
194
223
  pairTables: scoredPairTables,
195
224
  tables: p2Tables,
196
225
  maxDepth: maxP2,
197
226
  previousFace: cand.pFace,
198
227
  previousPreviousFace: cand.ppFace,
199
- timeoutMs: Math.min(attemptTimeout, remainingBudget)
228
+ ...candidateCap
200
229
  });
201
230
  improvementNodes += candP2.visitedNodes;
231
+ spendNodes(candP2.visitedNodes);
202
232
  const candTotal = cand.moves.length + candP2.moves.length;
203
233
  if (candTotal < bestMoves.length) {
204
234
  bestMoves = [...cand.moves, ...candP2.moves];
@@ -292,7 +322,7 @@ const PHASE2_MOVE_FACES = CUBE3_PHASE2_MOVES.map((move) => {
292
322
  const char = move[0];
293
323
  return char === "U" ? 0 : char === "D" ? 1 : char === "R" ? 2 : char === "L" ? 3 : char === "F" ? 4 : 5;
294
324
  });
295
- function createPhase2SearchContext(tables, pairTables, maxDepth, deadline) {
325
+ function createPhase2SearchContext(tables, pairTables, maxDepth, deadline, nodeCap = undefined) {
296
326
  const scratchLevels = maxDepth + 1;
297
327
  const scratchSize = scratchLevels * CUBE3_PHASE2_MOVES.length;
298
328
  return {
@@ -300,8 +330,10 @@ function createPhase2SearchContext(tables, pairTables, maxDepth, deadline) {
300
330
  pairTables,
301
331
  maxDepth,
302
332
  deadline,
333
+ nodeCap,
303
334
  visitedNodes: 0,
304
335
  timedOut: false,
336
+ nodesExceeded: false,
305
337
  scratchMove: new Int32Array(scratchSize),
306
338
  scratchBound: new Int32Array(scratchSize),
307
339
  scratchCp: new Int32Array(scratchSize),
@@ -311,8 +343,17 @@ function createPhase2SearchContext(tables, pairTables, maxDepth, deadline) {
311
343
  }
312
344
  function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, previousFace, previousPreviousFace, remainingDepth, path, context) {
313
345
  context.visitedNodes += 1;
314
- if (context.deadline !== undefined && (context.visitedNodes & 1023) === 0 && performance.now() > context.deadline) {
346
+ if (context.nodeCap !== undefined &&
347
+ context.visitedNodes > context.nodeCap) {
348
+ context.nodesExceeded = true;
349
+ }
350
+ if (!context.nodesExceeded &&
351
+ context.deadline !== undefined &&
352
+ (context.visitedNodes & 1023) === 0 &&
353
+ performance.now() > context.deadline) {
315
354
  context.timedOut = true;
355
+ }
356
+ if (context.timedOut || context.nodesExceeded) {
316
357
  return false;
317
358
  }
318
359
  if (remainingDepth === 0) {
@@ -403,7 +444,7 @@ function searchPhase2(cornerPermutation, udEdgePermutation, slicePermutation, pr
403
444
  return true;
404
445
  }
405
446
  path.pop();
406
- if (context.timedOut) {
447
+ if (context.timedOut || context.nodesExceeded) {
407
448
  return false;
408
449
  }
409
450
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/tables.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/tables.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ import { CUBE3_MOVES } from "../types.js";
2
2
  import { CUBE3_COORDINATE_RANGES, encodeCube3Coordinates, moveCoordinate } from "./coordinates.js";
3
3
  import { createSolvedCube3State } from "../state.js";
4
4
  import { loadGeneratedCube3Phase1Tables, loadGeneratedCube3Phase2Tables, loadGeneratedCube3SolverTables } from "./generated-assets.js";
5
+ import { loadOnce } from "../../shared/load-once.js";
5
6
  export const CUBE3_PHASE1_MOVES = CUBE3_MOVES;
6
7
  export const CUBE3_PHASE2_MOVES = [
7
8
  "U",
@@ -15,20 +16,21 @@ export const CUBE3_PHASE2_MOVES = [
15
16
  "F2",
16
17
  "B2"
17
18
  ];
18
- let cachedTables;
19
- let cachedPhase1Tables;
20
- let cachedPhase2Tables;
21
- export async function loadCube3SolverTables() {
22
- cachedTables ??= await loadGeneratedCube3SolverTables();
23
- return cachedTables;
19
+ // Promise slots (shared helper, see shared/load-once.ts): concurrent cold
20
+ // calls share ONE in-flight load instead of racing several loads and
21
+ // clobbering each other's results; a rejection purges the slot so the next
22
+ // call can retry.
23
+ const solverTablesSlot = { promise: undefined };
24
+ const phase1TablesSlot = { promise: undefined };
25
+ const phase2TablesSlot = { promise: undefined };
26
+ export function loadCube3SolverTables() {
27
+ return loadOnce(solverTablesSlot, loadGeneratedCube3SolverTables);
24
28
  }
25
- export async function loadCube3Phase1Tables() {
26
- cachedPhase1Tables ??= await loadGeneratedCube3Phase1Tables();
27
- return cachedPhase1Tables;
29
+ export function loadCube3Phase1Tables() {
30
+ return loadOnce(phase1TablesSlot, loadGeneratedCube3Phase1Tables);
28
31
  }
29
- export async function loadCube3Phase2Tables() {
30
- cachedPhase2Tables ??= await loadGeneratedCube3Phase2Tables();
31
- return cachedPhase2Tables;
32
+ export function loadCube3Phase2Tables() {
33
+ return loadOnce(phase2TablesSlot, loadGeneratedCube3Phase2Tables);
32
34
  }
33
35
  export function generateCube3Phase1Tables() {
34
36
  const cornerOrientation = generateCube3MoveTable("cornerOrientation", CUBE3_COORDINATE_RANGES.cornerOrientation, CUBE3_PHASE1_MOVES);
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Web Worker pool for 4x4 random-state scrambles — the browser counterpart
3
+ * of `worker-pool.ts` with the same protocol, lazy-spawn lifecycle, FIFO
4
+ * queue, typed error propagation and per-thread table reuse.
5
+ *
6
+ * Unlike the Node pool (which locates its own worker file), the browser
7
+ * pool REQUIRES the caller to pass `workerUrl`, because only the caller's
8
+ * file can host the static `new URL("./browser-worker.js", import.meta.url)`
9
+ * expression that bundlers (Vite, Webpack 5, Parcel) statically analyse to
10
+ * emit a separate worker chunk:
11
+ *
12
+ * ```ts
13
+ * const pool = new Cube4BrowserWorkerPool({
14
+ * workerUrl: new URL("./browser-worker.js", import.meta.url)
15
+ * });
16
+ * const batch = await pool.generateBatch(8, { seed: "demo" });
17
+ * await pool.terminate();
18
+ * ```
19
+ *
20
+ * This file contains no `import.meta` and no static Node imports, so it
21
+ * also compiles in the CommonJS build (unlike a hard-coded
22
+ * `new URL(..., import.meta.url)` default).
23
+ *
24
+ * Errors crossing the worker boundary arrive as `Error` instances with the
25
+ * original `name`, `message` and `code` preserved (`worker-protocol.ts`).
26
+ */
27
+ import type { ScrambleResult } from "../../core/types.js";
28
+ import { type Cube4WorkerGenerationOptions } from "./worker-protocol.js";
29
+ export interface Cube4BrowserWorkerPoolOptions {
30
+ /**
31
+ * URL of the worker entry, normally
32
+ * `new URL("./browser-worker.js", import.meta.url)` written in the
33
+ * consumer's module (statically analysable by bundlers).
34
+ */
35
+ workerUrl: string | URL;
36
+ /**
37
+ * Maximum number of Web Workers. Defaults to
38
+ * `navigator.hardwareConcurrency - 1` (min 1).
39
+ */
40
+ size?: number;
41
+ }
42
+ /** Typed rejection used for post-terminate operations and shutdown itself. */
43
+ export declare class Cube4BrowserWorkerPoolClosedError extends Error {
44
+ readonly code: "cube4-browser-pool-closed";
45
+ constructor(action?: string);
46
+ }
47
+ /**
48
+ * Batch seed derivation — identical to core `generateScrambles` and to
49
+ * `deriveBatchCube4Options` in the Node pool.
50
+ */
51
+ export declare function deriveBrowserBatchCube4Options(options: Cube4WorkerGenerationOptions, index: number, count: number): Cube4WorkerGenerationOptions;
52
+ export declare class Cube4BrowserWorkerPool {
53
+ private readonly workers;
54
+ private readonly queue;
55
+ private readonly workerUrl;
56
+ private readonly maxSize;
57
+ private pumping;
58
+ private terminatePromise;
59
+ private nextId;
60
+ private terminated;
61
+ constructor(options: Cube4BrowserWorkerPoolOptions);
62
+ /** Threads spawned so far (lazily grown; 0 before the first job). */
63
+ get workerCount(): number;
64
+ get queuedCount(): number;
65
+ get busyCount(): number;
66
+ get closed(): boolean;
67
+ generate(options?: Cube4WorkerGenerationOptions): Promise<ScrambleResult>;
68
+ generateBatch(count: number, options?: Cube4WorkerGenerationOptions): Promise<ScrambleResult[]>;
69
+ /**
70
+ * Prompt shutdown: rejects queued/in-flight tasks and terminates every
71
+ * Web Worker synchronously (no threads to join in the browser).
72
+ * Idempotent.
73
+ */
74
+ terminate(): void;
75
+ private pump;
76
+ private runPump;
77
+ private spawnWorker;
78
+ private dispatch;
79
+ private handleResponse;
80
+ private handleWorkerGone;
81
+ }
82
+ //# sourceMappingURL=browser-worker-pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-worker-pool.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/browser-worker-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAEL,KAAK,4BAA4B,EAGlC,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,SAAS,EAAE,MAAM,GAAG,GAAG,CAAC;IACxB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,8EAA8E;AAC9E,qBAAa,iCAAkC,SAAQ,KAAK;IAC1D,QAAQ,CAAC,IAAI,EAAG,2BAA2B,CAAU;gBAEzC,MAAM,SAAc;CAIjC;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,4BAA4B,EACrC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,4BAA4B,CAK9B;AAqBD,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,UAAU,CAAS;gBAEf,OAAO,EAAE,6BAA6B;IAiBlD,qEAAqE;IACrE,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,QAAQ,CAAC,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAAC,cAAc,CAAC;IAW7E,aAAa,CACX,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,cAAc,EAAE,CAAC;IAW5B;;;;OAIG;IACH,SAAS,IAAI,IAAI;IAoBjB,OAAO,CAAC,IAAI;YAUE,OAAO;IAerB,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,gBAAgB;CAUzB"}
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Web Worker pool for 4x4 random-state scrambles — the browser counterpart
3
+ * of `worker-pool.ts` with the same protocol, lazy-spawn lifecycle, FIFO
4
+ * queue, typed error propagation and per-thread table reuse.
5
+ *
6
+ * Unlike the Node pool (which locates its own worker file), the browser
7
+ * pool REQUIRES the caller to pass `workerUrl`, because only the caller's
8
+ * file can host the static `new URL("./browser-worker.js", import.meta.url)`
9
+ * expression that bundlers (Vite, Webpack 5, Parcel) statically analyse to
10
+ * emit a separate worker chunk:
11
+ *
12
+ * ```ts
13
+ * const pool = new Cube4BrowserWorkerPool({
14
+ * workerUrl: new URL("./browser-worker.js", import.meta.url)
15
+ * });
16
+ * const batch = await pool.generateBatch(8, { seed: "demo" });
17
+ * await pool.terminate();
18
+ * ```
19
+ *
20
+ * This file contains no `import.meta` and no static Node imports, so it
21
+ * also compiles in the CommonJS build (unlike a hard-coded
22
+ * `new URL(..., import.meta.url)` default).
23
+ *
24
+ * Errors crossing the worker boundary arrive as `Error` instances with the
25
+ * original `name`, `message` and `code` preserved (`worker-protocol.ts`).
26
+ */
27
+ import { deserializeCube4Error } from "./worker-protocol.js";
28
+ /** Typed rejection used for post-terminate operations and shutdown itself. */
29
+ export class Cube4BrowserWorkerPoolClosedError extends Error {
30
+ code = "cube4-browser-pool-closed";
31
+ constructor(action = "operation") {
32
+ super(`[cube4] browser worker pool is closed (${action} rejected).`);
33
+ this.name = "Cube4BrowserWorkerPoolClosedError";
34
+ }
35
+ }
36
+ /**
37
+ * Batch seed derivation — identical to core `generateScrambles` and to
38
+ * `deriveBatchCube4Options` in the Node pool.
39
+ */
40
+ export function deriveBrowserBatchCube4Options(options, index, count) {
41
+ if (count === 1 || options.seed === undefined) {
42
+ return options;
43
+ }
44
+ return { ...options, seed: `${options.seed}:${index}` };
45
+ }
46
+ function defaultBrowserSize() {
47
+ if (typeof navigator !== "undefined" && navigator.hardwareConcurrency) {
48
+ return Math.max(1, navigator.hardwareConcurrency - 1);
49
+ }
50
+ return 4;
51
+ }
52
+ export class Cube4BrowserWorkerPool {
53
+ workers = [];
54
+ queue = [];
55
+ workerUrl;
56
+ maxSize;
57
+ pumping = false;
58
+ terminatePromise;
59
+ nextId = 0;
60
+ terminated = false;
61
+ constructor(options) {
62
+ if (typeof Worker === "undefined") {
63
+ throw new Error("[cube4] Web Workers are not available in this environment; " +
64
+ "use generateCube4Scramble() directly for single-threaded generation.");
65
+ }
66
+ if (options.size !== undefined) {
67
+ if (!Number.isInteger(options.size) || options.size < 1) {
68
+ throw new RangeError(`[cube4] pool size must be an integer >= 1 (got ${options.size}).`);
69
+ }
70
+ }
71
+ this.workerUrl =
72
+ typeof options.workerUrl === "string" ? options.workerUrl : options.workerUrl.href;
73
+ this.maxSize = options.size ?? defaultBrowserSize();
74
+ }
75
+ /** Threads spawned so far (lazily grown; 0 before the first job). */
76
+ get workerCount() {
77
+ return this.workers.length;
78
+ }
79
+ get queuedCount() {
80
+ return this.queue.length;
81
+ }
82
+ get busyCount() {
83
+ return this.workers.reduce((n, rec) => n + (rec.task !== null ? 1 : 0), 0);
84
+ }
85
+ get closed() {
86
+ return this.terminated;
87
+ }
88
+ generate(options = {}) {
89
+ if (this.terminated) {
90
+ return Promise.reject(new Cube4BrowserWorkerPoolClosedError("generate"));
91
+ }
92
+ const id = this.nextId++;
93
+ return new Promise((resolve, reject) => {
94
+ this.queue.push({ id, options, resolve, reject });
95
+ this.pump();
96
+ });
97
+ }
98
+ generateBatch(count, options = {}) {
99
+ if (!Number.isInteger(count) || count < 1) {
100
+ return Promise.reject(new RangeError(`count must be a positive integer (got ${count}).`));
101
+ }
102
+ return Promise.all(Array.from({ length: count }, (_, index) => this.generate(deriveBrowserBatchCube4Options(options, index, count))));
103
+ }
104
+ /**
105
+ * Prompt shutdown: rejects queued/in-flight tasks and terminates every
106
+ * Web Worker synchronously (no threads to join in the browser).
107
+ * Idempotent.
108
+ */
109
+ terminate() {
110
+ if (this.terminatePromise !== undefined)
111
+ return;
112
+ this.terminated = true;
113
+ const closed = () => new Cube4BrowserWorkerPoolClosedError();
114
+ for (const task of this.queue.splice(0))
115
+ task.reject(closed());
116
+ const records = this.workers.splice(0);
117
+ for (const rec of records) {
118
+ if (rec.task !== null) {
119
+ rec.task.reject(closed());
120
+ rec.task = null;
121
+ }
122
+ rec.worker.terminate();
123
+ }
124
+ this.terminatePromise = Promise.resolve();
125
+ }
126
+ // ── internals (same shape as the Node pool) ──────────────────────────
127
+ pump() {
128
+ if (this.pumping || this.terminated)
129
+ return;
130
+ this.pumping = true;
131
+ void this.runPump()
132
+ .catch(() => undefined)
133
+ .finally(() => {
134
+ this.pumping = false;
135
+ });
136
+ }
137
+ async runPump() {
138
+ while (!this.terminated && this.queue.length > 0) {
139
+ const idle = this.workers.find((rec) => rec.task === null);
140
+ if (idle !== undefined) {
141
+ this.dispatch(idle, this.queue.shift());
142
+ continue;
143
+ }
144
+ if (this.workers.length < this.maxSize) {
145
+ this.spawnWorker();
146
+ continue;
147
+ }
148
+ break;
149
+ }
150
+ }
151
+ spawnWorker() {
152
+ const worker = new Worker(this.workerUrl, { type: "module", name: "cubingcenter-444" });
153
+ const rec = { worker, task: null };
154
+ worker.onmessage = (event) => {
155
+ this.handleResponse(rec, event.data);
156
+ };
157
+ worker.onerror = (event) => {
158
+ this.handleWorkerGone(rec, event.error instanceof Error
159
+ ? event.error
160
+ : new Error(event.message || "[cube4] browser worker failed."));
161
+ };
162
+ this.workers.push(rec);
163
+ return rec;
164
+ }
165
+ dispatch(rec, task) {
166
+ rec.task = task;
167
+ const message = { id: task.id, options: task.options };
168
+ try {
169
+ rec.worker.postMessage(message);
170
+ }
171
+ catch (error) {
172
+ rec.task = null;
173
+ task.reject(error);
174
+ this.pump();
175
+ }
176
+ }
177
+ handleResponse(rec, response) {
178
+ const task = rec.task;
179
+ if (task === null || task.id !== response.id)
180
+ return;
181
+ rec.task = null;
182
+ if (this.terminated)
183
+ return;
184
+ if (response.ok)
185
+ task.resolve(response.result);
186
+ else
187
+ task.reject(deserializeCube4Error(response.error));
188
+ const next = this.queue.shift();
189
+ if (next !== undefined)
190
+ this.dispatch(rec, next);
191
+ this.pump();
192
+ }
193
+ handleWorkerGone(rec, error) {
194
+ const index = this.workers.indexOf(rec);
195
+ if (index === -1)
196
+ return;
197
+ this.workers.splice(index, 1);
198
+ const task = rec.task;
199
+ rec.task = null;
200
+ if (task !== null && !this.terminated)
201
+ task.reject(error);
202
+ if (!this.terminated)
203
+ this.pump();
204
+ }
205
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=browser-worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-worker.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/browser-worker.ts"],"names":[],"mappings":""}
@@ -0,0 +1,31 @@
1
+ /// <reference lib="webworker" />
2
+ /**
3
+ * Web Worker entry for 4x4 scramble generation (browser build).
4
+ *
5
+ * Mirror of `worker.ts`: no work at boot, tables loaded on the FIRST task
6
+ * and reused afterwards (the browser loader caches per worker via
7
+ * `scrambler.ts`'s promise-deduplicated `loadCube4Tables`), full options
8
+ * passed through verbatim, errors flattened through `worker-protocol.ts`.
9
+ *
10
+ * Bundlers pick this file up through the static
11
+ * `new Worker(new URL(..., import.meta.url))` pattern used by the pool's
12
+ * consumer — see `Cube4BrowserWorkerPool`'s `workerUrl` option.
13
+ */
14
+ import { generateCube4Scramble } from "./scrambler.js";
15
+ import { serializeCube4Error } from "./worker-protocol.js";
16
+ const workerScope = self;
17
+ workerScope.onmessage = (event) => {
18
+ const task = event.data;
19
+ void (async () => {
20
+ const response = await (async () => {
21
+ try {
22
+ const result = await generateCube4Scramble(task.options);
23
+ return { id: task.id, ok: true, result };
24
+ }
25
+ catch (error) {
26
+ return { id: task.id, ok: false, error: serializeCube4Error(error) };
27
+ }
28
+ })();
29
+ workerScope.postMessage(response);
30
+ })();
31
+ };
@@ -1,8 +1,15 @@
1
1
  export { CORNER_COUNT, WING_COUNT, CENTER_COUNT, FACE_COUNT, CENTERS_PER_FACE, U, R, F, D, L, B, CUBE4_FACE_MOVES, CUBE4_ALL_MOVES, CUBE4_OUTER_MOVES } from "./types.js";
2
2
  export type { Cube4Face, Cube4FaceMove, Cube4Move, Cube4MoveSuffix, Cube4State, Cube4Sticker, Cube4Facelets, Cube4ValidationResult, Cube4RandomStateOptions } from "./types.js";
3
3
  export { createSolvedCube4State, cloneCube4State, cube4StatesEqual, isSolvedCube4State, areCentersReduced, areWingsPaired } from "./state.js";
4
+ export { Cube4FinishCapError, Cube4Phase2CapError, Cube4Phase3CapError } from "./reduction/solve.js";
4
5
  export { cube4ScrambleGenerator, generateCube4Scramble, generateCube4Attempt } from "./scrambler.js";
5
6
  export type { Cube4ScrambleOptions } from "./scrambler.js";
7
+ export { Cube4WorkerPool, Cube4WorkerPoolClosedError, deriveBatchCube4Options, generateCube4ScrambleBatch, getDefaultCube4WorkerPool, closeDefaultCube4WorkerPool } from "./worker-pool.js";
8
+ export type { Cube4WorkerPoolOptions } from "./worker-pool.js";
9
+ export { Cube4BrowserWorkerPool, Cube4BrowserWorkerPoolClosedError, deriveBrowserBatchCube4Options } from "./browser-worker-pool.js";
10
+ export type { Cube4BrowserWorkerPoolOptions } from "./browser-worker-pool.js";
11
+ export { Cube4ScrambleCache, createCube4ScrambleCache, deriveCube4StreamOptions } from "./scramble-cache.js";
12
+ export type { Cube4ScrambleCacheOptions, Cube4ScrambleGenerator } from "./scramble-cache.js";
6
13
  export { applyCube4Move, applyCube4Algorithm, invertCube4Move, invertCube4Algorithm, simplifyCube4Algorithm, formatCube4Algorithm, parseCube4Algorithm, parseCube4Move, parseMove, FACE_MOVE_EFFECTS } from "./moves.js";
7
14
  export { generateRandomCube4State } from "./random-state.js";
8
15
  export { validateCube4State, isOneMoveFromSolvedCube4State, isTrivialCube4State, isAllowedRandomCube4State } from "./validation.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/index.ts"],"names":[],"mappings":"AASA,OAAO,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAChB,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EAClB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,SAAS,EACT,aAAa,EACb,SAAS,EACT,eAAe,EACf,UAAU,EACV,YAAY,EACZ,aAAa,EACb,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3D,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,SAAS,EACT,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,wBAAwB,EACzB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,mBAAmB,EACnB,yBAAyB,EAC1B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/index.ts"],"names":[],"mappings":"AASA,OAAO,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAChB,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EAClB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,SAAS,EACT,aAAa,EACb,SAAS,EACT,eAAe,EACf,UAAU,EACV,YAAY,EACZ,aAAa,EACb,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3D,OAAO,EACL,eAAe,EACf,0BAA0B,EAC1B,uBAAuB,EACvB,0BAA0B,EAC1B,yBAAyB,EACzB,2BAA2B,EAC5B,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE/D,OAAO,EACL,sBAAsB,EACtB,iCAAiC,EACjC,8BAA8B,EAC/B,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,6BAA6B,EAAE,MAAM,0BAA0B,CAAC;AAE9E,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,yBAAyB,EACzB,sBAAsB,EACvB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,SAAS,EACT,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,wBAAwB,EACzB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,mBAAmB,EACnB,yBAAyB,EAC1B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC"}
@@ -6,7 +6,11 @@ import { cube4ScrambleGenerator } from "./scrambler.js";
6
6
  registerScrambleGenerator(cube4ScrambleGenerator);
7
7
  export { CORNER_COUNT, WING_COUNT, CENTER_COUNT, FACE_COUNT, CENTERS_PER_FACE, U, R, F, D, L, B, CUBE4_FACE_MOVES, CUBE4_ALL_MOVES, CUBE4_OUTER_MOVES } from "./types.js";
8
8
  export { createSolvedCube4State, cloneCube4State, cube4StatesEqual, isSolvedCube4State, areCentersReduced, areWingsPaired } from "./state.js";
9
+ export { Cube4FinishCapError, Cube4Phase2CapError, Cube4Phase3CapError } from "./reduction/solve.js";
9
10
  export { cube4ScrambleGenerator, generateCube4Scramble, generateCube4Attempt } from "./scrambler.js";
11
+ export { Cube4WorkerPool, Cube4WorkerPoolClosedError, deriveBatchCube4Options, generateCube4ScrambleBatch, getDefaultCube4WorkerPool, closeDefaultCube4WorkerPool } from "./worker-pool.js";
12
+ export { Cube4BrowserWorkerPool, Cube4BrowserWorkerPoolClosedError, deriveBrowserBatchCube4Options } from "./browser-worker-pool.js";
13
+ export { Cube4ScrambleCache, createCube4ScrambleCache, deriveCube4StreamOptions } from "./scramble-cache.js";
10
14
  export { applyCube4Move, applyCube4Algorithm, invertCube4Move, invertCube4Algorithm, simplifyCube4Algorithm, formatCube4Algorithm, parseCube4Algorithm, parseCube4Move, parseMove, FACE_MOVE_EFFECTS } from "./moves.js";
11
15
  export { generateRandomCube4State } from "./random-state.js";
12
16
  export { validateCube4State, isOneMoveFromSolvedCube4State, isTrivialCube4State, isAllowedRandomCube4State } from "./validation.js";
@@ -1 +1 @@
1
- {"version":3,"file":"moves.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/moves.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAOxF,UAAU,UAAU;IAClB,+DAA+D;IAC/D,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,yDAAyD;IACzD,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,kFAAkF;IAClF,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,oDAAoD;IACpD,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AA0KD,QAAA,MAAM,iBAAiB,EAAE,MAAM,CAAC,aAAa,EAAE,UAAU,CAGxD,CAAC;AAuDF,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,GAAG;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAc1G;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,GAAG,UAAU,CAK7E;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,UAAU,CA4C9F;AAKD,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAI1D;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,EAAE,CAE7E;AAMD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,CAExE;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,CAIlE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAMvD;AAkCD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,EAAE,CAsB/E;AAMD,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
1
+ {"version":3,"file":"moves.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/moves.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAOxF,UAAU,UAAU;IAClB,+DAA+D;IAC/D,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,yDAAyD;IACzD,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,kFAAkF;IAClF,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,oDAAoD;IACpD,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B;AA0KD,QAAA,MAAM,iBAAiB,EAAE,MAAM,CAAC,aAAa,EAAE,UAAU,CAGxD,CAAC;AAuDF,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,GAAG;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAc1G;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,GAAG,UAAU,CAK7E;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,UAAU,CA4C9F;AAKD,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAI1D;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,EAAE,CAE7E;AAMD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,CAExE;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,CAIlE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAMvD;AA+HD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,EAAE,CAsC/E;AAMD,OAAO,EAAE,iBAAiB,EAAE,CAAC"}