@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.
- package/README.md +15 -4
- package/dist/core/api.d.ts.map +1 -1
- package/dist/core/api.js +10 -2
- package/dist/puzzles/cube2/solver/browser-loader.d.ts +4 -7
- package/dist/puzzles/cube2/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/browser-loader.js +42 -14
- package/dist/puzzles/cube2/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/generated-assets.js +8 -3
- package/dist/puzzles/cube3/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/browser-loader.js +42 -20
- package/dist/puzzles/cube3/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/generated-assets.js +9 -3
- package/dist/puzzles/cube3/solver/phase1.d.ts +19 -0
- package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase1.js +29 -6
- package/dist/puzzles/cube3/solver/phase2.d.ts +28 -0
- package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase2.js +56 -15
- package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/tables.js +14 -12
- package/dist/puzzles/cube4/browser-worker-pool.d.ts +82 -0
- package/dist/puzzles/cube4/browser-worker-pool.d.ts.map +1 -0
- package/dist/puzzles/cube4/browser-worker-pool.js +205 -0
- package/dist/puzzles/cube4/browser-worker.d.ts +2 -0
- package/dist/puzzles/cube4/browser-worker.d.ts.map +1 -0
- package/dist/puzzles/cube4/browser-worker.js +31 -0
- package/dist/puzzles/cube4/index.d.ts +7 -0
- package/dist/puzzles/cube4/index.d.ts.map +1 -1
- package/dist/puzzles/cube4/index.js +4 -0
- package/dist/puzzles/cube4/moves.d.ts.map +1 -1
- package/dist/puzzles/cube4/moves.js +122 -30
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts +26 -1
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/browser-loader.js +212 -48
- package/dist/puzzles/cube4/reduction/center2.d.ts +24 -0
- package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/center2.js +99 -1
- package/dist/puzzles/cube4/reduction/finish3.d.ts +53 -0
- package/dist/puzzles/cube4/reduction/finish3.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/finish3.js +393 -0
- package/dist/puzzles/cube4/reduction/generated/hc-manifest.json +6 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts +5 -3
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/hc-pdb.js +30 -12
- package/dist/puzzles/cube4/reduction/node-loader.d.ts +10 -1
- package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/node-loader.js +170 -36
- package/dist/puzzles/cube4/reduction/pair-transversal.d.ts +28 -0
- package/dist/puzzles/cube4/reduction/pair-transversal.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/pair-transversal.js +107 -0
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts +33 -30
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/phase3-search.js +211 -104
- package/dist/puzzles/cube4/reduction/rotation-fix.d.ts +28 -0
- package/dist/puzzles/cube4/reduction/rotation-fix.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/rotation-fix.js +212 -0
- package/dist/puzzles/cube4/reduction/solve.d.ts +72 -13
- package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/solve.js +374 -92
- package/dist/puzzles/cube4/scramble-cache.d.ts +93 -0
- package/dist/puzzles/cube4/scramble-cache.d.ts.map +1 -0
- package/dist/puzzles/cube4/scramble-cache.js +150 -0
- package/dist/puzzles/cube4/scrambler.d.ts +112 -18
- package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube4/scrambler.js +271 -83
- package/dist/puzzles/cube4/worker-pool.d.ts +139 -0
- package/dist/puzzles/cube4/worker-pool.d.ts.map +1 -0
- package/dist/puzzles/cube4/worker-pool.js +405 -0
- package/dist/puzzles/cube4/worker-protocol.d.ts +58 -0
- package/dist/puzzles/cube4/worker-protocol.d.ts.map +1 -0
- package/dist/puzzles/cube4/worker-protocol.js +58 -0
- package/dist/puzzles/cube4/worker.d.ts +26 -0
- package/dist/puzzles/cube4/worker.d.ts.map +1 -0
- package/dist/puzzles/cube4/worker.js +49 -0
- package/dist/puzzles/pyraminx/solver/browser-loader.d.ts +4 -8
- package/dist/puzzles/pyraminx/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/browser-loader.js +44 -15
- package/dist/puzzles/pyraminx/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/generated-assets.js +8 -3
- package/dist/puzzles/shared/compressed-fetch.d.ts +37 -1
- package/dist/puzzles/shared/compressed-fetch.d.ts.map +1 -1
- package/dist/puzzles/shared/compressed-fetch.js +226 -31
- package/dist/puzzles/shared/load-once.d.ts +16 -0
- package/dist/puzzles/shared/load-once.d.ts.map +1 -0
- package/dist/puzzles/shared/load-once.js +11 -0
- package/dist/puzzles/skewb/solver/browser-loader.d.ts +4 -11
- package/dist/puzzles/skewb/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/browser-loader.js +44 -16
- package/dist/puzzles/skewb/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/generated-assets.js +8 -3
- package/dist/puzzles/sq1/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/sq1/solver/browser-loader.js +58 -26
- package/dist-cjs/core/api.js +10 -2
- package/dist-cjs/puzzles/cube2/solver/browser-loader.js +42 -14
- package/dist-cjs/puzzles/cube2/solver/generated-assets.js +8 -3
- package/dist-cjs/puzzles/cube3/solver/browser-loader.js +42 -20
- package/dist-cjs/puzzles/cube3/solver/generated-assets.js +9 -3
- package/dist-cjs/puzzles/cube3/solver/phase1.js +29 -6
- package/dist-cjs/puzzles/cube3/solver/phase2.js +56 -15
- package/dist-cjs/puzzles/cube3/solver/tables.js +14 -12
- package/dist-cjs/puzzles/cube4/browser-worker-pool.js +211 -0
- package/dist-cjs/puzzles/cube4/index.js +21 -1
- package/dist-cjs/puzzles/cube4/moves.js +122 -30
- package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +212 -48
- package/dist-cjs/puzzles/cube4/reduction/center2.js +102 -2
- package/dist-cjs/puzzles/cube4/reduction/finish3.js +398 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-manifest.json +6 -0
- package/dist-cjs/puzzles/cube4/reduction/hc-pdb.js +31 -12
- package/dist-cjs/puzzles/cube4/reduction/node-loader.js +171 -36
- package/dist-cjs/puzzles/cube4/reduction/pair-transversal.js +112 -0
- package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +211 -104
- package/dist-cjs/puzzles/cube4/reduction/rotation-fix.js +216 -0
- package/dist-cjs/puzzles/cube4/reduction/solve.js +374 -88
- package/dist-cjs/puzzles/cube4/scramble-cache.js +156 -0
- package/dist-cjs/puzzles/cube4/scrambler.js +271 -80
- package/dist-cjs/puzzles/cube4/worker-pool.js +447 -0
- package/dist-cjs/puzzles/cube4/worker-protocol.js +62 -0
- package/dist-cjs/puzzles/pyraminx/solver/browser-loader.js +44 -15
- package/dist-cjs/puzzles/pyraminx/solver/generated-assets.js +8 -3
- package/dist-cjs/puzzles/shared/compressed-fetch.js +229 -31
- package/dist-cjs/puzzles/shared/load-once.js +14 -0
- package/dist-cjs/puzzles/skewb/solver/browser-loader.js +44 -16
- package/dist-cjs/puzzles/skewb/solver/generated-assets.js +8 -3
- package/dist-cjs/puzzles/sq1/solver/browser-loader.js +58 -26
- package/docs/development.md +19 -7
- package/package.json +4 -3
|
@@ -1,25 +1,89 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The 4x4 scramble generator: sample a uniform random state, solve it with
|
|
3
|
-
* the reduction solver (phases 1-4), and invert the solution. The
|
|
4
|
-
*
|
|
3
|
+
* the reduction solver (phases 1-4), and invert the solution. The emitted
|
|
4
|
+
* scramble must reproduce the sampled target state EXACTLY — enforced by
|
|
5
|
+
* `assertCube4ScrambleReproducesTarget` on every generated result (unless
|
|
6
|
+
* `skipValidation` is set, which is only for the independent verification
|
|
7
|
+
* harness in `scripts/verify-scrambles.ts`).
|
|
5
8
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
+
* On a target mismatch the error is retryable, so the existing retry loop
|
|
10
|
+
* re-runs the solve over the SAME sampled target (deep attempt) and throws
|
|
11
|
+
* once attempts are exhausted. Re-sampling a different target to "make it
|
|
12
|
+
* fit" and degrading to random moves are forbidden: failing loudly (WCA 4g)
|
|
13
|
+
* is strictly preferable to emitting a scramble that lies about its seed.
|
|
14
|
+
*
|
|
15
|
+
* ── Determinism contract: what defines the output ──────────────────────
|
|
16
|
+
* `generateCube4Scramble` / `generateCube4Attempt` are pure functions of
|
|
17
|
+
* (seed, complete options): the emitted `moves` do not depend on wall-clock
|
|
18
|
+
* time, CPU load, warm-up, batch order, or process identity. This holds
|
|
19
|
+
* because every budget that can change the search outcome is a
|
|
20
|
+
* deterministic work counter (nodes visited), never a wall deadline:
|
|
21
|
+
* - reduction phases: node budgets inside `reduction/solve.ts`;
|
|
22
|
+
* - the 3x3 finish: node caps below (`finish.phase2MaxNodes`,
|
|
23
|
+
* `finish.improveMaxNodes`, per-candidate cap), which replace the old
|
|
24
|
+
* 150/50 ms wall budgets that made the same seed yield different move
|
|
25
|
+
* counts under CPU contention.
|
|
26
|
+
* Options are PART of the key: `restarts`/`bestOf`, `threeAxes`, `deep`,
|
|
27
|
+
* the beam knobs and `finish.*` legitimately change the result for the same
|
|
28
|
+
* seed — the output is reproducible within each configuration (documented
|
|
29
|
+
* behavior, not nondeterminism).
|
|
30
|
+
*
|
|
31
|
+
* Residual wall-clock backstop (hang guard only; never observed to bind —
|
|
32
|
+
* max measured solve 36.5 s): the 90 s
|
|
33
|
+
* `Promise.race` below. It cannot preempt the synchronous search — it
|
|
34
|
+
* can only fire at an event-loop tick — so if one ever fired, that attempt
|
|
35
|
+
* fails loudly (as a retryable error) instead of hanging. Making that bound
|
|
36
|
+
* exact deadline support would require cooperative search checkpoints.
|
|
9
37
|
*/
|
|
10
38
|
import { formatCube4Algorithm, applyCube4Algorithm, invertCube4Algorithm } from "./moves.js";
|
|
11
39
|
import { generateRandomCube4State } from "./random-state.js";
|
|
12
|
-
import { createSolvedCube4State,
|
|
40
|
+
import { createSolvedCube4State, cube4StatesEqual } from "./state.js";
|
|
13
41
|
import { formatCube4Facelets, toCube4Facelets } from "./facelets.js";
|
|
14
|
-
import { solveCube4State, applyCubeRotation,
|
|
15
|
-
import { solveCube3State } from "../cube3/solver/index.js";
|
|
16
|
-
import { loadCube3Phase1Tables } from "../cube3/solver/tables.js";
|
|
17
|
-
import {
|
|
42
|
+
import { solveCube4State, applyCubeRotation, Cube4FinishCapError, Cube4Phase2CapError, Cube4Phase3CapError } from "./reduction/solve.js";
|
|
43
|
+
import { solveCube3State, Cube3Phase1SearchError, Cube3Phase2SearchError } from "../cube3/solver/index.js";
|
|
44
|
+
import { loadCube3Phase1Tables, loadCube3Phase2Tables } from "../cube3/solver/tables.js";
|
|
45
|
+
import { solveFinish3, Finish3SearchError } from "./reduction/finish3.js";
|
|
46
|
+
import { placeRotationFix } from "./reduction/rotation-fix.js";
|
|
18
47
|
// ────────────────────────────────────────────────────────────
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Deterministic work caps for the 3x3 finish — nodes visited, never
|
|
50
|
+
* wall-clock. Calibrated against the former wall budgets (phase-2 50 ms,
|
|
51
|
+
* improvement 150 ms at a measured ~1.75k nodes/ms warm) so typical
|
|
52
|
+
* latency and finish length are unchanged, while the stopping point
|
|
53
|
+
* becomes independent of CPU load: same (seed, options) ⇒ same moves.
|
|
54
|
+
* A failing first finish call retries once with every cap doubled
|
|
55
|
+
* (equivalent of the former 150→300 / 50→100 ms retry).
|
|
56
|
+
*/
|
|
57
|
+
const FINISH_PHASE2_MAX_NODES = 90_000;
|
|
58
|
+
const FINISH_IMPROVE_MAX_NODES = 260_000;
|
|
59
|
+
const FINISH_RETRY_SCALE = 2;
|
|
60
|
+
// Continuous finish defaults (the production finish). Measured on cc:0..15:
|
|
61
|
+
// 6 views × 200k nodes = 44.88 moves @ 1257 ms vs legacy 46.06 @ 1288 ms
|
|
62
|
+
// (50k: 45.50 @ 1102; 100k: 45.31; 300k: 44.88 @ 1371 — no gain past 200k).
|
|
63
|
+
const FINISH3_MAX_NODES = 200_000;
|
|
64
|
+
const FINISH3_HARD_MAX_NODES = 20_000_000;
|
|
65
|
+
// Screening budget for the two-stage finish (every candidate reduction gets
|
|
66
|
+
// this; only the best `finishScreenTop` get FINISH3_MAX_NODES).
|
|
67
|
+
const FINISH3_SCREEN_NODES = 30_000;
|
|
68
|
+
const FINISH3_VIEWS = 6;
|
|
69
|
+
const FINISH3_MAX_PHASE2_LENGTH = 12;
|
|
70
|
+
let cube4TablesPromise;
|
|
71
|
+
/**
|
|
72
|
+
* Loads the 4x4 reduction tables with a promise-deduplicating cache: the
|
|
73
|
+
* cache stores the in-flight PROMISE (not a value), so concurrent first
|
|
74
|
+
* calls share one load instead of racing; a rejected load is purged so the
|
|
75
|
+
* next call can retry instead of caching the failure forever.
|
|
76
|
+
*/
|
|
77
|
+
function loadCube4Tables(baseUrl) {
|
|
78
|
+
if (cube4TablesPromise !== undefined)
|
|
79
|
+
return cube4TablesPromise;
|
|
80
|
+
cube4TablesPromise = loadCube4TablesOnce(baseUrl).catch((error) => {
|
|
81
|
+
cube4TablesPromise = undefined;
|
|
82
|
+
throw error;
|
|
83
|
+
});
|
|
84
|
+
return cube4TablesPromise;
|
|
85
|
+
}
|
|
86
|
+
async function loadCube4TablesOnce(baseUrl) {
|
|
23
87
|
const isBrowser = typeof window !== "undefined" ||
|
|
24
88
|
typeof document !== "undefined" ||
|
|
25
89
|
(typeof self !== "undefined" && typeof self.importScripts === "function") ||
|
|
@@ -32,20 +96,15 @@ async function loadCube4Tables(baseUrl) {
|
|
|
32
96
|
"./reduction/browser-loader.js");
|
|
33
97
|
const cached = loader.getCachedCube4Tables();
|
|
34
98
|
if (cached) {
|
|
35
|
-
cachedTables = cached;
|
|
36
99
|
return cached;
|
|
37
100
|
}
|
|
38
|
-
|
|
39
|
-
cachedTables = tables;
|
|
40
|
-
return tables;
|
|
101
|
+
return await loader.loadCube4ReductionTablesBrowser(baseUrl);
|
|
41
102
|
}
|
|
42
103
|
const loader = await import(
|
|
43
104
|
/* webpackIgnore: true */
|
|
44
105
|
/* @vite-ignore */
|
|
45
106
|
"./reduction/node-loader.js");
|
|
46
|
-
|
|
47
|
-
cachedTables = tables;
|
|
48
|
-
return tables;
|
|
107
|
+
return await loader.loadCube4ReductionTables();
|
|
49
108
|
}
|
|
50
109
|
export const cube4ScrambleGenerator = {
|
|
51
110
|
info: {
|
|
@@ -60,65 +119,118 @@ export const cube4ScrambleGenerator = {
|
|
|
60
119
|
return generateCube4Scramble(options);
|
|
61
120
|
}
|
|
62
121
|
};
|
|
63
|
-
|
|
64
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Thrown by the target gate when the emitted scramble does not reproduce
|
|
124
|
+
* the independently sampled state. Retryable: the retry loop re-solves the
|
|
125
|
+
* SAME sampled target (deep attempt) — never re-samples.
|
|
126
|
+
*/
|
|
127
|
+
export class Cube4TargetMismatchError extends Error {
|
|
128
|
+
code = "cube4-target-mismatch";
|
|
129
|
+
constructor(message) {
|
|
130
|
+
super(message);
|
|
131
|
+
this.name = "Cube4TargetMismatchError";
|
|
132
|
+
}
|
|
65
133
|
}
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
134
|
+
/** Thrown by the 90 s hang guard (wall-clock, fires only at a tick). */
|
|
135
|
+
export class Cube4WallTimeoutError extends Error {
|
|
136
|
+
code = "cube4-wall-timeout";
|
|
137
|
+
constructor(message) {
|
|
138
|
+
super(message);
|
|
139
|
+
this.name = "Cube4WallTimeoutError";
|
|
140
|
+
}
|
|
71
141
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Message patterns for errors thrown by `reduction/solve.ts`, which cannot
|
|
144
|
+
* be classified by type/code without editing that file (reported: it should
|
|
145
|
+
* grow error codes). Every other retryable path is typed below.
|
|
146
|
+
*/
|
|
147
|
+
const SOLVER_RETRYABLE_MESSAGE_PATTERNS = [
|
|
148
|
+
"reduction failed", // no candidates / node budget exhausted in a reduction phase
|
|
149
|
+
"no valid phase 2" // empty phase-2 candidate set
|
|
150
|
+
];
|
|
151
|
+
/**
|
|
152
|
+
* Retry classification. Typed first (error class / `code`), message
|
|
153
|
+
* patterns last and only for the reduction solver's own errors. Semantics:
|
|
154
|
+
* a retry re-runs the solve over the SAME sampled target with deeper work
|
|
155
|
+
* caps; it never changes the seed's target state.
|
|
156
|
+
*/
|
|
157
|
+
function isFinish3Cap(error) {
|
|
158
|
+
return error instanceof Error &&
|
|
159
|
+
error.code === "cube4-finish3-cap" &&
|
|
160
|
+
(error instanceof Finish3SearchError || error.name === "Finish3SearchError");
|
|
161
|
+
}
|
|
162
|
+
function isRetryableCube4Error(error) {
|
|
163
|
+
if (!(error instanceof Error))
|
|
164
|
+
return false;
|
|
165
|
+
const code = error.code;
|
|
166
|
+
if (code === "cube4-target-mismatch" || code === "cube4-wall-timeout")
|
|
167
|
+
return true;
|
|
168
|
+
if (isFinish3Cap(error))
|
|
169
|
+
return true;
|
|
170
|
+
if (error instanceof Cube4Phase2CapError ||
|
|
171
|
+
error instanceof Cube4Phase3CapError ||
|
|
172
|
+
error instanceof Cube4FinishCapError)
|
|
173
|
+
return true;
|
|
174
|
+
// Budget/depth failures of the 3x3 finish phase 2: a deeper attempt may
|
|
175
|
+
// succeed (historical behavior: any Cube3Phase2* error was retryable).
|
|
176
|
+
if (error instanceof Cube3Phase2SearchError || error.name === "Cube3Phase2SearchError") {
|
|
177
|
+
return true;
|
|
78
178
|
}
|
|
79
|
-
|
|
80
|
-
|
|
179
|
+
// Phase-1 failures are deterministic depth failures (phase 1 runs
|
|
180
|
+
// uncapped in the 444 finish): retrying the same target cannot help.
|
|
181
|
+
if (error instanceof Cube3Phase1SearchError || error.name === "Cube3Phase1SearchError") {
|
|
182
|
+
return false;
|
|
81
183
|
}
|
|
82
|
-
return
|
|
184
|
+
return SOLVER_RETRYABLE_MESSAGE_PATTERNS.some((pattern) => error.message.includes(pattern));
|
|
83
185
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
186
|
+
/**
|
|
187
|
+
* The real random-state contract for 444 (non-tautological): replaying the
|
|
188
|
+
* emitted scramble on the solved cube must reproduce the exact state that
|
|
189
|
+
* was sampled for the seed — the reference the sampler produced, never a
|
|
190
|
+
* state derived from the scramble itself. Throws when it does not.
|
|
191
|
+
*
|
|
192
|
+
* Used by the production validation path in `solveCube4Attempt` and
|
|
193
|
+
* exported so tests can prove the gate rejects corrupted scrambles.
|
|
194
|
+
*/
|
|
195
|
+
export function assertCube4ScrambleReproducesTarget(scrambleMoves, targetState, seed) {
|
|
196
|
+
const replayed = applyCube4Algorithm(createSolvedCube4State(), scrambleMoves);
|
|
197
|
+
if (!cube4StatesEqual(replayed, targetState)) {
|
|
198
|
+
throw new Cube4TargetMismatchError("[cube4-scrambler] scramble does not reproduce the sampled target state" +
|
|
199
|
+
(seed !== undefined ? ` (seed=${seed})` : "") +
|
|
200
|
+
": applying the emitted scramble to the solved cube must yield the state " +
|
|
201
|
+
"sampled for this seed. Refusing to emit it (no re-sampling, no random-move fallback).");
|
|
202
|
+
}
|
|
95
203
|
}
|
|
96
204
|
export async function generateCube4Scramble(options = {}) {
|
|
97
|
-
const bestOf = Math.max(1, Math.floor(options.bestOf ?? 1) || 1);
|
|
98
205
|
let tables;
|
|
99
|
-
|
|
206
|
+
// Optional pair tables are unnecessary for the 444 finish. In browsers,
|
|
207
|
+
// probing for absent tables on every generation adds a network dependency
|
|
208
|
+
// before the search even starts (outside its timeout).
|
|
209
|
+
const pairTables = undefined;
|
|
100
210
|
try {
|
|
101
|
-
const
|
|
211
|
+
const continuousFinish = options.finish?.mode !== "legacy";
|
|
212
|
+
const [loadedTables, cube3P1Tables, cube3P2Tables] = await Promise.all([
|
|
102
213
|
loadCube4Tables(),
|
|
103
214
|
loadCube3Phase1Tables(),
|
|
104
|
-
|
|
215
|
+
continuousFinish ? loadCube3Phase2Tables() : Promise.resolve(undefined)
|
|
105
216
|
]);
|
|
106
217
|
tables = loadedTables;
|
|
107
218
|
tables.cube3Phase1 = cube3P1Tables;
|
|
108
|
-
|
|
109
|
-
|
|
219
|
+
if (cube3P2Tables !== undefined)
|
|
220
|
+
tables.cube3Phase2 = cube3P2Tables;
|
|
110
221
|
}
|
|
111
222
|
catch (error) {
|
|
112
223
|
throw new Error("[cube4-scrambler] Failed to load reduction tables; run pnpm cube4:center1:tables etc. " +
|
|
113
224
|
`(${error instanceof Error ? error.message : String(error)})`);
|
|
114
225
|
}
|
|
115
226
|
const totalRestarts = Math.max(1, Math.floor(options.restarts ?? options.bestOf ?? 1) || 1);
|
|
227
|
+
const targetState = generateRandomCube4State(options);
|
|
116
228
|
let lastError;
|
|
117
229
|
try {
|
|
118
|
-
return await
|
|
230
|
+
return await solveCube4Attempt(tables, pairTables, {
|
|
119
231
|
...options,
|
|
120
232
|
restarts: totalRestarts
|
|
121
|
-
});
|
|
233
|
+
}, targetState);
|
|
122
234
|
}
|
|
123
235
|
catch (error) {
|
|
124
236
|
if (!isRetryableCube4Error(error))
|
|
@@ -127,11 +239,11 @@ export async function generateCube4Scramble(options = {}) {
|
|
|
127
239
|
}
|
|
128
240
|
if (!options.deep) {
|
|
129
241
|
try {
|
|
130
|
-
return await
|
|
242
|
+
return await solveCube4Attempt(tables, pairTables, {
|
|
131
243
|
...options,
|
|
132
244
|
restarts: totalRestarts,
|
|
133
245
|
deep: true
|
|
134
|
-
});
|
|
246
|
+
}, targetState);
|
|
135
247
|
}
|
|
136
248
|
catch (error) {
|
|
137
249
|
if (isRetryableCube4Error(error))
|
|
@@ -146,12 +258,17 @@ export async function generateCube4Scramble(options = {}) {
|
|
|
146
258
|
}
|
|
147
259
|
/**
|
|
148
260
|
* Single-attempt 444 solve: samples ONE state (from `options.seed`) and
|
|
149
|
-
* solves it with the reduction solver. Throws on failure —
|
|
261
|
+
* solves it with the reduction solver. Throws on failure — including a
|
|
262
|
+
* target mismatch ("scramble does not reproduce the sampled target state")
|
|
263
|
+
* — no resampling; hosts may retry with the same seed, which re-uses the
|
|
264
|
+
* same target.
|
|
150
265
|
* Fan-out primitive for hosts: run with seeds `seed`, `seed:bestN`, ...
|
|
151
266
|
* across workers and keep the shortest valid result.
|
|
152
267
|
*/
|
|
153
268
|
export async function generateCube4Attempt(tables, pairTables, options = {}) {
|
|
154
|
-
|
|
269
|
+
return solveCube4Attempt(tables, pairTables, options, generateRandomCube4State(options));
|
|
270
|
+
}
|
|
271
|
+
async function solveCube4Attempt(tables, pairTables, options, attemptState) {
|
|
155
272
|
const axes = options.threeAxes ? [[], ["z"], ["y'"]] : [[]];
|
|
156
273
|
let bestScrambleMoves = null;
|
|
157
274
|
let bestSolutionMoves = null;
|
|
@@ -168,36 +285,84 @@ export async function generateCube4Attempt(tables, pairTables, options = {}) {
|
|
|
168
285
|
// The reduction search is bounded by deterministic work caps (see
|
|
169
286
|
// solve.ts), so these timeouts are only wall-clock backstops for
|
|
170
287
|
// pathological cases.
|
|
171
|
-
const solvePromise = solveCube4State(sRot, tables, async (cube3State) => {
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
const
|
|
288
|
+
const solvePromise = solveCube4State(sRot, tables, async (cube3State, effort) => {
|
|
289
|
+
// Legacy batch two-phase finish. Budgets are deterministic node
|
|
290
|
+
// caps (see FINISH_* constants); `capScale` doubles every cap on
|
|
291
|
+
// the one flaky-finish retry.
|
|
292
|
+
const solveLegacyFinish = async (capScale) => {
|
|
176
293
|
const solution = await solveCube3State(cube3State, {
|
|
177
294
|
orientations: finishOpts.orientations ?? 3,
|
|
178
295
|
phase1ExtraDepth: finishOpts.phase1ExtraDepth ?? 1,
|
|
179
296
|
phase1SolutionLimit: finishOpts.phase1SolutionLimit ?? 24,
|
|
180
|
-
|
|
181
|
-
|
|
297
|
+
// 18 (not 20): the improve loop only runs when the first
|
|
298
|
+
// answer exceeds the target, so 20 skipped it for 19-20 move
|
|
299
|
+
// answers. Measured: -0.13 moves at ~+3% time.
|
|
300
|
+
targetLength: finishOpts.targetLength ?? 18,
|
|
301
|
+
improveMaxNodes: Math.round((finishOpts.improveMaxNodes ?? FINISH_IMPROVE_MAX_NODES) * capScale),
|
|
302
|
+
...(finishOpts.improvePhase2MaxNodes !== undefined
|
|
303
|
+
? {
|
|
304
|
+
improvePhase2MaxNodes: Math.round(finishOpts.improvePhase2MaxNodes * capScale)
|
|
305
|
+
}
|
|
306
|
+
: {}),
|
|
182
307
|
// The 54MB pair tables are skipped for the 444 finish: pair
|
|
183
308
|
// pruning changes nothing measurable here (identical lengths
|
|
184
309
|
// on a 44-state corpus, zero extra finish retries), and
|
|
185
310
|
// loading them dominates cold-start I/O. Hosts that already
|
|
186
311
|
// hold pairTables can still pass them via generateCube4Attempt.
|
|
187
|
-
phase2: {
|
|
312
|
+
phase2: {
|
|
313
|
+
pairTables,
|
|
314
|
+
skipPairTables: pairTables === undefined,
|
|
315
|
+
maxNodes: Math.round((finishOpts.phase2MaxNodes ?? FINISH_PHASE2_MAX_NODES) * capScale)
|
|
316
|
+
}
|
|
188
317
|
});
|
|
189
318
|
return { moves: solution.moves };
|
|
190
319
|
};
|
|
191
|
-
|
|
192
|
-
|
|
320
|
+
if (finishOpts.mode !== "legacy") {
|
|
321
|
+
// Callers of generateCube4Attempt pass their own table bag,
|
|
322
|
+
// which may predate the continuous finish: load what is missing.
|
|
323
|
+
tables.cube3Phase1 ??= await loadCube3Phase1Tables();
|
|
324
|
+
tables.cube3Phase2 ??= await loadCube3Phase2Tables();
|
|
325
|
+
const views = finishOpts.continuousViews ?? FINISH3_VIEWS;
|
|
326
|
+
const runContinuous = (capScale) => {
|
|
327
|
+
if (tables.cube3Phase1 === undefined || tables.cube3Phase2 === undefined) {
|
|
328
|
+
throw new Error("[cube4-scrambler] continuous finish requires cube3 phase-1 and phase-2 tables");
|
|
329
|
+
}
|
|
330
|
+
return solveFinish3(cube3State, { phase1: tables.cube3Phase1, phase2: tables.cube3Phase2 }, {
|
|
331
|
+
targetLength: finishOpts.targetLength ?? 18,
|
|
332
|
+
maxNodes: Math.round((effort === "screen"
|
|
333
|
+
? finishOpts.continuousScreenNodes ?? FINISH3_SCREEN_NODES
|
|
334
|
+
: finishOpts.continuousMaxNodes ?? FINISH3_MAX_NODES) * capScale),
|
|
335
|
+
hardMaxNodes: Math.round((finishOpts.continuousHardMaxNodes ?? FINISH3_HARD_MAX_NODES) * capScale),
|
|
336
|
+
maxPhase2Length: finishOpts.continuousMaxPhase2Length ?? FINISH3_MAX_PHASE2_LENGTH,
|
|
337
|
+
views
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
try {
|
|
341
|
+
return runContinuous(1);
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
if (!isFinish3Cap(error))
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
return runContinuous(FINISH_RETRY_SCALE);
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
if (!isFinish3Cap(error))
|
|
352
|
+
throw error;
|
|
353
|
+
return solveLegacyFinish(1);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// The first legacy call seeds everything, so a flaky finish gets
|
|
357
|
+
// one retry with doubled caps before the whole finish is declared
|
|
358
|
+
// failed (formerly 150→300 / 50→100 wall budgets).
|
|
193
359
|
try {
|
|
194
|
-
return await
|
|
360
|
+
return await solveLegacyFinish(1);
|
|
195
361
|
}
|
|
196
362
|
catch {
|
|
197
|
-
return
|
|
363
|
+
return solveLegacyFinish(FINISH_RETRY_SCALE);
|
|
198
364
|
}
|
|
199
365
|
}, {
|
|
200
|
-
timeoutMs: 75000,
|
|
201
366
|
deep: options.deep === true,
|
|
202
367
|
deepPolish: options.deepPolish ?? options.threeAxes,
|
|
203
368
|
...(options.finishTries !== undefined ? { finishTries: options.finishTries } : {}),
|
|
@@ -207,14 +372,22 @@ export async function generateCube4Attempt(tables, pairTables, options = {}) {
|
|
|
207
372
|
...(options.phase1Cap !== undefined ? { phase1Cap: options.phase1Cap } : {}),
|
|
208
373
|
...(options.center2Wide !== undefined ? { center2Wide: options.center2Wide } : {}),
|
|
209
374
|
...(options.restarts !== undefined ? { restarts: options.restarts } : {}),
|
|
375
|
+
finishScreenTop: finishOpts.mode === "legacy" ? 0 : finishOpts.screenTop ?? 0,
|
|
376
|
+
...(options.maxFixMoves !== undefined ? { maxFixMoves: options.maxFixMoves } : {}),
|
|
210
377
|
...(options.prepCap !== undefined ? { prepCap: options.prepCap } : {}),
|
|
211
378
|
...(options.p1CandCap !== undefined ? { p1CandCap: options.p1CandCap } : {}),
|
|
212
379
|
...(options.stagnationCut !== undefined ? { stagnationCut: options.stagnationCut } : {}),
|
|
213
380
|
maxReductionLen: bestReductionLen
|
|
214
381
|
});
|
|
215
382
|
let timeoutHandle;
|
|
383
|
+
// Honest scope: this race cannot interrupt the synchronous CPU-bound
|
|
384
|
+
// search — a setTimeout callback only runs at an event-loop tick, so
|
|
385
|
+
// the guard can fire only if the async solve chain yields for 90 s
|
|
386
|
+
// total. It exists solely so a pathological hang fails loudly
|
|
387
|
+
// (retryable Cube4WallTimeoutError) instead of wedging the host; it
|
|
388
|
+
// has never bound (max measured solve 36.5 s).
|
|
216
389
|
const timeoutPromise = new Promise((_, reject) => {
|
|
217
|
-
timeoutHandle = setTimeout(() => reject(new
|
|
390
|
+
timeoutHandle = setTimeout(() => reject(new Cube4WallTimeoutError("4x4 solve: timeout")), 90000);
|
|
218
391
|
});
|
|
219
392
|
let solveOutcome;
|
|
220
393
|
try {
|
|
@@ -227,9 +400,12 @@ export async function generateCube4Attempt(tables, pairTables, options = {}) {
|
|
|
227
400
|
clearTimeout(timeoutHandle);
|
|
228
401
|
}
|
|
229
402
|
const { scramble: scrambleMoves, moves: solutionMoves, phases, timings, nodes } = solveOutcome;
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
403
|
+
const fullSolution = rot.length === 0
|
|
404
|
+
? solutionMoves
|
|
405
|
+
: placeRotationFix([], rot, solutionMoves);
|
|
406
|
+
const fullScramble = rot.length === 0
|
|
407
|
+
? scrambleMoves
|
|
408
|
+
: invertCube4Algorithm(fullSolution);
|
|
233
409
|
if (!bestScrambleMoves || fullScramble.length < bestScrambleMoves.length) {
|
|
234
410
|
bestScrambleMoves = fullScramble;
|
|
235
411
|
bestSolutionMoves = fullSolution;
|
|
@@ -249,13 +425,17 @@ export async function generateCube4Attempt(tables, pairTables, options = {}) {
|
|
|
249
425
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
250
426
|
}
|
|
251
427
|
const scrambleMoves = bestScrambleMoves;
|
|
252
|
-
const solutionMoves = bestSolutionMoves;
|
|
253
428
|
const scrambledState = applyCube4Algorithm(createSolvedCube4State(), scrambleMoves);
|
|
254
429
|
if (!options.skipValidation) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
430
|
+
// Non-tautological gate: compare against `attemptState` — the state
|
|
431
|
+
// sampled for this seed (the same reference the sampler produced),
|
|
432
|
+
// NOT a state derived from the scramble. The former check
|
|
433
|
+
// (`apply(scramble, invert(scramble)) === solved`) passed by
|
|
434
|
+
// construction and hid frame bugs. A mismatch throws an error the
|
|
435
|
+
// retry loop classifies as retryable: `generateCube4Scramble` then
|
|
436
|
+
// re-runs the solve over this SAME `attemptState` (deep) and finally
|
|
437
|
+
// throws — it never re-samples another target.
|
|
438
|
+
assertCube4ScrambleReproducesTarget(scrambleMoves, attemptState, options.seed);
|
|
259
439
|
}
|
|
260
440
|
const facelets = toCube4Facelets(scrambledState);
|
|
261
441
|
const result = {
|
|
@@ -266,10 +446,18 @@ export async function generateCube4Attempt(tables, pairTables, options = {}) {
|
|
|
266
446
|
generator: "cube4-reduction",
|
|
267
447
|
metadata: {
|
|
268
448
|
generator: "cube4-reduction",
|
|
269
|
-
|
|
449
|
+
// The sampled target — the independent reference the seed stands for
|
|
450
|
+
// (consistent with the 222/333/pymx/skwb/fto scramblers). When
|
|
451
|
+
// validation ran, this is exactly `apply(solved, moves)`; with
|
|
452
|
+
// `skipValidation` it stays the sampled truth so external checks can
|
|
453
|
+
// compare the emitted scramble against it instead of a derived state.
|
|
454
|
+
state: attemptState,
|
|
270
455
|
facelets,
|
|
271
456
|
faceletString: formatCube4Facelets(facelets),
|
|
272
457
|
distribution: "random-state",
|
|
458
|
+
// The scramble's inverse: by construction it solves the state the
|
|
459
|
+
// scramble actually produces, and (validation on) that state is
|
|
460
|
+
// `attemptState`.
|
|
273
461
|
solutionMoves: invertCube4Algorithm(scrambleMoves),
|
|
274
462
|
// Winning-reduction breakdown (unsimplified lengths) + wall-clock
|
|
275
463
|
// split, for tuning and A/B comparisons.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker pool for 4x4 random-state scrambles (Node.js `worker_threads`).
|
|
3
|
+
*
|
|
4
|
+
* Why: generation is CPU-bound in a single event loop — a `Promise.all`
|
|
5
|
+
* batch of N scrambles does not scale. This pool spreads one batch across
|
|
6
|
+
* N threads so `generateScrambles`-style batches grow roughly linearly
|
|
7
|
+
* with core count, while keeping the (seed, complete-options) → moves
|
|
8
|
+
* contract byte-identical to the inline path: tasks forward the FULL
|
|
9
|
+
* options object verbatim and `worker.ts` calls the very same
|
|
10
|
+
* `generateCube4Scramble`.
|
|
11
|
+
*
|
|
12
|
+
* Lifecycle:
|
|
13
|
+
* - Lazy spawn: threads are created on demand (one per queued job, up to
|
|
14
|
+
* `size`), never in the constructor. A cold first generation pays for
|
|
15
|
+
* table loading (≈37 MB) per spawned thread, once; the thread then
|
|
16
|
+
* reuses its tables for every later job (per-thread promise cache inside
|
|
17
|
+
* `scrambler.ts`).
|
|
18
|
+
* - Idle threads are `unref()`d, so a pool that is never terminated does
|
|
19
|
+
* not keep the process alive; in-flight jobs `ref()` their thread so an
|
|
20
|
+
* awaited batch always completes.
|
|
21
|
+
* - One task in flight per thread; extra tasks queue FIFO.
|
|
22
|
+
* - `terminate()` rejects everything queued/in-flight with
|
|
23
|
+
* `Cube4WorkerPoolClosedError` and joins all threads — after it
|
|
24
|
+
* resolves there are no open handles.
|
|
25
|
+
*
|
|
26
|
+
* Failure: a crashed thread only fails ITS in-flight task and is dropped
|
|
27
|
+
* from the pool; the next dispatch spawns a fresh thread (which reloads
|
|
28
|
+
* tables). Typed solver errors from inside a worker arrive as an `Error`
|
|
29
|
+
* with the original `name`, `message` and `code` preserved (see
|
|
30
|
+
* `worker-protocol.ts`).
|
|
31
|
+
*
|
|
32
|
+
* Dual-build note: this file intentionally contains no bare `import.meta`
|
|
33
|
+
* (it must also compile in the CommonJS build that `index.ts` is part of).
|
|
34
|
+
* The worker entry is located from `__filename` (CJS) or this module's top
|
|
35
|
+
* stack frame (ESM), and `dist-cjs` falls back to the sibling ESM tree,
|
|
36
|
+
* which always ships `worker.js`.
|
|
37
|
+
*/
|
|
38
|
+
import type { ScrambleResult } from "../../core/types.js";
|
|
39
|
+
import type { Cube4ScrambleOptions } from "./scrambler.js";
|
|
40
|
+
import { type Cube4WorkerGenerationOptions } from "./worker-protocol.js";
|
|
41
|
+
import type { ScrambleOptions } from "../../core/types.js";
|
|
42
|
+
/** Full option surface accepted by the pool and the batch helper. */
|
|
43
|
+
export type Cube4PoolOptions = Cube4WorkerGenerationOptions;
|
|
44
|
+
export interface Cube4WorkerPoolOptions {
|
|
45
|
+
/**
|
|
46
|
+
* Maximum number of worker threads. Defaults to
|
|
47
|
+
* `os.availableParallelism() - 1` (min 1), resolved lazily so importing
|
|
48
|
+
* this module costs nothing.
|
|
49
|
+
*/
|
|
50
|
+
size?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Explicit path to the worker entry (`worker.ts` / `worker.js`).
|
|
53
|
+
* Auto-resolved normally; pass this only if the automatic location
|
|
54
|
+
* fails (e.g. a bundler renamed this module).
|
|
55
|
+
*/
|
|
56
|
+
workerPath?: string;
|
|
57
|
+
}
|
|
58
|
+
/** Typed rejection used for post-terminate operations and shutdown itself. */
|
|
59
|
+
export declare class Cube4WorkerPoolClosedError extends Error {
|
|
60
|
+
readonly code: "cube4-pool-closed";
|
|
61
|
+
constructor(action?: string);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Batch seed derivation — byte-for-byte the semantics of
|
|
65
|
+
* `deriveBatchScrambleOptions` in `src/core/api.ts` (`generateScrambles`):
|
|
66
|
+
* with a `seed` and `count > 1`, item i uses `${seed}:${i}`; otherwise the
|
|
67
|
+
* options pass through untouched. Exported so tests can assert the pool
|
|
68
|
+
* batch equals the sequential/core batch for the same inputs.
|
|
69
|
+
*/
|
|
70
|
+
export declare function deriveBatchCube4Options(options: Cube4PoolOptions, index: number, count: number): Cube4PoolOptions;
|
|
71
|
+
export declare class Cube4WorkerPool {
|
|
72
|
+
private readonly workers;
|
|
73
|
+
private readonly queue;
|
|
74
|
+
private readonly explicitSize;
|
|
75
|
+
private readonly workerPathOption;
|
|
76
|
+
private sizePromise;
|
|
77
|
+
/** Last resolved size, kept sync-readable for the pump's headroom check. */
|
|
78
|
+
private sizeSync;
|
|
79
|
+
private pumping;
|
|
80
|
+
private terminatePromise;
|
|
81
|
+
private nextId;
|
|
82
|
+
private terminated;
|
|
83
|
+
constructor(options?: Cube4WorkerPoolOptions);
|
|
84
|
+
/** Maximum thread count (resolves the default lazily on first use). */
|
|
85
|
+
get size(): Promise<number>;
|
|
86
|
+
/** Threads spawned so far (lazily grown; 0 before the first job). */
|
|
87
|
+
get workerCount(): number;
|
|
88
|
+
get queuedCount(): number;
|
|
89
|
+
get busyCount(): number;
|
|
90
|
+
get closed(): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Generates one scramble on an available thread. Rejects (typed) if the
|
|
93
|
+
* pool is closed, the worker crashes, the options cannot be
|
|
94
|
+
* structured-cloned, or generation fails inside the worker.
|
|
95
|
+
*/
|
|
96
|
+
generate(options?: Cube4PoolOptions): Promise<ScrambleResult>;
|
|
97
|
+
/**
|
|
98
|
+
* Batch generation with EXACTLY the seed derivation of core
|
|
99
|
+
* `generateScrambles` (see `deriveBatchCube4Options`), so results are
|
|
100
|
+
* identical — move for move — to the sequential path for the same
|
|
101
|
+
* inputs. Order of the returned array matches request order regardless
|
|
102
|
+
* of completion order.
|
|
103
|
+
*/
|
|
104
|
+
generateBatch(count: number, options?: Cube4PoolOptions): Promise<ScrambleResult[]>;
|
|
105
|
+
/**
|
|
106
|
+
* Prompt shutdown: rejects queued and in-flight tasks with
|
|
107
|
+
* `Cube4WorkerPoolClosedError`, terminates every thread and resolves
|
|
108
|
+
* when none is left. Idempotent.
|
|
109
|
+
*/
|
|
110
|
+
terminate(): Promise<void>;
|
|
111
|
+
private resolveSize;
|
|
112
|
+
/**
|
|
113
|
+
* Serialised dispatcher: grows the pool (lazy spawn), hands queued jobs
|
|
114
|
+
* to idle threads, and stops when every thread is busy at capacity.
|
|
115
|
+
* Re-entry while a spawn is awaited is a no-op — the running loop's
|
|
116
|
+
* `while` re-checks the queue, and every completion event (`response`,
|
|
117
|
+
* `crash`) calls `pump()` again.
|
|
118
|
+
*/
|
|
119
|
+
private pump;
|
|
120
|
+
private hasDispatchHeadroom;
|
|
121
|
+
private runPump;
|
|
122
|
+
private spawnWorker;
|
|
123
|
+
private dispatch;
|
|
124
|
+
private handleResponse;
|
|
125
|
+
private handleWorkerGone;
|
|
126
|
+
private resolveWorkerEntry;
|
|
127
|
+
}
|
|
128
|
+
/** Shared lazily-created pool (used by `generateCube4ScrambleBatch`). */
|
|
129
|
+
export declare function getDefaultCube4WorkerPool(): Cube4WorkerPool;
|
|
130
|
+
/** Terminates (and forgets) the shared pool — call on process shutdown. */
|
|
131
|
+
export declare function closeDefaultCube4WorkerPool(): Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* Batch convenience mirroring core `generateScrambles` for `444`:
|
|
134
|
+
* - `count <= 1` runs INLINE in the caller (no thread spawn, no cold
|
|
135
|
+
* start — a single scramble must never get slower);
|
|
136
|
+
* - `count > 1` fans out over the shared pool (same seed derivation).
|
|
137
|
+
*/
|
|
138
|
+
export declare function generateCube4ScrambleBatch(count: number, options?: ScrambleOptions & Cube4ScrambleOptions): Promise<ScrambleResult[]>;
|
|
139
|
+
//# sourceMappingURL=worker-pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-pool.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/worker-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAEL,KAAK,4BAA4B,EAGlC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,qEAAqE;AACrE,MAAM,MAAM,gBAAgB,GAAG,4BAA4B,CAAC;AAE5D,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,8EAA8E;AAC9E,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,QAAQ,CAAC,IAAI,EAAG,mBAAmB,CAAU;gBAEjC,MAAM,SAAc;CAIjC;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,gBAAgB,CAKlB;AAsED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IACtD,OAAO,CAAC,WAAW,CAA8B;IACjD,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,UAAU,CAAS;gBAEf,OAAO,GAAE,sBAA2B;IAWhD,uEAAuE;IACvE,IAAI,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAE1B;IAED,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;;;;OAIG;IACH,QAAQ,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,cAAc,CAAC;IAWjE;;;;;;OAMG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAWvF;;;;OAIG;IACH,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB1B,OAAO,CAAC,WAAW;IAmBnB;;;;;;OAMG;IACH,OAAO,CAAC,IAAI;IAiBZ,OAAO,CAAC,mBAAmB;YAKb,OAAO;YAuBP,WAAW;IA6BzB,OAAO,CAAC,QAAQ;IAgBhB,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,gBAAgB;IAexB,OAAO,CAAC,kBAAkB;CAkC3B;AAMD,yEAAyE;AACzE,wBAAgB,yBAAyB,IAAI,eAAe,CAG3D;AAED,2EAA2E;AAC3E,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,IAAI,CAAC,CAIjE;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,eAAe,GAAG,oBAAyB,GACnD,OAAO,CAAC,cAAc,EAAE,CAAC,CAQ3B"}
|