@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
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ The package currently ships random-state scramblers for `222` (2x2x2), `333` (3x
|
|
|
16
16
|
- 3x3 quality: random-state target + two-phase solver + verified scramble inversion
|
|
17
17
|
- Pyraminx quality: random-state target + optimal policy-table solver (all 933,120 core states verified)
|
|
18
18
|
- Skewb quality: random-state target + optimal policy-table solver (all 3,149,280 states; exact uniform sampling)
|
|
19
|
-
- 4x4 quality: random-state target (uniform sampler) + reduction solver (4 phases) +
|
|
19
|
+
- 4x4 quality: random-state target (uniform sampler) + reduction solver (4 phases) + exact target reproduction, validated against the sampled state on every generation
|
|
20
20
|
|
|
21
21
|
This library is intended for practice, training, and internal CubingCenter use. Do not present it as an official WCA competition scrambler; sanctioned competitions must use the official program required by the delegate/organization.
|
|
22
22
|
|
|
@@ -59,7 +59,7 @@ console.log(batch.map((item) => item.scramble));
|
|
|
59
59
|
|
|
60
60
|
`generateScramble("333")` returns a legal random-state 3x3 scramble. Its metadata includes the target cubie state, timer-ready facelets, a 54-character facelet string, and solver diagnostics.
|
|
61
61
|
|
|
62
|
-
`generateScramble("pymx")` returns a legal random-state Pyraminx scramble. Its metadata includes the target state, facelets, solution moves and depth. Pyraminx/2x2/3x3 accept `{ skipValidation: true }` to skip the per-call verification for maximum throughput (
|
|
62
|
+
`generateScramble("pymx")` returns a legal random-state Pyraminx scramble. Its metadata includes the target state, facelets, solution moves and depth. Pyraminx/2x2/3x3/444 accept `{ skipValidation: true }` to skip the per-call verification for maximum throughput (correctness is covered by the test suite, and independently by `pnpm verify:scrambles`, which re-samples each target from its seed).
|
|
63
63
|
|
|
64
64
|
## Scoped imports (web / smaller bundles)
|
|
65
65
|
|
|
@@ -194,7 +194,15 @@ const paired = cube4.areWingsPaired(state);
|
|
|
194
194
|
|
|
195
195
|
Supported moves: `U D R L F B` (outer) and `Uw Dw Rw Lw Fw Bw` (wide), each with `'` and `2` suffixes.
|
|
196
196
|
|
|
197
|
-
The public `generateScramble("444")` path is: sample a uniform random state (`generateRandomCube4State` — corners `8! × 3^7`, wings `24!`, centers as a color multiset `24!/(4!)^6`), solve it with the reduction solver (phases: axis-color face-pairs → ct/rl phase-2 goal → joint center3+pairing → 3x3 finish), and invert the solution — so the scramble always reproduces the sampled target. The pairing pruning table is generated by a C++ tool (`tools/edge3-gen/`).
|
|
197
|
+
The public `generateScramble("444")` path is: sample a uniform random state (`generateRandomCube4State` — corners `8! × 3^7`, wings `24!`, centers as a color multiset `24!/(4!)^6`), solve it with the reduction solver (phases: axis-color face-pairs → ct/rl phase-2 goal → joint center3+pairing → 3x3 finish), and invert the solution — so the scramble always reproduces the sampled target. The production path enforces this on every generation (`assertCube4ScrambleReproducesTarget` re-applies the emitted moves and compares against the state sampled for the seed; a mismatch retries the SAME target — never re-samples, never falls back to random-move). The pairing pruning table is generated by a C++ tool (`tools/edge3-gen/`).
|
|
198
|
+
|
|
199
|
+
How to verify 444, from cheap to heavy:
|
|
200
|
+
|
|
201
|
+
- `pnpm test` — model/round-trip tests, the rotation-sensitive invariant (`test/cube4-solve-invariant.test.ts`), and the uniformity net (`test/cube4-uniformity.test.ts`: χ² over a fixed corpus at two layers — direct sampler N=20,000 and full pipeline N=40 with structural checks on parity quadrants and per-slot diversity).
|
|
202
|
+
- `pnpm verify:scrambles 444` — independent replay: the target is re-sampled from the seed and the solution must genuinely solve it.
|
|
203
|
+
- `node --import tsx scripts/uniformity-444.ts --n=500` — heavy χ² bench over the state **reached** by the pipeline (centers/wings/corners per slot, twist, parity quadrants, joint bins); exits 1 below threshold. The corpora are fixed seeds, so the numbers are deterministic; p-values are reference values, not classical significance.
|
|
204
|
+
|
|
205
|
+
Measured baseline (i5-13420H, warm): 46–53 moves per scramble (avg ≈ 48), latency p50 ≈ 2–3 s / p95 ≈ 9 s, ~37 MB of reduction tables loaded once. Seeded 444 generation is a pure function of `(seed, options)` — search budgets are node counters, not wall deadlines; the only wall-clock code is a pair of hang guards that never bound an observed solve.
|
|
198
206
|
|
|
199
207
|
Shorter 444 scrambles, two mechanisms: `bestOf: N` solves N independently sampled states and keeps the shortest (trades CPU time, biases toward short-solution states), while `restarts: R` re-runs the full pipeline R times over the SAME state with a deterministically reshuffled search order and keeps the shortest total (uniformity-preserving; measured ~-0.45 moves at R=3, ~-0.85 at R=6 on a 24-state corpus, single-attempt mean ~46.1).
|
|
200
208
|
|
|
@@ -242,9 +250,12 @@ pnpm package:check
|
|
|
242
250
|
pnpm verify
|
|
243
251
|
```
|
|
244
252
|
|
|
245
|
-
Solver tables are versioned as generated binary assets. If cube coordinates, moves, or pruning logic change, regenerate and check them:
|
|
253
|
+
Solver tables are versioned as generated binary assets. If cube coordinates, moves, or pruning logic change, regenerate and check them (the 4x4 reduction tables follow the same pattern, one script per phase):
|
|
246
254
|
|
|
247
255
|
```sh
|
|
248
256
|
pnpm cube3:tables
|
|
249
257
|
pnpm cube3:tables:check
|
|
258
|
+
pnpm cube4:center1:tables:check # likewise center2, center3, edge3, hc
|
|
250
259
|
```
|
|
260
|
+
|
|
261
|
+
`pnpm verify` runs lint, the full test suite, the build, every table checksum check (including all five `cube4:*:tables:check`), and the package checks. `pnpm verify:scrambles [--count N] [events…]` additionally replays generated scrambles against independently re-sampled targets and exits 1 on any mismatch.
|
package/dist/core/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/core/api.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/core/api.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4CrG;AAED,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,iBAAmC,GAC5C,IAAI,CAEN;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,GAAE,iBAAmC,GAAG,SAAS,EAAE,CAE7F;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,OAAO,EAChB,QAAQ,GAAE,iBAAmC,GAC5C,SAAS,CAQX;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,eAAoB,EAC7B,QAAQ,GAAE,iBAAmC,GAC5C,OAAO,CAAC,cAAc,CAAC,CAiBzB;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,eAAoB,EAC7B,QAAQ,GAAE,iBAAmC,GAC5C,OAAO,CAAC,cAAc,EAAE,CAAC,CAU3B"}
|
package/dist/core/api.js
CHANGED
|
@@ -39,8 +39,16 @@ export async function initializeScrambler(baseUrl, eventId) {
|
|
|
39
39
|
await loadBrowserTables(baseUrl);
|
|
40
40
|
}
|
|
41
41
|
else if (eventId === "444") {
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
// Reduction ends with a 3x3 solve in the same worker. Initializing 444
|
|
43
|
+
// must also load those required tables, including the caller's base URL.
|
|
44
|
+
const [cube4, cube3] = await Promise.all([
|
|
45
|
+
import("../puzzles/cube4/reduction/browser-loader.js"),
|
|
46
|
+
import("../puzzles/cube3/solver/browser-loader.js")
|
|
47
|
+
]);
|
|
48
|
+
await Promise.all([
|
|
49
|
+
cube4.loadCube4ReductionTablesBrowser(baseUrl),
|
|
50
|
+
cube3.loadBrowserTables(baseUrl)
|
|
51
|
+
]);
|
|
44
52
|
}
|
|
45
53
|
else if (!eventId) {
|
|
46
54
|
await Promise.all([
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface Cube2BrowserTables {
|
|
2
2
|
permutationTable: Uint16Array;
|
|
3
3
|
orientationTable: Uint16Array;
|
|
4
4
|
policyTable: Uint8Array;
|
|
5
|
-
}
|
|
5
|
+
}
|
|
6
|
+
export declare function getCachedBrowserTables(): Cube2BrowserTables | undefined;
|
|
6
7
|
export declare function clearCachedBrowserTables(): void;
|
|
7
|
-
export declare function loadBrowserTables(baseUrl?: string): Promise<
|
|
8
|
-
permutationTable: Uint16Array;
|
|
9
|
-
orientationTable: Uint16Array;
|
|
10
|
-
policyTable: Uint8Array;
|
|
11
|
-
}>;
|
|
8
|
+
export declare function loadBrowserTables(baseUrl?: string): Promise<Cube2BrowserTables>;
|
|
12
9
|
//# sourceMappingURL=browser-loader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/browser-loader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/browser-loader.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,kBAAkB;IACjC,gBAAgB,EAAE,WAAW,CAAC;IAC9B,gBAAgB,EAAE,WAAW,CAAC;IAC9B,WAAW,EAAE,UAAU,CAAC;CACzB;AAKD,wBAAgB,sBAAsB,IAAI,kBAAkB,GAAG,SAAS,CAEvE;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C;AAED,wBAAsB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAoBrF"}
|
|
@@ -1,23 +1,42 @@
|
|
|
1
1
|
import { CUBE2_SOLVER_TABLE_METADATA } from "./table-metadata.js";
|
|
2
2
|
import { fetchTableBuffer } from "../../shared/compressed-fetch.js";
|
|
3
3
|
const TABLES_DIR = "puzzles/cube2/solver/generated";
|
|
4
|
+
const CACHE_NAME = "cubingcenter-scrambler-tables-v1";
|
|
4
5
|
let cachedBrowserTables;
|
|
6
|
+
let cachedBrowserTablesPromise;
|
|
5
7
|
export function getCachedBrowserTables() {
|
|
6
8
|
return cachedBrowserTables;
|
|
7
9
|
}
|
|
8
10
|
export function clearCachedBrowserTables() {
|
|
9
11
|
cachedBrowserTables = undefined;
|
|
12
|
+
cachedBrowserTablesPromise = undefined;
|
|
10
13
|
}
|
|
11
14
|
export async function loadBrowserTables(baseUrl) {
|
|
12
15
|
if (cachedBrowserTables) {
|
|
13
16
|
return cachedBrowserTables;
|
|
14
17
|
}
|
|
18
|
+
if (cachedBrowserTablesPromise) {
|
|
19
|
+
// One shared in-flight promise: concurrent calls trigger exactly one
|
|
20
|
+
// fetch pass (dedup).
|
|
21
|
+
return cachedBrowserTablesPromise;
|
|
22
|
+
}
|
|
23
|
+
const promise = fetchTables(baseUrl);
|
|
24
|
+
cachedBrowserTablesPromise = promise;
|
|
25
|
+
promise.then(() => { }, () => {
|
|
26
|
+
// A failed load must not poison the dedup slot: purge the rejected
|
|
27
|
+
// promise so a later call retries from scratch.
|
|
28
|
+
if (cachedBrowserTablesPromise === promise)
|
|
29
|
+
cachedBrowserTablesPromise = undefined;
|
|
30
|
+
});
|
|
31
|
+
return promise;
|
|
32
|
+
}
|
|
33
|
+
async function fetchTables(baseUrl) {
|
|
15
34
|
const resolved = resolveBaseUrl(baseUrl);
|
|
16
35
|
const manifest = await fetchManifest(resolved);
|
|
17
36
|
const [permTable, orientTable, dist] = await Promise.all([
|
|
18
|
-
fetchUint16Table(resolved, manifest.files.permutationTable, CUBE2_SOLVER_TABLE_METADATA.permutationTable
|
|
19
|
-
fetchUint16Table(resolved, manifest.files.orientationTable, CUBE2_SOLVER_TABLE_METADATA.orientationTable
|
|
20
|
-
fetchUint8Table(resolved, manifest.files.policy, CUBE2_SOLVER_TABLE_METADATA.policy
|
|
37
|
+
fetchUint16Table(resolved, manifest.files.permutationTable, CUBE2_SOLVER_TABLE_METADATA.permutationTable),
|
|
38
|
+
fetchUint16Table(resolved, manifest.files.orientationTable, CUBE2_SOLVER_TABLE_METADATA.orientationTable),
|
|
39
|
+
fetchUint8Table(resolved, manifest.files.policy, CUBE2_SOLVER_TABLE_METADATA.policy)
|
|
21
40
|
]);
|
|
22
41
|
cachedBrowserTables = {
|
|
23
42
|
permutationTable: permTable,
|
|
@@ -51,22 +70,31 @@ async function fetchManifest(baseUrl) {
|
|
|
51
70
|
}
|
|
52
71
|
return manifest;
|
|
53
72
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Fetch + verify with the SAME checks as node-loader.ts: byte length from
|
|
75
|
+
* the manifest entries, FNV-1a checksum against the table-metadata
|
|
76
|
+
* constants (the values node asserts against).
|
|
77
|
+
*/
|
|
78
|
+
async function fetchUint16Table(baseUrl, entry, meta) {
|
|
79
|
+
const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
|
|
80
|
+
cacheName: CACHE_NAME,
|
|
81
|
+
compressed: entry.compressed !== false,
|
|
82
|
+
expectedBytes: entry.entries * 2,
|
|
83
|
+
expectedFnv1a: { value: meta.checksum, elementSize: 2 }
|
|
84
|
+
});
|
|
85
|
+
const values = new Uint16Array(entry.entries);
|
|
60
86
|
const view = new DataView(buffer);
|
|
61
87
|
for (let i = 0; i < values.length; i++) {
|
|
62
88
|
values[i] = view.getUint16(i * 2, true);
|
|
63
89
|
}
|
|
64
90
|
return values;
|
|
65
91
|
}
|
|
66
|
-
async function fetchUint8Table(baseUrl, entry,
|
|
67
|
-
const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
92
|
+
async function fetchUint8Table(baseUrl, entry, meta) {
|
|
93
|
+
const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
|
|
94
|
+
cacheName: CACHE_NAME,
|
|
95
|
+
compressed: entry.compressed !== false,
|
|
96
|
+
expectedBytes: entry.entries,
|
|
97
|
+
expectedFnv1a: { value: meta.checksum, elementSize: 1 }
|
|
98
|
+
});
|
|
71
99
|
return new Uint8Array(buffer);
|
|
72
100
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated-assets.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/generated-assets.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generated-assets.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/generated-assets.ts"],"names":[],"mappings":"AAkBA,wBAAsB,8BAA8B,IAAI,OAAO,CAAC;IAC9D,gBAAgB,EAAE,WAAW,CAAC;IAC9B,gBAAgB,EAAE,WAAW,CAAC;IAC9B,WAAW,EAAE,UAAU,CAAC;CACzB,CAAC,CAcD"}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { getCachedBrowserTables } from "./browser-loader.js";
|
|
2
|
-
|
|
2
|
+
import { loadOnce } from "../../shared/load-once.js";
|
|
3
|
+
// One shared in-flight promise for the node fs load (same pattern as
|
|
4
|
+
// cube3/solver/generated-assets.ts): the previous `cached ??= await …` raced —
|
|
5
|
+
// concurrent callers interleaving on the await both ran `loadNodeTables()`
|
|
6
|
+
// (two filesystem reads, last assignment wins). On rejection the slot is
|
|
7
|
+
// purged so the next call can retry.
|
|
8
|
+
const nodeTablesSlot = { promise: undefined };
|
|
3
9
|
function isBrowser() {
|
|
4
10
|
return typeof self !== "undefined";
|
|
5
11
|
}
|
|
@@ -13,8 +19,7 @@ export async function loadGeneratedCube2SolverTables() {
|
|
|
13
19
|
"In browser environments, call `initializeScrambler(baseUrl)` " +
|
|
14
20
|
"before generating scrambles.");
|
|
15
21
|
}
|
|
16
|
-
|
|
17
|
-
return cachedTables;
|
|
22
|
+
return loadOnce(nodeTablesSlot, loadNodeTables);
|
|
18
23
|
}
|
|
19
24
|
async function loadNodeTables() {
|
|
20
25
|
const loader = await import(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/browser-loader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAMV,iBAAiB,EAClB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/browser-loader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAMV,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAuDrB,wBAAgB,sBAAsB,IAAI,iBAAiB,GAAG,SAAS,CAEtE;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C;AAED,wBAAsB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAoBpF"}
|
|
@@ -2,6 +2,7 @@ import { fetchTableBuffer } from "../../shared/compressed-fetch.js";
|
|
|
2
2
|
const TABLES_DIR = "puzzles/cube3/solver/generated";
|
|
3
3
|
const CACHE_NAME = "cubingcenter-scrambler-tables-v1";
|
|
4
4
|
let browserTables;
|
|
5
|
+
let browserTablesPromise;
|
|
5
6
|
function resolveBaseUrl(provided) {
|
|
6
7
|
if (provided) {
|
|
7
8
|
return provided.replace(/\/$/, "");
|
|
@@ -20,11 +21,28 @@ export function getCachedBrowserTables() {
|
|
|
20
21
|
}
|
|
21
22
|
export function clearCachedBrowserTables() {
|
|
22
23
|
browserTables = undefined;
|
|
24
|
+
browserTablesPromise = undefined;
|
|
23
25
|
}
|
|
24
26
|
export async function loadBrowserTables(baseUrl) {
|
|
25
27
|
if (browserTables) {
|
|
26
28
|
return browserTables;
|
|
27
29
|
}
|
|
30
|
+
if (browserTablesPromise) {
|
|
31
|
+
// One shared in-flight promise: concurrent calls trigger exactly one
|
|
32
|
+
// fetch pass (dedup).
|
|
33
|
+
return browserTablesPromise;
|
|
34
|
+
}
|
|
35
|
+
const promise = fetchTables(baseUrl);
|
|
36
|
+
browserTablesPromise = promise;
|
|
37
|
+
promise.then(() => { }, () => {
|
|
38
|
+
// A failed load must not poison the dedup slot: purge the rejected
|
|
39
|
+
// promise so a later call retries from scratch.
|
|
40
|
+
if (browserTablesPromise === promise)
|
|
41
|
+
browserTablesPromise = undefined;
|
|
42
|
+
});
|
|
43
|
+
return promise;
|
|
44
|
+
}
|
|
45
|
+
async function fetchTables(baseUrl) {
|
|
28
46
|
const resolvedBase = resolveBaseUrl(baseUrl);
|
|
29
47
|
const manifest = await fetchManifest(resolvedBase);
|
|
30
48
|
browserTables = {
|
|
@@ -58,19 +76,15 @@ async function fetchJointTables(baseUrl, entries) {
|
|
|
58
76
|
return undefined;
|
|
59
77
|
}
|
|
60
78
|
const fetchJoint = async (entry) => {
|
|
79
|
+
// Verified by fetchTableBuffer with the SAME checks as node-loader.ts:
|
|
80
|
+
// nibble-packed byte length + FNV-1a over the packed bytes.
|
|
61
81
|
const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
|
|
62
82
|
cacheName: CACHE_NAME,
|
|
63
|
-
compressed: entry.compressed !== false
|
|
83
|
+
compressed: entry.compressed !== false,
|
|
84
|
+
expectedBytes: Math.ceil((entry.rangeA * entry.rangeB) / 2),
|
|
85
|
+
expectedFnv1a: { value: entry.checksum, elementSize: 1 }
|
|
64
86
|
});
|
|
65
|
-
|
|
66
|
-
if (buffer.byteLength !== expectedBytes) {
|
|
67
|
-
throw new Error(`Invalid byte size for ${entry.file}.`);
|
|
68
|
-
}
|
|
69
|
-
const values = new Uint8Array(buffer);
|
|
70
|
-
if (checksum(values) !== entry.checksum) {
|
|
71
|
-
throw new Error(`Checksum mismatch for ${entry.file}.`);
|
|
72
|
-
}
|
|
73
|
-
return values;
|
|
87
|
+
return new Uint8Array(buffer);
|
|
74
88
|
};
|
|
75
89
|
return {
|
|
76
90
|
jointUdCo: await fetchJoint(entries.udCo),
|
|
@@ -90,11 +104,15 @@ async function fetchManifest(baseUrl) {
|
|
|
90
104
|
return manifest;
|
|
91
105
|
}
|
|
92
106
|
async function fetchMoveTable(baseUrl, entry) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
107
|
+
// Verified by fetchTableBuffer with the SAME checks as node-loader.ts:
|
|
108
|
+
// int32 wire length + FNV-1a over the decoded coordinate values (element
|
|
109
|
+
// size 4 reproduces node's hash over the uint16 values — same numbers).
|
|
110
|
+
const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
|
|
111
|
+
cacheName: CACHE_NAME,
|
|
112
|
+
compressed: entry.compressed !== false,
|
|
113
|
+
expectedBytes: entry.range * entry.moves.length * 4,
|
|
114
|
+
expectedFnv1a: { value: entry.checksum, elementSize: 4 }
|
|
115
|
+
});
|
|
98
116
|
const values = new Uint16Array(entry.range * entry.moves.length);
|
|
99
117
|
const view = new DataView(buffer);
|
|
100
118
|
for (let index = 0; index < values.length; index += 1) {
|
|
@@ -114,11 +132,15 @@ async function fetchMoveTable(baseUrl, entry) {
|
|
|
114
132
|
};
|
|
115
133
|
}
|
|
116
134
|
async function fetchPruningTable(baseUrl, entry) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
135
|
+
// Verified by fetchTableBuffer with the SAME checks as node-loader.ts:
|
|
136
|
+
// nibble-packed byte length + FNV-1a over the packed bytes (this browser
|
|
137
|
+
// path did NOT compare the pruning checksum before — only the length).
|
|
138
|
+
const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
|
|
139
|
+
cacheName: CACHE_NAME,
|
|
140
|
+
compressed: entry.compressed !== false,
|
|
141
|
+
expectedBytes: Math.ceil(entry.range / 2),
|
|
142
|
+
expectedFnv1a: { value: entry.checksum, elementSize: 1 }
|
|
143
|
+
});
|
|
122
144
|
const values = new Uint8Array(buffer);
|
|
123
145
|
return {
|
|
124
146
|
coordinate: entry.coordinate,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated-assets.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/generated-assets.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"generated-assets.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/generated-assets.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAgBrB,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAcjF;AAWD,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAkBjF;AAED,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAkBjF"}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { getCachedBrowserTables } from "./browser-loader.js";
|
|
2
|
-
|
|
2
|
+
import { loadOnce } from "../../shared/load-once.js";
|
|
3
3
|
function isBrowser() {
|
|
4
4
|
return typeof self !== "undefined";
|
|
5
5
|
}
|
|
6
|
+
// One shared in-flight promise for the node fs load: mixed concurrent
|
|
7
|
+
// phase-1 + phase-2 loads (separate slots in tables.ts) converge on ONE
|
|
8
|
+
// `readGeneratedCube3SolverTables()` call. The previous `cached ??= await …`
|
|
9
|
+
// raced — two callers interleaving on the await both ran `loadNodeTables()`
|
|
10
|
+
// (two filesystem reads, two table sets, last assignment wins). On
|
|
11
|
+
// rejection the slot is purged so the next call can retry.
|
|
12
|
+
const nodeTablesSlot = { promise: undefined };
|
|
6
13
|
export async function loadGeneratedCube3SolverTables() {
|
|
7
14
|
if (isBrowser()) {
|
|
8
15
|
const browserTables = getCachedBrowserTables();
|
|
@@ -13,8 +20,7 @@ export async function loadGeneratedCube3SolverTables() {
|
|
|
13
20
|
"In browser environments, call `initializeScrambler(baseUrl)` " +
|
|
14
21
|
"before generating scrambles.");
|
|
15
22
|
}
|
|
16
|
-
|
|
17
|
-
return cachedGeneratedTables;
|
|
23
|
+
return loadOnce(nodeTablesSlot, loadNodeTables);
|
|
18
24
|
}
|
|
19
25
|
async function loadNodeTables() {
|
|
20
26
|
const loader = await import(
|
|
@@ -7,7 +7,20 @@ export interface Cube3Phase1Coordinates {
|
|
|
7
7
|
}
|
|
8
8
|
export interface Cube3Phase1Options {
|
|
9
9
|
maxDepth?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Legacy wall-clock deadline (checked every 1024 nodes). The stopping
|
|
12
|
+
* point — and therefore the result — depends on machine speed and CPU
|
|
13
|
+
* load. Prefer `maxNodes` when the caller needs a reproducible outcome.
|
|
14
|
+
*/
|
|
10
15
|
timeoutMs?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Deterministic work cap: stop after visiting this many nodes (checked on
|
|
18
|
+
* every node; overshoot ≤ 1). The result then depends only on
|
|
19
|
+
* (state, tables, budgets) — never on the clock — so the same input
|
|
20
|
+
* yields the same solution under any CPU load. Composes with `timeoutMs`
|
|
21
|
+
* when both are set (whichever trips first stops the search).
|
|
22
|
+
*/
|
|
23
|
+
maxNodes?: number;
|
|
11
24
|
tables?: Cube3Phase1Tables;
|
|
12
25
|
/** Phase-1 joint tables (ud x co / ud x eo), experimental. */
|
|
13
26
|
jointTables?: Cube3JointTables | undefined;
|
|
@@ -32,6 +45,12 @@ export interface Cube3Phase1CollectOptions {
|
|
|
32
45
|
/** Phase-1 joint tables (ud x co / ud x eo), experimental. */
|
|
33
46
|
jointTables?: Cube3JointTables | undefined;
|
|
34
47
|
timeoutMs?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Deterministic node budget for this enumeration (see
|
|
50
|
+
* `Cube3Phase1Options.maxNodes`). Preferred over `timeoutMs` when the
|
|
51
|
+
* collected set must not depend on CPU load.
|
|
52
|
+
*/
|
|
53
|
+
maxNodes?: number;
|
|
35
54
|
/** Stop after collecting this many solutions. Defaults to 32. */
|
|
36
55
|
limit?: number;
|
|
37
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phase1.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/phase1.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAMzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAOvE,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,8DAA8D;IAC9D,WAAW,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;CAC5C;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,sBAAsB,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;gBAElB,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;CAKlD;
|
|
1
|
+
{"version":3,"file":"phase1.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/phase1.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAMzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAOvE,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,8DAA8D;IAC9D,WAAW,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;CAC5C;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,sBAAsB,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;gBAElB,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;CAKlD;AA4CD,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAwF5B;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,UAAU,GAAG,sBAAsB,CAMtF;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,UAAU,GAAG,sBAAsB,EAC1C,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAQT;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,SAAS,SAAS,EAAE,GAC1B,UAAU,CAEZ;AAyFD,MAAM,WAAW,yBAAyB;IACxC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,8DAA8D;IAC9D,WAAW,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC;IAAE,SAAS,EAAE,SAAS,EAAE,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,CA2B7D"}
|
|
@@ -28,7 +28,7 @@ export async function solveCube3Phase1(state, options = {}) {
|
|
|
28
28
|
const maxDepth = options.maxDepth ?? 12;
|
|
29
29
|
const start = encodeCube3Phase1Coordinates(state);
|
|
30
30
|
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
31
|
-
const context = createPhase1SearchContext(tables, jointTables, maxDepth, deadline);
|
|
31
|
+
const context = createPhase1SearchContext(tables, jointTables, maxDepth, deadline, options.maxNodes);
|
|
32
32
|
if (await isCube3Phase1Solved(start, tables)) {
|
|
33
33
|
return {
|
|
34
34
|
moves: [],
|
|
@@ -70,6 +70,9 @@ export async function solveCube3Phase1(state, options = {}) {
|
|
|
70
70
|
visitedNodes: context.visitedNodes
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
+
if (context.nodesExceeded) {
|
|
74
|
+
throw new Cube3Phase1SearchError(`3x3 Phase 1 node budget exceeded (${options.maxNodes} nodes).`, context.visitedNodes);
|
|
75
|
+
}
|
|
73
76
|
if (context.timedOut) {
|
|
74
77
|
throw new Cube3Phase1SearchError(`3x3 Phase 1 search timed out after ${options.timeoutMs}ms.`, context.visitedNodes);
|
|
75
78
|
}
|
|
@@ -100,8 +103,17 @@ const PHASE1_MOVE_FACES = CUBE3_PHASE1_MOVES.map((move) => {
|
|
|
100
103
|
});
|
|
101
104
|
function searchPhase1(cornerOrientation, edgeOrientation, udSlice, previousFace, previousPreviousFace, remainingDepth, path, context) {
|
|
102
105
|
context.visitedNodes += 1;
|
|
103
|
-
if (context.
|
|
106
|
+
if (context.nodeCap !== undefined &&
|
|
107
|
+
context.visitedNodes > context.nodeCap) {
|
|
108
|
+
context.nodesExceeded = true;
|
|
109
|
+
}
|
|
110
|
+
if (!context.nodesExceeded &&
|
|
111
|
+
context.deadline !== undefined &&
|
|
112
|
+
(context.visitedNodes & 1023) === 0 &&
|
|
113
|
+
performance.now() > context.deadline) {
|
|
104
114
|
context.timedOut = true;
|
|
115
|
+
}
|
|
116
|
+
if (context.timedOut || context.nodesExceeded) {
|
|
105
117
|
return false;
|
|
106
118
|
}
|
|
107
119
|
if (remainingDepth === 0) {
|
|
@@ -124,7 +136,7 @@ function searchPhase1(cornerOrientation, edgeOrientation, udSlice, previousFace,
|
|
|
124
136
|
return true;
|
|
125
137
|
}
|
|
126
138
|
path.pop();
|
|
127
|
-
if (context.timedOut) {
|
|
139
|
+
if (context.timedOut || context.nodesExceeded) {
|
|
128
140
|
return false;
|
|
129
141
|
}
|
|
130
142
|
}
|
|
@@ -141,13 +153,13 @@ export async function collectCube3Phase1Solutions(state, depth, options = {}) {
|
|
|
141
153
|
const jointTables = options.jointTables ?? tables.jointTables;
|
|
142
154
|
const limit = options.limit ?? 32;
|
|
143
155
|
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
144
|
-
const context = createPhase1SearchContext(tables, jointTables, depth, deadline);
|
|
156
|
+
const context = createPhase1SearchContext(tables, jointTables, depth, deadline, options.maxNodes);
|
|
145
157
|
const start = encodeCube3Phase1Coordinates(state);
|
|
146
158
|
const solutions = [];
|
|
147
159
|
searchPhase1Collect(start.cornerOrientation, start.edgeOrientation, start.udSlice, -1, -1, depth, [], solutions, limit, context);
|
|
148
160
|
return { solutions, visitedNodes: context.visitedNodes };
|
|
149
161
|
}
|
|
150
|
-
function createPhase1SearchContext(tables, jointTables, maxDepth, deadline) {
|
|
162
|
+
function createPhase1SearchContext(tables, jointTables, maxDepth, deadline, nodeCap) {
|
|
151
163
|
const scratchLevels = maxDepth + 1;
|
|
152
164
|
const scratchSize = scratchLevels * CUBE3_PHASE1_MOVES.length;
|
|
153
165
|
return {
|
|
@@ -155,8 +167,10 @@ function createPhase1SearchContext(tables, jointTables, maxDepth, deadline) {
|
|
|
155
167
|
jointTables,
|
|
156
168
|
maxDepth,
|
|
157
169
|
deadline,
|
|
170
|
+
nodeCap,
|
|
158
171
|
visitedNodes: 0,
|
|
159
172
|
timedOut: false,
|
|
173
|
+
nodesExceeded: false,
|
|
160
174
|
scratchMove: new Int32Array(scratchSize),
|
|
161
175
|
scratchBound: new Int32Array(scratchSize),
|
|
162
176
|
scratchCo: new Int32Array(scratchSize),
|
|
@@ -252,8 +266,17 @@ function collectPhase1Children(cornerOrientation, edgeOrientation, udSlice, prev
|
|
|
252
266
|
*/
|
|
253
267
|
function searchPhase1Collect(cornerOrientation, edgeOrientation, udSlice, previousFace, previousPreviousFace, remainingDepth, path, solutions, limit, context) {
|
|
254
268
|
context.visitedNodes += 1;
|
|
255
|
-
if (context.
|
|
269
|
+
if (context.nodeCap !== undefined &&
|
|
270
|
+
context.visitedNodes > context.nodeCap) {
|
|
271
|
+
context.nodesExceeded = true;
|
|
272
|
+
}
|
|
273
|
+
if (!context.nodesExceeded &&
|
|
274
|
+
context.deadline !== undefined &&
|
|
275
|
+
(context.visitedNodes & 1023) === 0 &&
|
|
276
|
+
performance.now() > context.deadline) {
|
|
256
277
|
context.timedOut = true;
|
|
278
|
+
}
|
|
279
|
+
if (context.timedOut || context.nodesExceeded) {
|
|
257
280
|
return true;
|
|
258
281
|
}
|
|
259
282
|
if (remainingDepth === 0) {
|
|
@@ -9,7 +9,18 @@ export interface Cube3Phase2Coordinates {
|
|
|
9
9
|
}
|
|
10
10
|
export interface Cube3Phase2Options {
|
|
11
11
|
maxDepth?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Legacy wall-clock deadline (checked every 1024 nodes): the stopping
|
|
14
|
+
* point — and the result — depends on machine speed and CPU load.
|
|
15
|
+
* Prefer `maxNodes` for reproducible outcomes.
|
|
16
|
+
*/
|
|
12
17
|
timeoutMs?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Deterministic work cap: stop after visiting this many nodes (checked
|
|
20
|
+
* on every node; overshoot ≤ 1). Same input + same cap ⇒ same result
|
|
21
|
+
* regardless of load/clock. Composes with `timeoutMs` when both are set.
|
|
22
|
+
*/
|
|
23
|
+
maxNodes?: number;
|
|
13
24
|
tables?: Cube3Phase2Tables;
|
|
14
25
|
/** Combined (cp x uep) pair-distance pruning tables (experimental). */
|
|
15
26
|
pairTables?: Cube3PairTables | undefined;
|
|
@@ -63,8 +74,25 @@ export interface Cube3SolveOptions {
|
|
|
63
74
|
* Per-candidate phase-2 budget (ms) inside the improvement loop.
|
|
64
75
|
* Defaults to `phase2.timeoutMs` when set, else 25. The initial solve
|
|
65
76
|
* always uses `phase2.timeoutMs` untouched, so this never starves it.
|
|
77
|
+
* Legacy wall-clock; superseded by `improvePhase2MaxNodes`.
|
|
66
78
|
*/
|
|
67
79
|
improvePhase2TimeoutMs?: number;
|
|
80
|
+
/**
|
|
81
|
+
* Deterministic node budget for the improvement loop (phase-1 collection
|
|
82
|
+
* + candidate phase-2 searches), counted as visited search nodes across
|
|
83
|
+
* the whole loop of ONE solve call. When set, the wall deadline
|
|
84
|
+
* (`timeoutMs`, default 800 ms) is NOT armed: the returned solution
|
|
85
|
+
* depends only on (state, tables, budgets) and is reproducible under any
|
|
86
|
+
* CPU load — this is the mode the 4x4 finish uses.
|
|
87
|
+
*/
|
|
88
|
+
improveMaxNodes?: number;
|
|
89
|
+
/**
|
|
90
|
+
* Deterministic per-candidate phase-2 node cap inside the improvement
|
|
91
|
+
* loop (counterpart of `improvePhase2TimeoutMs`). Defaults to
|
|
92
|
+
* `phase2.maxNodes` when set, else the shared `improveMaxNodes`
|
|
93
|
+
* remainder. Ignored unless `improveMaxNodes` is set.
|
|
94
|
+
*/
|
|
95
|
+
improvePhase2MaxNodes?: number;
|
|
68
96
|
/**
|
|
69
97
|
* Solve rotated copies of the state and keep the shortest solution
|
|
70
98
|
* (1 = UD only, 3 = UD/x/z triple, up to 24 for the full rotation
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phase2.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/phase2.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAezD,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAarD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAQ9D,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,uEAAuE;IACvE,UAAU,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IACzC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,sBAAsB,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B
|
|
1
|
+
{"version":3,"file":"phase2.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/phase2.ts"],"names":[],"mappings":"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"}
|