@cubingcenter/scrambler 0.6.0 → 0.6.2
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/dist/core/types.d.ts +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/browser-loader.js +26 -1
- package/dist/puzzles/cube3/solver/generated/manifest.json +14 -0
- package/dist/puzzles/cube3/solver/generated/phase1-udco.bin +1 -0
- package/dist/puzzles/cube3/solver/generated/phase1-udeo.bin +1 -0
- package/dist/puzzles/cube3/solver/generated-assets.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/generated-assets.js +2 -1
- package/dist/puzzles/cube3/solver/node-loader.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/node-loader.js +22 -0
- package/dist/puzzles/cube3/solver/pair-browser-loader.d.ts +11 -0
- package/dist/puzzles/cube3/solver/pair-browser-loader.d.ts.map +1 -0
- package/dist/puzzles/cube3/solver/pair-browser-loader.js +93 -0
- package/dist/puzzles/cube3/solver/pair-node-loader.d.ts +6 -0
- package/dist/puzzles/cube3/solver/pair-node-loader.d.ts.map +1 -0
- package/dist/puzzles/cube3/solver/pair-node-loader.js +79 -0
- package/dist/puzzles/cube3/solver/pair-tables-types.d.ts +37 -0
- package/dist/puzzles/cube3/solver/pair-tables-types.d.ts.map +1 -0
- package/dist/puzzles/cube3/solver/pair-tables-types.js +29 -0
- package/dist/puzzles/cube3/solver/pair-tables.d.ts +29 -0
- package/dist/puzzles/cube3/solver/pair-tables.d.ts.map +1 -0
- package/dist/puzzles/cube3/solver/pair-tables.js +69 -0
- package/dist/puzzles/cube3/solver/phase1.d.ts +3 -1
- package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase1.js +32 -2
- package/dist/puzzles/cube3/solver/phase2.d.ts +3 -0
- package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase2.js +63 -5
- package/dist/puzzles/cube3/solver/tables.d.ts +16 -0
- package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/tables.js +4 -2
- package/dist/puzzles/cube4/scrambler.d.ts +4 -1
- package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube4/scrambler.js +101 -14
- package/dist-cjs/puzzles/cube3/solver/browser-loader.js +26 -1
- package/dist-cjs/puzzles/cube3/solver/generated/manifest.json +14 -0
- package/dist-cjs/puzzles/cube3/solver/generated/phase1-udco.bin +1 -0
- package/dist-cjs/puzzles/cube3/solver/generated/phase1-udeo.bin +1 -0
- package/dist-cjs/puzzles/cube3/solver/generated-assets.js +2 -1
- package/dist-cjs/puzzles/cube3/solver/node-loader.js +22 -0
- package/dist-cjs/puzzles/cube3/solver/pair-browser-loader.js +96 -0
- package/dist-cjs/puzzles/cube3/solver/pair-node-loader.js +85 -0
- package/dist-cjs/puzzles/cube3/solver/pair-tables-types.js +33 -0
- package/dist-cjs/puzzles/cube3/solver/pair-tables.js +107 -0
- package/dist-cjs/puzzles/cube3/solver/phase1.js +32 -2
- package/dist-cjs/puzzles/cube3/solver/phase2.js +63 -5
- package/dist-cjs/puzzles/cube3/solver/tables.js +4 -2
- package/dist-cjs/puzzles/cube4/scrambler.js +101 -15
- package/package.json +4 -1
|
@@ -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;AASrB,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAejF;AAWD,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,iBAAiB,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;AASrB,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAejF;AAWD,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAkBjF;AAED,wBAAsB,8BAA8B,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAkBjF"}
|
|
@@ -37,7 +37,8 @@ export async function loadGeneratedCube3Phase1Tables() {
|
|
|
37
37
|
edgeOrientation: tables.pruningTables.edgeOrientation,
|
|
38
38
|
udSlice: tables.pruningTables.udSlice,
|
|
39
39
|
cornerOrientationEdgeOrientation: tables.pruningTables.cornerOrientationEdgeOrientation
|
|
40
|
-
}
|
|
40
|
+
},
|
|
41
|
+
jointTables: tables.jointTables
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
export async function loadGeneratedCube3Phase2Tables() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/node-loader.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"node-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/node-loader.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAMV,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAkCrB,wBAAgB,8BAA8B,IAAI,iBAAiB,CAqClE"}
|
|
@@ -26,7 +26,29 @@ export function readGeneratedCube3SolverTables() {
|
|
|
26
26
|
slicePermutation: readPruningTable(assetDirectory, manifest.pruningTables.slicePermutation),
|
|
27
27
|
cornerPermutationSlicePermutation: readPruningTable(assetDirectory, manifest.pruningTables.cornerPermutationSlicePermutation),
|
|
28
28
|
udEdgePermutationSlicePermutation: readPruningTable(assetDirectory, manifest.pruningTables.udEdgePermutationSlicePermutation)
|
|
29
|
+
},
|
|
30
|
+
jointTables: readJointTables(assetDirectory, manifest.jointTables)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function readJointTables(assetDirectory, entries) {
|
|
34
|
+
if (!entries?.udCo || !entries.udEo) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const readJoint = (entry) => {
|
|
38
|
+
const buffer = readFileSync(path.join(assetDirectory, entry.file));
|
|
39
|
+
const expectedBytes = Math.ceil((entry.rangeA * entry.rangeB) / 2);
|
|
40
|
+
if (buffer.length !== expectedBytes) {
|
|
41
|
+
throw new Error(`Invalid byte size for ${entry.file}.`);
|
|
29
42
|
}
|
|
43
|
+
const values = new Uint8Array(buffer);
|
|
44
|
+
if (checksum(values) !== entry.checksum) {
|
|
45
|
+
throw new Error(`Checksum mismatch for ${entry.file}.`);
|
|
46
|
+
}
|
|
47
|
+
return values;
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
jointUdCo: readJoint(entries.udCo),
|
|
51
|
+
jointUdEo: readJoint(entries.udEo)
|
|
30
52
|
};
|
|
31
53
|
}
|
|
32
54
|
function readManifest(assetDirectory) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser loader for the combined (cp x uep) pair pruning tables.
|
|
3
|
+
*
|
|
4
|
+
* Fetches the plain .bin files from the web assets base URL (the web sync
|
|
5
|
+
* script copies `generated/pair/` wholesale; compression variants are not
|
|
6
|
+
* used for the pair tables yet — the 55 MB dist table is the dominant
|
|
7
|
+
* download either way).
|
|
8
|
+
*/
|
|
9
|
+
import type { Cube3PairTables } from "./pair-tables-types.js";
|
|
10
|
+
export declare function loadBrowserPairTables(baseUrl?: string): Promise<Cube3PairTables | undefined>;
|
|
11
|
+
//# sourceMappingURL=pair-browser-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pair-browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/pair-browser-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,wBAAwB,CAAC;AAmB5E,wBAAsB,qBAAqB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CA6DlG"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser loader for the combined (cp x uep) pair pruning tables.
|
|
3
|
+
*
|
|
4
|
+
* Fetches the plain .bin files from the web assets base URL (the web sync
|
|
5
|
+
* script copies `generated/pair/` wholesale; compression variants are not
|
|
6
|
+
* used for the pair tables yet — the 55 MB dist table is the dominant
|
|
7
|
+
* download either way).
|
|
8
|
+
*/
|
|
9
|
+
import { buildDenseMin, checksumBytes } from "./pair-tables-types.js";
|
|
10
|
+
const PAIR_DIR = "puzzles/cube3/solver/generated/pair";
|
|
11
|
+
function resolveBaseUrl(provided) {
|
|
12
|
+
if (provided) {
|
|
13
|
+
return provided.replace(/\/$/, "");
|
|
14
|
+
}
|
|
15
|
+
if (typeof document !== "undefined") {
|
|
16
|
+
const script = document.currentScript;
|
|
17
|
+
if (script?.src) {
|
|
18
|
+
const base = new URL(".", script.src).href;
|
|
19
|
+
return base.replace(/\/$/, "");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return ".";
|
|
23
|
+
}
|
|
24
|
+
export async function loadBrowserPairTables(baseUrl) {
|
|
25
|
+
const resolvedBase = resolveBaseUrl(baseUrl);
|
|
26
|
+
const manifest = await fetchManifest(resolvedBase);
|
|
27
|
+
if (manifest === undefined) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const fetchBin = async (file, expectedBytes, expectedChecksum) => {
|
|
31
|
+
const url = `${resolvedBase}/${PAIR_DIR}/${file}`;
|
|
32
|
+
const response = await fetch(url);
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw new Error(`Failed to load pair table ${url}: ${response.status} ${response.statusText}`);
|
|
35
|
+
}
|
|
36
|
+
const buffer = await response.arrayBuffer();
|
|
37
|
+
if (buffer.byteLength !== expectedBytes) {
|
|
38
|
+
throw new Error(`Invalid byte size for ${file}.`);
|
|
39
|
+
}
|
|
40
|
+
const values = new Uint8Array(buffer);
|
|
41
|
+
const actual = checksumBytes(values);
|
|
42
|
+
if (actual !== expectedChecksum) {
|
|
43
|
+
throw new Error(`Checksum mismatch for ${file}: expected ${expectedChecksum}, received ${actual}.`);
|
|
44
|
+
}
|
|
45
|
+
return values;
|
|
46
|
+
};
|
|
47
|
+
const fetchU16 = async (file, count, expectedChecksum) => {
|
|
48
|
+
const values = new Uint16Array(count);
|
|
49
|
+
const buffer = await fetchBin(file, count * 2, expectedChecksum);
|
|
50
|
+
for (let index = 0; index < count; index += 1) {
|
|
51
|
+
values[index] = buffer[index * 2] | (buffer[index * 2 + 1] << 8);
|
|
52
|
+
}
|
|
53
|
+
return values;
|
|
54
|
+
};
|
|
55
|
+
const fetchU32 = async (file, count, expectedChecksum) => {
|
|
56
|
+
const values = new Uint32Array(count);
|
|
57
|
+
const buffer = await fetchBin(file, count * 4, expectedChecksum);
|
|
58
|
+
for (let index = 0; index < count; index += 1) {
|
|
59
|
+
values[index] =
|
|
60
|
+
buffer[index * 4] |
|
|
61
|
+
(buffer[index * 4 + 1] << 8) |
|
|
62
|
+
(buffer[index * 4 + 2] << 16) |
|
|
63
|
+
(buffer[index * 4 + 3] << 24);
|
|
64
|
+
}
|
|
65
|
+
return values;
|
|
66
|
+
};
|
|
67
|
+
const canonCpMin = await fetchU16("phase2-pair-canoncpmin.bin", 40320, manifest.checksums.canonCpMin);
|
|
68
|
+
const canonTauStart = await fetchU32("phase2-pair-canonctaustart.bin", 40321, manifest.checksums.canonTauStart);
|
|
69
|
+
const canonTaus = await fetchBin("phase2-pair-canonctaus.bin", canonTauStart[40320], manifest.checksums.canonTaus);
|
|
70
|
+
const conjEp = await fetchU16("phase2-pair-conjep.bin", 16 * 40320, manifest.checksums.conjEp);
|
|
71
|
+
const dist = await fetchBin("phase2-pair-dist.bin", manifest.tableBytes, manifest.checksums.dist);
|
|
72
|
+
return {
|
|
73
|
+
denseMin: buildDenseMin(canonCpMin, manifest.nMinima),
|
|
74
|
+
canonCpMin,
|
|
75
|
+
canonTauStart,
|
|
76
|
+
canonTaus,
|
|
77
|
+
conjEp,
|
|
78
|
+
dist,
|
|
79
|
+
nMinima: manifest.nMinima,
|
|
80
|
+
maxDepth: manifest.maxDepth
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function fetchManifest(baseUrl) {
|
|
84
|
+
const url = `${baseUrl}/${PAIR_DIR}/phase2-pair-manifest.json`;
|
|
85
|
+
const response = await fetch(url);
|
|
86
|
+
if (response.status === 404) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
throw new Error(`Failed to load pair table manifest from ${url}: ${response.status} ${response.statusText}`);
|
|
91
|
+
}
|
|
92
|
+
return await response.json();
|
|
93
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pair-node-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/pair-node-loader.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,wBAAwB,CAAC;AAK5E,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,SAAS,CAwDhE"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node loader for the combined (cp x uep) pair pruning tables.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { buildDenseMin, checksumBytes } from "./pair-tables-types.js";
|
|
7
|
+
const PAIR_DIR = "puzzles/cube3/solver/generated/pair";
|
|
8
|
+
export function loadNodePairTables() {
|
|
9
|
+
const dir = resolvePairDirectory();
|
|
10
|
+
if (dir === undefined) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
const manifest = JSON.parse(readFileSync(path.join(dir, "phase2-pair-manifest.json"), "utf8"));
|
|
14
|
+
const readBin = (file, expectedBytes, expectedChecksum) => {
|
|
15
|
+
const buffer = readFileSync(path.join(dir, file));
|
|
16
|
+
if (buffer.length !== expectedBytes) {
|
|
17
|
+
throw new Error(`Invalid byte size for ${file}.`);
|
|
18
|
+
}
|
|
19
|
+
const values = new Uint8Array(buffer);
|
|
20
|
+
const actual = checksumBytes(values);
|
|
21
|
+
if (actual !== expectedChecksum) {
|
|
22
|
+
throw new Error(`Checksum mismatch for ${file}: expected ${expectedChecksum}, received ${actual}.`);
|
|
23
|
+
}
|
|
24
|
+
return values;
|
|
25
|
+
};
|
|
26
|
+
const readU16 = (file, count, expectedChecksum) => {
|
|
27
|
+
const values = new Uint16Array(count);
|
|
28
|
+
const buffer = readBin(file, count * 2, expectedChecksum);
|
|
29
|
+
for (let index = 0; index < count; index += 1) {
|
|
30
|
+
values[index] = buffer[index * 2] | (buffer[index * 2 + 1] << 8);
|
|
31
|
+
}
|
|
32
|
+
return values;
|
|
33
|
+
};
|
|
34
|
+
const readU32 = (file, count, expectedChecksum) => {
|
|
35
|
+
const values = new Uint32Array(count);
|
|
36
|
+
const buffer = readBin(file, count * 4, expectedChecksum);
|
|
37
|
+
for (let index = 0; index < count; index += 1) {
|
|
38
|
+
values[index] =
|
|
39
|
+
buffer[index * 4] |
|
|
40
|
+
(buffer[index * 4 + 1] << 8) |
|
|
41
|
+
(buffer[index * 4 + 2] << 16) |
|
|
42
|
+
(buffer[index * 4 + 3] << 24);
|
|
43
|
+
}
|
|
44
|
+
return values;
|
|
45
|
+
};
|
|
46
|
+
const canonCpMin = readU16("phase2-pair-canoncpmin.bin", 40320, manifest.checksums.canonCpMin);
|
|
47
|
+
const canonTauStart = readU32("phase2-pair-canonctaustart.bin", 40321, manifest.checksums.canonTauStart);
|
|
48
|
+
const canonTaus = readBin("phase2-pair-canonctaus.bin", canonTauStart[40320], manifest.checksums.canonTaus);
|
|
49
|
+
const conjEp = readU16("phase2-pair-conjep.bin", 16 * 40320, manifest.checksums.conjEp);
|
|
50
|
+
const dist = readBin("phase2-pair-dist.bin", manifest.tableBytes, manifest.checksums.dist);
|
|
51
|
+
return {
|
|
52
|
+
denseMin: buildDenseMin(canonCpMin, manifest.nMinima),
|
|
53
|
+
canonCpMin,
|
|
54
|
+
canonTauStart,
|
|
55
|
+
canonTaus,
|
|
56
|
+
conjEp,
|
|
57
|
+
dist,
|
|
58
|
+
nMinima: manifest.nMinima,
|
|
59
|
+
maxDepth: manifest.maxDepth
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function resolvePairDirectory() {
|
|
63
|
+
const envDirectory = process.env.CUBINGCENTER_SCRAMBLER_TABLES_DIR;
|
|
64
|
+
if (envDirectory && existsSync(path.join(envDirectory, "pair", "phase2-pair-manifest.json"))) {
|
|
65
|
+
return path.join(envDirectory, "pair");
|
|
66
|
+
}
|
|
67
|
+
const cwd = process.cwd();
|
|
68
|
+
const candidates = [
|
|
69
|
+
path.join(cwd, "src", PAIR_DIR),
|
|
70
|
+
path.join(cwd, "dist", PAIR_DIR),
|
|
71
|
+
path.join(cwd, "dist-cjs", PAIR_DIR)
|
|
72
|
+
];
|
|
73
|
+
for (const candidate of candidates) {
|
|
74
|
+
if (existsSync(path.join(candidate, "phase2-pair-manifest.json"))) {
|
|
75
|
+
return candidate;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pair-table types shared by the node and browser loaders.
|
|
3
|
+
*/
|
|
4
|
+
export interface Cube3PairTables {
|
|
5
|
+
/** Dense index of the distinct cp-orbit minima (canonCpMin value -> 0..nMinima-1). */
|
|
6
|
+
denseMin: Uint16Array;
|
|
7
|
+
/** Canonical cp-orbit minimum per corner permutation (40320). */
|
|
8
|
+
canonCpMin: Uint16Array;
|
|
9
|
+
/** Offsets into canonTaus per corner permutation (40321). */
|
|
10
|
+
canonTauStart: Uint32Array;
|
|
11
|
+
/** The slice-16 group indices achieving each cp minimum. */
|
|
12
|
+
canonTaus: Uint8Array;
|
|
13
|
+
/** Conjugation transforms of the UD-edge permutation (16 * 40320). */
|
|
14
|
+
conjEp: Uint16Array;
|
|
15
|
+
/** 4-bit packed pair distances (nMinima * 40320 nibbles). */
|
|
16
|
+
dist: Uint8Array;
|
|
17
|
+
nMinima: number;
|
|
18
|
+
maxDepth: number;
|
|
19
|
+
}
|
|
20
|
+
export interface PairManifest {
|
|
21
|
+
nMinima: number;
|
|
22
|
+
tableBytes: number;
|
|
23
|
+
maxDepth: number;
|
|
24
|
+
checksums: {
|
|
25
|
+
dist: number;
|
|
26
|
+
canonCpMin: number;
|
|
27
|
+
canonTauStart: number;
|
|
28
|
+
canonTaus: number;
|
|
29
|
+
conjEp: number;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export declare function checksumBytes(values: Uint8Array): number;
|
|
33
|
+
/**
|
|
34
|
+
* Builds the dense class map (first-appearance order, matching exp3-gen).
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildDenseMin(canonCpMin: Uint16Array, expectedCount: number): Uint16Array;
|
|
37
|
+
//# sourceMappingURL=pair-tables-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pair-tables-types.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/pair-tables-types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,eAAe;IAC9B,sFAAsF;IACtF,QAAQ,EAAE,WAAW,CAAC;IACtB,iEAAiE;IACjE,UAAU,EAAE,WAAW,CAAC;IACxB,6DAA6D;IAC7D,aAAa,EAAE,WAAW,CAAC;IAC3B,4DAA4D;IAC5D,SAAS,EAAE,UAAU,CAAC;IACtB,sEAAsE;IACtE,MAAM,EAAE,WAAW,CAAC;IACpB,6DAA6D;IAC7D,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CASxD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,GAAG,WAAW,CAczF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pair-table types shared by the node and browser loaders.
|
|
3
|
+
*/
|
|
4
|
+
export function checksumBytes(values) {
|
|
5
|
+
let hash = 0x811c9dc5;
|
|
6
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
7
|
+
hash ^= values[index];
|
|
8
|
+
hash = Math.imul(hash, 0x01000193);
|
|
9
|
+
}
|
|
10
|
+
return hash >>> 0;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Builds the dense class map (first-appearance order, matching exp3-gen).
|
|
14
|
+
*/
|
|
15
|
+
export function buildDenseMin(canonCpMin, expectedCount) {
|
|
16
|
+
const denseMin = new Uint16Array(40320).fill(0xffff);
|
|
17
|
+
let next = 0;
|
|
18
|
+
for (let cp = 0; cp < 40320; cp += 1) {
|
|
19
|
+
const minCp = canonCpMin[cp];
|
|
20
|
+
if (denseMin[minCp] === 0xffff) {
|
|
21
|
+
denseMin[minCp] = next;
|
|
22
|
+
next += 1;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (next !== expectedCount) {
|
|
26
|
+
throw new Error(`Dense class count mismatch: ${next} != ${expectedCount}.`);
|
|
27
|
+
}
|
|
28
|
+
return denseMin;
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Combined (cp x uep) phase-2 pruning table (experiment:
|
|
3
|
+
* experiment/cube3-pair-pruning). The exact distance to (cp==0 && uep==0)
|
|
4
|
+
* with the slice permutation free, canonicalized under the slice-16
|
|
5
|
+
* conjugation group:
|
|
6
|
+
*
|
|
7
|
+
* index = denseMin[canonCpMin[cp]] * 40320 + uep*
|
|
8
|
+
* uep* = min over the cp-minimizer set (canonTaus) of conjEp[g][uep]
|
|
9
|
+
*
|
|
10
|
+
* The distance is ~3.5-4 moves tighter than max(cpsp, uepsp) (the plain
|
|
11
|
+
* phase-2 bound) and admissible for the joint phase-2 problem
|
|
12
|
+
* (d_pair <= d_joint), so it prunes the deep phase-2 searches
|
|
13
|
+
* exponentially. Measured in the native C++ repo (exp3): phase-2 drops
|
|
14
|
+
* from ~34k nodes to ~37 (990x) and the full scramble from ~5.7 ms to
|
|
15
|
+
* ~1.2 ms. Generated by the C++ tool `tools/exp3-gen`, ~1 min,
|
|
16
|
+
* deterministic, FNV-1a checksummed.
|
|
17
|
+
*
|
|
18
|
+
* The tables live in `puzzles/cube3/solver/generated/pair/` next to the
|
|
19
|
+
* standard solver tables.
|
|
20
|
+
*/
|
|
21
|
+
import type { Cube3PairTables } from "./pair-tables-types.js";
|
|
22
|
+
export declare function getCachedPairTables(): Cube3PairTables | undefined;
|
|
23
|
+
export declare function loadCube3PairTables(): Promise<Cube3PairTables | undefined>;
|
|
24
|
+
/**
|
|
25
|
+
* The pair distance of (cp, uep) — the exact distance to (cp==0 && uep==0)
|
|
26
|
+
* with the slice free. Admissible as a phase-2 lower bound.
|
|
27
|
+
*/
|
|
28
|
+
export declare function pairBound(tables: Cube3PairTables, cp: number, uep: number): number;
|
|
29
|
+
//# sourceMappingURL=pair-tables.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pair-tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/pair-tables.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAI9D,wBAAgB,mBAAmB,IAAI,eAAe,GAAG,SAAS,CAEjE;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAKhF;AAwBD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAelF"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Combined (cp x uep) phase-2 pruning table (experiment:
|
|
3
|
+
* experiment/cube3-pair-pruning). The exact distance to (cp==0 && uep==0)
|
|
4
|
+
* with the slice permutation free, canonicalized under the slice-16
|
|
5
|
+
* conjugation group:
|
|
6
|
+
*
|
|
7
|
+
* index = denseMin[canonCpMin[cp]] * 40320 + uep*
|
|
8
|
+
* uep* = min over the cp-minimizer set (canonTaus) of conjEp[g][uep]
|
|
9
|
+
*
|
|
10
|
+
* The distance is ~3.5-4 moves tighter than max(cpsp, uepsp) (the plain
|
|
11
|
+
* phase-2 bound) and admissible for the joint phase-2 problem
|
|
12
|
+
* (d_pair <= d_joint), so it prunes the deep phase-2 searches
|
|
13
|
+
* exponentially. Measured in the native C++ repo (exp3): phase-2 drops
|
|
14
|
+
* from ~34k nodes to ~37 (990x) and the full scramble from ~5.7 ms to
|
|
15
|
+
* ~1.2 ms. Generated by the C++ tool `tools/exp3-gen`, ~1 min,
|
|
16
|
+
* deterministic, FNV-1a checksummed.
|
|
17
|
+
*
|
|
18
|
+
* The tables live in `puzzles/cube3/solver/generated/pair/` next to the
|
|
19
|
+
* standard solver tables.
|
|
20
|
+
*/
|
|
21
|
+
let cached;
|
|
22
|
+
export function getCachedPairTables() {
|
|
23
|
+
return cached;
|
|
24
|
+
}
|
|
25
|
+
export async function loadCube3PairTables() {
|
|
26
|
+
if (cached === undefined) {
|
|
27
|
+
cached = await loadPairTables();
|
|
28
|
+
}
|
|
29
|
+
return cached;
|
|
30
|
+
}
|
|
31
|
+
async function loadPairTables() {
|
|
32
|
+
try {
|
|
33
|
+
if (typeof self !== "undefined") {
|
|
34
|
+
const loader = await import(
|
|
35
|
+
/* webpackIgnore: true */
|
|
36
|
+
/* @vite-ignore */
|
|
37
|
+
"./pair-browser-loader.js");
|
|
38
|
+
return await loader.loadBrowserPairTables();
|
|
39
|
+
}
|
|
40
|
+
const loader = await import(
|
|
41
|
+
/* webpackIgnore: true */
|
|
42
|
+
/* @vite-ignore */
|
|
43
|
+
"./pair-node-loader.js");
|
|
44
|
+
return loader.loadNodePairTables();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The pair distance of (cp, uep) — the exact distance to (cp==0 && uep==0)
|
|
52
|
+
* with the slice free. Admissible as a phase-2 lower bound.
|
|
53
|
+
*/
|
|
54
|
+
export function pairBound(tables, cp, uep) {
|
|
55
|
+
const minCp = tables.canonCpMin[cp];
|
|
56
|
+
const start = tables.canonTauStart[cp];
|
|
57
|
+
const end = tables.canonTauStart[cp + 1];
|
|
58
|
+
let bestUep = 0x7fffffff;
|
|
59
|
+
for (let index = start; index < end; index += 1) {
|
|
60
|
+
const g = tables.canonTaus[index];
|
|
61
|
+
const e = tables.conjEp[g * 40320 + uep];
|
|
62
|
+
if (e < bestUep) {
|
|
63
|
+
bestUep = e;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const pairIndex = tables.denseMin[minCp] * 40320 + bestUep;
|
|
67
|
+
const nibble = tables.dist[pairIndex >> 1];
|
|
68
|
+
return (nibble >> ((pairIndex & 1) << 2)) & 0x0f;
|
|
69
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Cube3Move, Cube3State } from "../types.js";
|
|
2
|
-
import type { Cube3Phase1Tables } from "./tables.js";
|
|
2
|
+
import type { Cube3JointTables, Cube3Phase1Tables } from "./tables.js";
|
|
3
3
|
export interface Cube3Phase1Coordinates {
|
|
4
4
|
cornerOrientation: number;
|
|
5
5
|
edgeOrientation: number;
|
|
@@ -9,6 +9,8 @@ export interface Cube3Phase1Options {
|
|
|
9
9
|
maxDepth?: number;
|
|
10
10
|
timeoutMs?: number;
|
|
11
11
|
tables?: Cube3Phase1Tables;
|
|
12
|
+
/** Phase-1 joint tables (ud x co / ud x eo), experimental. */
|
|
13
|
+
jointTables?: Cube3JointTables | undefined;
|
|
12
14
|
}
|
|
13
15
|
export interface Cube3Phase1Result {
|
|
14
16
|
moves: Cube3Move[];
|
|
@@ -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,iBAAiB,EAAE,MAAM,aAAa,CAAC;
|
|
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;AA+BD,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAkF5B;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"}
|
|
@@ -24,11 +24,13 @@ const PHASE1_VALID_MOVES = Array.from({ length: 7 }, (_, prevPlusOne) => Array.f
|
|
|
24
24
|
}));
|
|
25
25
|
export async function solveCube3Phase1(state, options = {}) {
|
|
26
26
|
const tables = options.tables ?? await loadCube3Phase1Tables();
|
|
27
|
+
const jointTables = options.jointTables ?? tables.jointTables;
|
|
27
28
|
const maxDepth = options.maxDepth ?? 12;
|
|
28
29
|
const start = encodeCube3Phase1Coordinates(state);
|
|
29
30
|
const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
|
|
30
31
|
const context = {
|
|
31
32
|
tables,
|
|
33
|
+
jointTables,
|
|
32
34
|
maxDepth,
|
|
33
35
|
deadline,
|
|
34
36
|
visitedNodes: 0,
|
|
@@ -48,7 +50,19 @@ export async function solveCube3Phase1(state, options = {}) {
|
|
|
48
50
|
const combinedIndex = (start.cornerOrientation << 11) + start.edgeOrientation;
|
|
49
51
|
const coeo = (coeoTable[combinedIndex >> 1] >> ((combinedIndex & 1) << 2)) & 0x0f;
|
|
50
52
|
const udSliceDist = (udPruningTable[start.udSlice >> 1] >> ((start.udSlice & 1) << 2)) & 0x0f;
|
|
51
|
-
|
|
53
|
+
let minDepth = coeo > udSliceDist ? coeo : udSliceDist;
|
|
54
|
+
if (jointTables) {
|
|
55
|
+
const jc = (jointTables.jointUdCo[(start.udSlice * 2187 + start.cornerOrientation) >> 1] >>
|
|
56
|
+
(((start.udSlice * 2187 + start.cornerOrientation) & 1) << 2)) & 0x0f;
|
|
57
|
+
if (jc > minDepth) {
|
|
58
|
+
minDepth = jc;
|
|
59
|
+
}
|
|
60
|
+
const je = (jointTables.jointUdEo[(start.udSlice * 2048 + start.edgeOrientation) >> 1] >>
|
|
61
|
+
(((start.udSlice * 2048 + start.edgeOrientation) & 1) << 2)) & 0x0f;
|
|
62
|
+
if (je > minDepth) {
|
|
63
|
+
minDepth = je;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
52
66
|
for (let depth = minDepth; depth <= maxDepth; depth += 1) {
|
|
53
67
|
const path = [];
|
|
54
68
|
const solved = searchPhase1(start.cornerOrientation, start.edgeOrientation, start.udSlice, -1, -1, depth, path, context);
|
|
@@ -120,10 +134,26 @@ function searchPhase1(cornerOrientation, edgeOrientation, udSlice, previousFace,
|
|
|
120
134
|
const combinedIndex = (nextCornerOrientation << 11) + nextEdgeOrientation;
|
|
121
135
|
const coeo = (coeoTable[combinedIndex >> 1] >> ((combinedIndex & 1) << 2)) & 0x0f;
|
|
122
136
|
const udSliceDist = (udPruningTable[nextUdSlice >> 1] >> ((nextUdSlice & 1) << 2)) & 0x0f;
|
|
123
|
-
|
|
137
|
+
let nextLowerBound = coeo > udSliceDist ? coeo : udSliceDist;
|
|
124
138
|
if (nextLowerBound > remainingDepth - 1) {
|
|
125
139
|
continue;
|
|
126
140
|
}
|
|
141
|
+
const jointTables = context.jointTables;
|
|
142
|
+
if (jointTables) {
|
|
143
|
+
const udCoIndex = nextUdSlice * 2187 + nextCornerOrientation;
|
|
144
|
+
const jc = (jointTables.jointUdCo[udCoIndex >> 1] >> ((udCoIndex & 1) << 2)) & 0x0f;
|
|
145
|
+
if (jc > nextLowerBound) {
|
|
146
|
+
nextLowerBound = jc;
|
|
147
|
+
}
|
|
148
|
+
const udEoIndex = nextUdSlice * 2048 + nextEdgeOrientation;
|
|
149
|
+
const je = (jointTables.jointUdEo[udEoIndex >> 1] >> ((udEoIndex & 1) << 2)) & 0x0f;
|
|
150
|
+
if (je > nextLowerBound) {
|
|
151
|
+
nextLowerBound = je;
|
|
152
|
+
}
|
|
153
|
+
if (nextLowerBound > remainingDepth - 1) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
127
157
|
const face = PHASE1_MOVE_FACES[moveIndex];
|
|
128
158
|
path.push(context.tables.phase1Moves[moveIndex]);
|
|
129
159
|
if (searchPhase1(nextCornerOrientation, nextEdgeOrientation, nextUdSlice, face, previousFace, remainingDepth - 1, path, context)) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Cube3Move, Cube3State } from "../types.js";
|
|
2
2
|
import type { Cube3Phase1Options, Cube3Phase1Result } from "./phase1.js";
|
|
3
3
|
import type { Cube3Phase2Tables } from "./tables.js";
|
|
4
|
+
import type { Cube3PairTables } from "./pair-tables-types.js";
|
|
4
5
|
export interface Cube3Phase2Coordinates {
|
|
5
6
|
cornerPermutation: number;
|
|
6
7
|
udEdgePermutation: number;
|
|
@@ -10,6 +11,8 @@ export interface Cube3Phase2Options {
|
|
|
10
11
|
maxDepth?: number;
|
|
11
12
|
timeoutMs?: number;
|
|
12
13
|
tables?: Cube3Phase2Tables;
|
|
14
|
+
/** Combined (cp x uep) pair-distance pruning tables (experimental). */
|
|
15
|
+
pairTables?: Cube3PairTables | undefined;
|
|
13
16
|
previousFace?: number;
|
|
14
17
|
previousPreviousFace?: number;
|
|
15
18
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phase2.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/phase2.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAczD,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"phase2.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/phase2.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAczD,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAarD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,uEAAuE;IACvE,UAAU,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,sBAAsB,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,MAAM,CAAC,EAAE,kBAAkB,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;gBAElB,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;CAKlD;AA8BD,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAuF5B;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CA+B3B;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,UAAU,GAAG,sBAAsB,CAMtF;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,sBAAsB,GAAG,OAAO,CAQvF;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,SAAS,SAAS,EAAE,GAC1B,UAAU,CAEZ"}
|