@cubingcenter/scrambler 0.6.3 → 0.7.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 +2 -0
- package/dist/puzzles/cube2/solver/search.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/search.js +3 -3
- package/dist/puzzles/cube2/solver/tables.d.ts +6 -0
- package/dist/puzzles/cube2/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube2/solver/tables.js +16 -2
- package/dist/puzzles/cube3/index.d.ts +1 -0
- package/dist/puzzles/cube3/index.d.ts.map +1 -1
- package/dist/puzzles/cube3/moves.d.ts.map +1 -1
- package/dist/puzzles/cube3/moves.js +104 -18
- package/dist/puzzles/cube3/scrambler.d.ts +16 -1
- package/dist/puzzles/cube3/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube3/scrambler.js +1 -1
- package/dist/puzzles/cube3/solver/browser-loader.js +6 -2
- package/dist/puzzles/cube3/solver/index.d.ts +5 -3
- package/dist/puzzles/cube3/solver/index.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/index.js +2 -1
- package/dist/puzzles/cube3/solver/node-loader.js +9 -2
- package/dist/puzzles/cube3/solver/phase1.d.ts +18 -0
- package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase1.js +132 -14
- package/dist/puzzles/cube3/solver/phase2.d.ts +47 -0
- package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/phase2.js +236 -66
- package/dist/puzzles/cube3/solver/search-utils.d.ts +1 -1
- package/dist/puzzles/cube3/solver/search-utils.d.ts.map +1 -1
- package/dist/puzzles/cube3/solver/symmetry.d.ts +24 -0
- package/dist/puzzles/cube3/solver/symmetry.d.ts.map +1 -0
- package/dist/puzzles/cube3/solver/symmetry.js +74 -0
- package/dist/puzzles/cube3/solver/tables.d.ts +6 -1
- package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/cube3/validation.d.ts.map +1 -1
- package/dist/puzzles/cube3/validation.js +15 -7
- package/dist/puzzles/cube4/index.d.ts +3 -2
- package/dist/puzzles/cube4/index.d.ts.map +1 -1
- package/dist/puzzles/cube4/index.js +2 -2
- package/dist/puzzles/cube4/moves.d.ts +5 -0
- package/dist/puzzles/cube4/moves.d.ts.map +1 -1
- package/dist/puzzles/cube4/moves.js +120 -11
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts +3 -0
- package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/browser-loader.js +34 -3
- package/dist/puzzles/cube4/reduction/center2.d.ts +20 -3
- package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/center2.js +53 -9
- package/dist/puzzles/cube4/reduction/edge3.d.ts +4 -0
- package/dist/puzzles/cube4/reduction/edge3.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/edge3.js +23 -3
- package/dist/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
- package/dist/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
- package/dist/puzzles/cube4/reduction/generated/hc-manifest.json +38 -0
- package/dist/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts +50 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.d.ts.map +1 -0
- package/dist/puzzles/cube4/reduction/hc-pdb.js +202 -0
- package/dist/puzzles/cube4/reduction/node-loader.d.ts +5 -0
- package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/node-loader.js +48 -8
- package/dist/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts +111 -5
- package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/phase3-search.js +341 -22
- package/dist/puzzles/cube4/reduction/solve.d.ts +112 -1
- package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -1
- package/dist/puzzles/cube4/reduction/solve.js +920 -181
- package/dist/puzzles/cube4/scrambler.d.ts +105 -4
- package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
- package/dist/puzzles/cube4/scrambler.js +216 -166
- package/dist/puzzles/cube4/state.d.ts.map +1 -1
- package/dist/puzzles/cube4/state.js +4 -1
- package/dist/puzzles/pyraminx/solver/search.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/search.js +3 -6
- package/dist/puzzles/pyraminx/solver/tables.d.ts +6 -0
- package/dist/puzzles/pyraminx/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/pyraminx/solver/tables.js +17 -0
- package/dist/puzzles/skewb/solver/search.js +3 -3
- package/dist/puzzles/skewb/solver/tables.d.ts +6 -0
- package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -1
- package/dist/puzzles/skewb/solver/tables.js +17 -0
- package/dist-cjs/puzzles/cube2/solver/search.js +2 -2
- package/dist-cjs/puzzles/cube2/solver/tables.js +17 -3
- package/dist-cjs/puzzles/cube3/moves.js +104 -18
- package/dist-cjs/puzzles/cube3/scrambler.js +1 -1
- package/dist-cjs/puzzles/cube3/solver/browser-loader.js +6 -2
- package/dist-cjs/puzzles/cube3/solver/index.js +8 -1
- package/dist-cjs/puzzles/cube3/solver/node-loader.js +9 -2
- package/dist-cjs/puzzles/cube3/solver/phase1.js +133 -14
- package/dist-cjs/puzzles/cube3/solver/phase2.js +233 -63
- package/dist-cjs/puzzles/cube3/solver/symmetry.js +80 -0
- package/dist-cjs/puzzles/cube3/validation.js +15 -7
- package/dist-cjs/puzzles/cube4/index.js +3 -1
- package/dist-cjs/puzzles/cube4/moves.js +121 -11
- package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +34 -3
- package/dist-cjs/puzzles/cube4/reduction/center2.js +54 -9
- package/dist-cjs/puzzles/cube4/reduction/edge3.js +24 -3
- package/dist-cjs/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-manifest.json +38 -0
- package/dist-cjs/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
- package/dist-cjs/puzzles/cube4/reduction/hc-pdb.js +213 -0
- package/dist-cjs/puzzles/cube4/reduction/node-loader.js +48 -8
- package/dist-cjs/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
- package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +343 -23
- package/dist-cjs/puzzles/cube4/reduction/solve.js +918 -173
- package/dist-cjs/puzzles/cube4/scrambler.js +214 -163
- package/dist-cjs/puzzles/cube4/state.js +4 -1
- package/dist-cjs/puzzles/pyraminx/solver/search.js +2 -5
- package/dist-cjs/puzzles/pyraminx/solver/tables.js +18 -1
- package/dist-cjs/puzzles/skewb/solver/search.js +2 -2
- package/dist-cjs/puzzles/skewb/solver/tables.js +18 -1
- package/package.json +14 -5
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
* and all 12 wing pairs paired (sym-index == 0), using the 17-move exploration set.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.FastPhase3Solver = exports.skipAxis3 = exports.ckmv3 = exports.SEARCH_MOVES = void 0;
|
|
9
|
+
exports.proofStats = exports.FastPhase3Solver = exports.skipAxis3 = exports.ckmv3 = exports.SEARCH_MOVES = void 0;
|
|
10
|
+
exports.resetProofStats = resetProofStats;
|
|
10
11
|
exports.solvePhase3 = solvePhase3;
|
|
11
12
|
const center3_js_1 = require("./center3.js");
|
|
12
13
|
const edge3_js_1 = require("./edge3.js");
|
|
14
|
+
const hc_pdb_js_1 = require("./hc-pdb.js");
|
|
13
15
|
/** The 17 exploration moves (the reference's m < 17 bound). */
|
|
14
16
|
exports.SEARCH_MOVES = center3_js_1.CENTER3_MOVES.slice(0, 17);
|
|
15
17
|
// Commutation / move-cancellation pruning tables
|
|
@@ -37,81 +39,399 @@ for (let i = 0; i < 20; i++) {
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
const F_SLOT_MAP = [
|
|
43
|
+
[4, 5, 2, 3, 1, 0, 6, 7, 8, 9, 10, 11],
|
|
44
|
+
[1, 0, 2, 3, 5, 4, 6, 7, 8, 9, 10, 11],
|
|
45
|
+
[5, 4, 2, 3, 0, 1, 6, 7, 8, 9, 10, 11],
|
|
46
|
+
[0, 1, 2, 3, 4, 6, 5, 7, 8, 9, 11, 10],
|
|
47
|
+
[11, 1, 2, 8, 4, 5, 6, 7, 0, 9, 10, 3],
|
|
48
|
+
[3, 1, 2, 0, 4, 5, 6, 7, 11, 9, 10, 8],
|
|
49
|
+
[8, 1, 2, 11, 4, 5, 6, 7, 3, 9, 10, 0],
|
|
50
|
+
[0, 1, 7, 6, 4, 5, 2, 3, 8, 9, 10, 11],
|
|
51
|
+
[0, 1, 3, 2, 4, 5, 7, 6, 8, 9, 10, 11],
|
|
52
|
+
[0, 1, 6, 7, 4, 5, 3, 2, 8, 9, 10, 11],
|
|
53
|
+
[0, 1, 2, 3, 7, 5, 6, 4, 9, 8, 10, 11],
|
|
54
|
+
[0, 9, 10, 3, 4, 5, 6, 7, 8, 2, 1, 11],
|
|
55
|
+
[0, 2, 1, 3, 4, 5, 6, 7, 8, 10, 9, 11],
|
|
56
|
+
[0, 10, 9, 3, 4, 5, 6, 7, 8, 1, 2, 11],
|
|
57
|
+
[1, 0, 2, 3, 5, 4, 6, 7, 8, 11, 10, 9],
|
|
58
|
+
[0, 3, 2, 1, 4, 6, 5, 7, 8, 9, 11, 10],
|
|
59
|
+
[3, 1, 2, 0, 4, 7, 6, 5, 11, 9, 10, 8]
|
|
60
|
+
];
|
|
61
|
+
const H_SLOT_MAP = [
|
|
62
|
+
[4, 5, 2, 3, 1, 0, 6, 7, 8, 9, 10, 11],
|
|
63
|
+
[1, 0, 2, 3, 5, 4, 6, 7, 8, 9, 10, 11],
|
|
64
|
+
[5, 4, 2, 3, 0, 1, 6, 7, 8, 9, 10, 11],
|
|
65
|
+
[0, 1, 2, 3, 4, 6, 5, 7, 8, 9, 11, 10],
|
|
66
|
+
[11, 1, 2, 8, 4, 5, 6, 7, 0, 9, 10, 3],
|
|
67
|
+
[3, 1, 2, 0, 4, 5, 6, 7, 11, 9, 10, 8],
|
|
68
|
+
[8, 1, 2, 11, 4, 5, 6, 7, 3, 9, 10, 0],
|
|
69
|
+
[0, 1, 7, 6, 4, 5, 2, 3, 8, 9, 10, 11],
|
|
70
|
+
[0, 1, 3, 2, 4, 5, 7, 6, 8, 9, 10, 11],
|
|
71
|
+
[0, 1, 6, 7, 4, 5, 3, 2, 8, 9, 10, 11],
|
|
72
|
+
[0, 1, 2, 3, 7, 5, 6, 4, 9, 8, 10, 11],
|
|
73
|
+
[0, 9, 10, 3, 4, 5, 6, 7, 8, 2, 1, 11],
|
|
74
|
+
[0, 2, 1, 3, 4, 5, 6, 7, 8, 10, 9, 11],
|
|
75
|
+
[0, 10, 9, 3, 4, 5, 6, 7, 8, 1, 2, 11],
|
|
76
|
+
[1, 0, 2, 3, 5, 4, 6, 7, 10, 9, 8, 11],
|
|
77
|
+
[2, 1, 0, 3, 4, 6, 5, 7, 8, 9, 11, 10],
|
|
78
|
+
[3, 1, 2, 0, 6, 5, 4, 7, 11, 9, 10, 8]
|
|
79
|
+
];
|
|
43
80
|
class FastPhase3Solver {
|
|
44
81
|
tempep;
|
|
45
82
|
moveLog;
|
|
46
83
|
sym;
|
|
84
|
+
/** Transposition Table size: 2^21 = 2,097,152 entries (24 MB in RAM).
|
|
85
|
+
* NOTE: doubling to 4M was tried and reverted (-0.07% nodes on the
|
|
86
|
+
* corpus — the 2M working set already captures the hit locality). */
|
|
87
|
+
static TT_SIZE = 1 << 21;
|
|
88
|
+
static TT_MASK = FastPhase3Solver.TT_SIZE - 1;
|
|
89
|
+
static tt = new Uint32Array(FastPhase3Solver.TT_SIZE * 3);
|
|
90
|
+
static epoch = 1;
|
|
91
|
+
/**
|
|
92
|
+
* Per-slot exact-length-call tag (8 MB transient). TT pruning stays
|
|
93
|
+
* epoch-scoped (unchanged traversal), but threshold proofs only trust
|
|
94
|
+
* same-call hits: a hit from another call may have validated under a
|
|
95
|
+
* different candidate, so it still prunes yet marks the proof dirty.
|
|
96
|
+
*/
|
|
97
|
+
static ttGen = new Uint32Array(FastPhase3Solver.TT_SIZE);
|
|
98
|
+
static callSeq = 1;
|
|
99
|
+
callId = 0;
|
|
100
|
+
/** Reset the per-attempt cumulative node counter. */
|
|
101
|
+
resetEpochNodes() {
|
|
102
|
+
this.epochNodes = 0;
|
|
103
|
+
}
|
|
104
|
+
static nextEpoch() {
|
|
105
|
+
FastPhase3Solver.epoch = (FastPhase3Solver.epoch + 1) & 0xffff;
|
|
106
|
+
if (FastPhase3Solver.epoch === 0) {
|
|
107
|
+
FastPhase3Solver.tt.fill(0);
|
|
108
|
+
FastPhase3Solver.ttGen.fill(0);
|
|
109
|
+
FastPhase3Solver.callSeq = 1;
|
|
110
|
+
FastPhase3Solver.epoch = 1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Fresh per-call id for TT-generation tagging (epoch wrap resets it). */
|
|
114
|
+
static nextCallId() {
|
|
115
|
+
const id = FastPhase3Solver.callSeq++;
|
|
116
|
+
if (FastPhase3Solver.callSeq > 0xfffffffe) {
|
|
117
|
+
FastPhase3Solver.ttGen.fill(0);
|
|
118
|
+
FastPhase3Solver.callSeq = 1;
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
return id;
|
|
122
|
+
}
|
|
47
123
|
constructor() {
|
|
48
124
|
this.tempep = Array.from({ length: 30 }, () => new Uint8Array(12));
|
|
49
125
|
this.moveLog = new Array(30);
|
|
50
126
|
this.sym = (0, edge3_js_1.getSymTables)();
|
|
51
127
|
}
|
|
128
|
+
nodeCount = 0;
|
|
129
|
+
/** Cumulative nodes across calls (reset per attempt via resetEpochNodes). */
|
|
130
|
+
epochNodes = 0;
|
|
131
|
+
/**
|
|
132
|
+
* Proof bookkeeping for IDA* threshold propagation (reset per exact-length
|
|
133
|
+
* call). When a length-L search exhausts its tree WITHOUT hitting any of
|
|
134
|
+
* these flags, no (center, edge) solution exists at any length below
|
|
135
|
+
* `minExcess`, so the caller may skip straight to it:
|
|
136
|
+
* - `capped`: the node budget aborted the search (bounds unknown).
|
|
137
|
+
* - `limitHit`: goal enumeration hit its cap (tree not exhausted).
|
|
138
|
+
* - `sawGoal`: a goal was reached (even if the validator rejected it —
|
|
139
|
+
* a nearby length may still validate, so only step L+1).
|
|
140
|
+
* - `ttTouched`: a transposition-table hit from ANOTHER exact-length call
|
|
141
|
+
* skipped a subtree (same-call hits replay this call's own exhausted
|
|
142
|
+
* subtree and stay clean; other calls may validate under a different
|
|
143
|
+
* candidate, so their stored proof does not transfer).
|
|
144
|
+
* Bound-based axis skips need no flag: the skipped siblings are
|
|
145
|
+
* same-face turns, so noteExcess records their triangle-inequality bound.
|
|
146
|
+
*/
|
|
147
|
+
capped = false;
|
|
148
|
+
limitHit = false;
|
|
149
|
+
sawGoal = false;
|
|
150
|
+
ttTouched = false;
|
|
151
|
+
minExcess = 0;
|
|
52
152
|
getPrun(distances, idx) {
|
|
53
|
-
|
|
54
|
-
|
|
153
|
+
return distances.length >= 31006080
|
|
154
|
+
? distances[idx]
|
|
155
|
+
: (distances[idx >> 1] >> ((idx & 1) * 4)) & 0x0f;
|
|
55
156
|
}
|
|
56
157
|
/**
|
|
57
|
-
* Recursive IDA* worker on table transitions.
|
|
158
|
+
* Recursive IDA* worker on table transitions with Transposition Table pruning.
|
|
58
159
|
*/
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
160
|
+
search(eplast, ct, prun, maxl, lm, depth, center3Tables, edge3Distances, maxNodes = 15000, collect, hc, hcCoord = -1, c3PairDist) {
|
|
161
|
+
this.nodeCount++;
|
|
162
|
+
this.epochNodes++;
|
|
163
|
+
if (this.nodeCount > maxNodes) {
|
|
164
|
+
this.capped = true;
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
if (maxl === 0) {
|
|
168
|
+
if (!collect)
|
|
169
|
+
return true;
|
|
170
|
+
this.sawGoal = true;
|
|
171
|
+
collect.examined++;
|
|
172
|
+
const moves = this.moveLog.slice(0, depth).map((m) => exports.SEARCH_MOVES[m]);
|
|
173
|
+
if (collect.validate(moves))
|
|
174
|
+
return true;
|
|
175
|
+
if (collect.examined >= collect.limit)
|
|
176
|
+
this.limitHit = true;
|
|
177
|
+
return collect.examined >= collect.limit;
|
|
178
|
+
}
|
|
179
|
+
const sym = this.sym;
|
|
180
|
+
const symMvrot = sym.mvrot;
|
|
181
|
+
const symMvroto = sym.mvroto;
|
|
64
182
|
const ep = this.tempep[depth];
|
|
65
183
|
if (lm !== 20) {
|
|
66
184
|
const movoBase = (lm << 3) * 12;
|
|
67
|
-
const movBase = (lm << 3) * 12;
|
|
68
185
|
for (let i = 0; i < 12; i++) {
|
|
69
|
-
ep[i] =
|
|
186
|
+
ep[i] = symMvroto[movoBase + eplast[symMvrot[movoBase + i]]];
|
|
70
187
|
}
|
|
71
188
|
}
|
|
72
189
|
else {
|
|
73
190
|
ep.set(eplast);
|
|
74
191
|
}
|
|
192
|
+
// TT lookup (for depths >= 2 where TT hit prunes multi-move subtrees)
|
|
193
|
+
const keyHigh = ((ct << 16) | (ep[8] | (ep[9] << 4) | (ep[10] << 8) | (ep[11] << 12))) >>> 0;
|
|
194
|
+
const keyLow = (ep[0] | (ep[1] << 4) | (ep[2] << 8) | (ep[3] << 12) |
|
|
195
|
+
(ep[4] << 16) | (ep[5] << 20) | (ep[6] << 24) | (ep[7] << 28)) >>> 0;
|
|
196
|
+
const slot = (((keyLow ^ Math.imul(keyHigh, 0x9e3779b9)) & FastPhase3Solver.TT_MASK) * 3) >>> 0;
|
|
197
|
+
if (maxl >= 2) {
|
|
198
|
+
const storedHigh = FastPhase3Solver.tt[slot];
|
|
199
|
+
const storedLow = FastPhase3Solver.tt[slot + 1];
|
|
200
|
+
const storedInfo = FastPhase3Solver.tt[slot + 2];
|
|
201
|
+
if (storedHigh === keyHigh && storedLow === keyLow) {
|
|
202
|
+
const storedEpoch = storedInfo >>> 16;
|
|
203
|
+
const storedLm = (storedInfo >> 8) & 0xff;
|
|
204
|
+
const storedDepth = storedInfo & 0xff;
|
|
205
|
+
if (storedEpoch === FastPhase3Solver.epoch && storedDepth >= maxl && storedLm === lm) {
|
|
206
|
+
// Same-call hits replay this call's own exhausted subtree (proof
|
|
207
|
+
// stays clean); cross-call hits still prune but dirty the proof.
|
|
208
|
+
// NOTE: ignoring cross-call entries was tried and reverted — they
|
|
209
|
+
// carry most of the pruning power (3.82s vs 2.39s on the corpus).
|
|
210
|
+
if (FastPhase3Solver.ttGen[(slot / 3) | 0] !== this.callId)
|
|
211
|
+
this.ttTouched = true;
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const c3Move = center3Tables.moveTable;
|
|
217
|
+
const c3Prun = center3Tables.pruning;
|
|
218
|
+
const c3Row = ct * center3_js_1.CENTER3_MOVES.length;
|
|
219
|
+
const sym2Raw = sym.sym2Raw;
|
|
220
|
+
const sym2Mask = sym.sym2Mask;
|
|
221
|
+
const raw2Sym = sym.raw2Sym;
|
|
222
|
+
const hasHc = hc !== undefined && hcCoord >= 0 && hc.moves.length > 0;
|
|
223
|
+
const hcMoves = hasHc ? hc.moves : null;
|
|
224
|
+
const hcDist = hasHc ? hc.distances : null;
|
|
225
|
+
const hcRow = hasHc ? hcCoord * hc_pdb_js_1.HC_PDB_MOVES.length : 0;
|
|
226
|
+
// Byte-expanded (tests/generators) vs nibble-packed (loaders) distances.
|
|
227
|
+
const edgeUnpacked = edge3Distances.length >= 31006080;
|
|
75
228
|
for (let m = 0; m < 17; m++) {
|
|
76
229
|
if (exports.ckmv3[lm][m]) {
|
|
77
230
|
m = exports.skipAxis3[m];
|
|
78
231
|
continue;
|
|
79
232
|
}
|
|
80
|
-
const nc =
|
|
233
|
+
const nc = c3Move[c3Row + m];
|
|
81
234
|
if (nc < 0)
|
|
82
235
|
continue;
|
|
83
|
-
const prun1 =
|
|
236
|
+
const prun1 = c3Prun[nc];
|
|
84
237
|
if (prun1 >= maxl) {
|
|
238
|
+
this.noteExcess(depth, prun1, prun1 > maxl && m < 14);
|
|
85
239
|
if (prun1 > maxl && m < 14)
|
|
86
240
|
m = exports.skipAxis3[m];
|
|
87
241
|
continue;
|
|
88
242
|
}
|
|
89
|
-
|
|
90
|
-
|
|
243
|
+
let nhc = -1;
|
|
244
|
+
if (hasHc) {
|
|
245
|
+
nhc = hcMoves[hcRow + m];
|
|
246
|
+
if (nhc >= 0) {
|
|
247
|
+
const prunh = hcDist[nhc];
|
|
248
|
+
if (prunh >= maxl) {
|
|
249
|
+
this.noteExcess(depth, prunh, prunh > maxl && m < 14);
|
|
250
|
+
if (prunh > maxl && m < 14)
|
|
251
|
+
m = exports.skipAxis3[m];
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (c3PairDist && depth === 0) {
|
|
257
|
+
const c3Base = nc * 144;
|
|
258
|
+
const fSlot = F_SLOT_MAP[m];
|
|
259
|
+
const hSlot = H_SLOT_MAP[m];
|
|
260
|
+
let prunPair = 0;
|
|
261
|
+
for (let s = 0; s < 12; s++) {
|
|
262
|
+
const idx = c3Base + fSlot[s] * 12 + hSlot[ep[s]];
|
|
263
|
+
const d = (idx & 1) === 0 ? (c3PairDist[idx >> 1] & 0x0f) : (c3PairDist[idx >> 1] >> 4);
|
|
264
|
+
if (d >= maxl) {
|
|
265
|
+
prunPair = d;
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
if (d > prunPair)
|
|
269
|
+
prunPair = d;
|
|
270
|
+
}
|
|
271
|
+
if (prunPair >= maxl) {
|
|
272
|
+
this.noteExcess(depth, prunPair, prunPair > maxl && m < 14);
|
|
273
|
+
if (prunPair > maxl && m < 14)
|
|
274
|
+
m = exports.skipAxis3[m];
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const symIdx = (0, edge3_js_1.getMvSymFrom)(ep, ep, m, sym2Raw, sym2Mask, raw2Sym, symMvrot, symMvroto);
|
|
279
|
+
// Format-aware read: loaders keep the nibble-packed table (15.5MB);
|
|
280
|
+
// tests and generators may pass byte-expanded buffers instead.
|
|
281
|
+
const eIdx = symIdx >> 3;
|
|
282
|
+
const prunx = edgeUnpacked
|
|
283
|
+
? edge3Distances[eIdx]
|
|
284
|
+
: (edge3Distances[eIdx >> 1] >> ((eIdx & 1) * 4)) & 0x0f;
|
|
91
285
|
if (prunx >= maxl) {
|
|
286
|
+
this.noteExcess(depth, prunx, prunx > maxl && m < 14);
|
|
92
287
|
if (prunx > maxl && m < 14)
|
|
93
288
|
m = exports.skipAxis3[m];
|
|
94
289
|
continue;
|
|
95
290
|
}
|
|
96
|
-
|
|
97
|
-
|
|
291
|
+
this.moveLog[depth] = m;
|
|
292
|
+
if (this.search(ep, nc, prunx, maxl - 1, m, depth + 1, center3Tables, edge3Distances, maxNodes, collect, hc, nhc, c3PairDist)) {
|
|
98
293
|
return true;
|
|
99
294
|
}
|
|
100
295
|
}
|
|
296
|
+
// Record failure in TT if node didn't abort due to node cap
|
|
297
|
+
if (this.nodeCount <= maxNodes && maxl >= 2) {
|
|
298
|
+
FastPhase3Solver.tt[slot] = keyHigh;
|
|
299
|
+
FastPhase3Solver.tt[slot + 1] = keyLow;
|
|
300
|
+
FastPhase3Solver.tt[slot + 2] = (FastPhase3Solver.epoch << 16) | (lm << 8) | maxl;
|
|
301
|
+
FastPhase3Solver.ttGen[(slot / 3) | 0] = this.callId;
|
|
302
|
+
}
|
|
101
303
|
return false;
|
|
102
304
|
}
|
|
103
305
|
/**
|
|
104
|
-
*
|
|
306
|
+
* Record an IDA* bound prune: the child at `depth + 1` needs at least
|
|
307
|
+
* `childBound` more moves, so no solution exists through this branch
|
|
308
|
+
* below root threshold `depth + 1 + childBound`. When a bound-based axis
|
|
309
|
+
* skip also fires, the skipped siblings are same-face turns (verified:
|
|
310
|
+
* every non-empty skipAxis3 run under m < 14 is a same-base triple),
|
|
311
|
+
* hence at most 1 legal move apart — their bound is at least
|
|
312
|
+
* `childBound - 1` by the triangle inequality on the exact PDBs, so the
|
|
313
|
+
* recorded excess `depth + childBound` stays a true lower bound.
|
|
105
314
|
*/
|
|
106
|
-
|
|
315
|
+
noteExcess(depth, childBound, skippedSiblings) {
|
|
316
|
+
const excess = skippedSiblings ? depth + childBound : depth + 1 + childBound;
|
|
317
|
+
if (excess < this.minExcess)
|
|
318
|
+
this.minExcess = excess;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Search for a phase-3 solution with exact target length (first found).
|
|
322
|
+
*/
|
|
323
|
+
solveLength(edge, ct3, edist, length3, center3Tables, edge3Distances, maxNodes = 15000, hc, hcCoord = -1, c3PairDist) {
|
|
107
324
|
this.tempep[0].set(edge);
|
|
108
|
-
|
|
325
|
+
this.nodeCount = 0;
|
|
326
|
+
if (this.search(this.tempep[0], ct3, edist, length3, 20, 0, center3Tables, edge3Distances, maxNodes, undefined, hc, hcCoord, c3PairDist)) {
|
|
109
327
|
return this.moveLog.slice(0, length3).map((m) => exports.SEARCH_MOVES[m]);
|
|
110
328
|
}
|
|
111
329
|
return null;
|
|
112
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Search for a phase-3 solution with exact target length, accepting the
|
|
333
|
+
* first goal sequence that passes `validate` (up to `limit` examined,
|
|
334
|
+
* sharing `maxNodes`). With a validator that enforces the downstream
|
|
335
|
+
* requirements (reduction + standard orientation + 3x3 conversion), hard
|
|
336
|
+
* states whose first-found solutions are unusable get more draws from
|
|
337
|
+
* the same budget instead of an immediate miss.
|
|
338
|
+
*/
|
|
339
|
+
solveLengthValid(edge, ct3, edist, length3, center3Tables, edge3Distances, maxNodes = 15000, limit = 16, validate, hc, hcCoord = -1, c3PairDist) {
|
|
340
|
+
return this.solveLengthProven(edge, ct3, edist, length3, center3Tables, edge3Distances, maxNodes, limit, validate, hc, hcCoord, c3PairDist).moves;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Exact-length validated search that also reports what the call proved.
|
|
344
|
+
* `provenThrough` is the greatest length with provably no (center, edge)
|
|
345
|
+
* solution: set only when the tree was exhaustively explored with no goal
|
|
346
|
+
* reached, no node-cap abort, no TT hit, and complete excess info — the
|
|
347
|
+
* caller may then skip every shorter length for this candidate. Otherwise
|
|
348
|
+
* it is `length3 - 1` at most (only the attempted length is settled).
|
|
349
|
+
*/
|
|
350
|
+
solveLengthProven(edge, ct3, edist, length3, center3Tables, edge3Distances, maxNodes = 15000, limit = 16, validate, hc, hcCoord = -1, c3PairDist) {
|
|
351
|
+
if (c3PairDist) {
|
|
352
|
+
const c3Base = ct3 * 144;
|
|
353
|
+
let maxPairDist = 0;
|
|
354
|
+
for (let s = 0; s < 12; s++) {
|
|
355
|
+
const idx = c3Base + s * 12 + edge[s];
|
|
356
|
+
const d = (idx & 1) === 0 ? (c3PairDist[idx >> 1] & 0x0f) : (c3PairDist[idx >> 1] >> 4);
|
|
357
|
+
if (d > maxPairDist)
|
|
358
|
+
maxPairDist = d;
|
|
359
|
+
}
|
|
360
|
+
if (maxPairDist > length3) {
|
|
361
|
+
return { moves: null, provenThrough: maxPairDist - 1, nodesUsed: 0 };
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
this.tempep[0].set(edge);
|
|
365
|
+
this.nodeCount = 0;
|
|
366
|
+
this.capped = false;
|
|
367
|
+
this.limitHit = false;
|
|
368
|
+
this.sawGoal = false;
|
|
369
|
+
this.ttTouched = false;
|
|
370
|
+
this.minExcess = Number.MAX_SAFE_INTEGER;
|
|
371
|
+
this.callId = FastPhase3Solver.nextCallId();
|
|
372
|
+
let accepted = null;
|
|
373
|
+
const collect = {
|
|
374
|
+
limit,
|
|
375
|
+
examined: 0,
|
|
376
|
+
validate: (moves) => {
|
|
377
|
+
if (validate(moves)) {
|
|
378
|
+
accepted = moves;
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
if (!accepted)
|
|
382
|
+
accepted = moves;
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
this.search(this.tempep[0], ct3, edist, length3, 20, 0, center3Tables, edge3Distances, maxNodes, collect, hc, hcCoord, c3PairDist);
|
|
387
|
+
if (accepted)
|
|
388
|
+
return { moves: accepted, provenThrough: length3, nodesUsed: this.nodeCount };
|
|
389
|
+
if (process.env.CUBE4_PROOF_STATS === "1") {
|
|
390
|
+
exports.proofStats.total++;
|
|
391
|
+
if (this.capped)
|
|
392
|
+
exports.proofStats.capped++;
|
|
393
|
+
if (this.limitHit)
|
|
394
|
+
exports.proofStats.limitHit++;
|
|
395
|
+
if (this.sawGoal)
|
|
396
|
+
exports.proofStats.sawGoal++;
|
|
397
|
+
if (this.ttTouched)
|
|
398
|
+
exports.proofStats.ttTouched++;
|
|
399
|
+
}
|
|
400
|
+
if (!this.capped &&
|
|
401
|
+
!this.limitHit &&
|
|
402
|
+
!this.sawGoal &&
|
|
403
|
+
!this.ttTouched &&
|
|
404
|
+
this.minExcess > length3 &&
|
|
405
|
+
this.minExcess !== Number.MAX_SAFE_INTEGER) {
|
|
406
|
+
if (process.env.CUBE4_PROOF_STATS === "1") {
|
|
407
|
+
exports.proofStats.clean++;
|
|
408
|
+
exports.proofStats.jumpSum += this.minExcess - 1 - length3;
|
|
409
|
+
}
|
|
410
|
+
return { moves: null, provenThrough: this.minExcess - 1, nodesUsed: this.nodeCount };
|
|
411
|
+
}
|
|
412
|
+
return { moves: null, provenThrough: length3, nodesUsed: this.nodeCount };
|
|
413
|
+
}
|
|
113
414
|
}
|
|
114
415
|
exports.FastPhase3Solver = FastPhase3Solver;
|
|
416
|
+
/** Temporary instrumentation for proof-statistics (CUBE4_PROOF_STATS=1). */
|
|
417
|
+
exports.proofStats = {
|
|
418
|
+
total: 0,
|
|
419
|
+
capped: 0,
|
|
420
|
+
limitHit: 0,
|
|
421
|
+
sawGoal: 0,
|
|
422
|
+
ttTouched: 0,
|
|
423
|
+
clean: 0,
|
|
424
|
+
jumpSum: 0
|
|
425
|
+
};
|
|
426
|
+
function resetProofStats() {
|
|
427
|
+
exports.proofStats.total = 0;
|
|
428
|
+
exports.proofStats.capped = 0;
|
|
429
|
+
exports.proofStats.limitHit = 0;
|
|
430
|
+
exports.proofStats.sawGoal = 0;
|
|
431
|
+
exports.proofStats.ttTouched = 0;
|
|
432
|
+
exports.proofStats.clean = 0;
|
|
433
|
+
exports.proofStats.jumpSum = 0;
|
|
434
|
+
}
|
|
115
435
|
const defaultSolver = new FastPhase3Solver();
|
|
116
436
|
/**
|
|
117
437
|
* Solve Phase 3 with iterative deepening on a single Phase3Start.
|