@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
|
@@ -39,6 +39,17 @@ for (let i = 0; i < 20; i++) {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
/** Popcount of 12-bit masks (the inlined sym-coordinate rank). */
|
|
43
|
+
const POPC12 = new Uint8Array(4096);
|
|
44
|
+
for (let i = 1; i < 4096; i++)
|
|
45
|
+
POPC12[i] = POPC12[i >> 1] + (i & 1);
|
|
46
|
+
/** Flat row-major ckmv3 (21x20) + typed skipAxis3 for the hot recursion. */
|
|
47
|
+
const ckmv3Flat = new Uint8Array(21 * 20);
|
|
48
|
+
for (let i = 0; i < 21; i++) {
|
|
49
|
+
for (let j = 0; j < 20; j++)
|
|
50
|
+
ckmv3Flat[i * 20 + j] = exports.ckmv3[i][j] ? 1 : 0;
|
|
51
|
+
}
|
|
52
|
+
const skipAxis3Fast = Int32Array.from(exports.skipAxis3);
|
|
42
53
|
const F_SLOT_MAP = [
|
|
43
54
|
[4, 5, 2, 3, 1, 0, 6, 7, 8, 9, 10, 11],
|
|
44
55
|
[1, 0, 2, 3, 5, 4, 6, 7, 8, 9, 10, 11],
|
|
@@ -81,52 +92,59 @@ class FastPhase3Solver {
|
|
|
81
92
|
tempep;
|
|
82
93
|
moveLog;
|
|
83
94
|
sym;
|
|
84
|
-
/** Transposition Table size: 2^
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
|
|
95
|
+
/** Transposition Table size: 2^20 = 1,048,576 entries (12 MiB per solve).
|
|
96
|
+
* The previous 2M table used 24 MiB per process, plus a separate 8 MiB
|
|
97
|
+
* generation array. The compact 1M table leaves room for concurrent
|
|
98
|
+
* solvers under the 150 MiB reduction-table budget. */
|
|
99
|
+
static TT_SIZE = 1 << 20;
|
|
88
100
|
static TT_MASK = FastPhase3Solver.TT_SIZE - 1;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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;
|
|
101
|
+
tt = new Uint32Array(FastPhase3Solver.TT_SIZE * 3);
|
|
102
|
+
// TT info packs (call id:20, previous move:5, remaining depth:6), avoiding
|
|
103
|
+
// a separate 8 MiB generation array while retaining exact proof tracking.
|
|
104
|
+
static CALL_ID_LIMIT = 1 << 20;
|
|
105
|
+
callSeq = 1;
|
|
99
106
|
callId = 0;
|
|
100
|
-
|
|
107
|
+
contextCenter3;
|
|
108
|
+
contextEdge3;
|
|
109
|
+
/** Reset this solver's cumulative node counter (legacy method name). */
|
|
101
110
|
resetEpochNodes() {
|
|
102
111
|
this.epochNodes = 0;
|
|
103
112
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
113
|
+
clearTranspositionTable() {
|
|
114
|
+
this.tt.fill(0);
|
|
115
|
+
this.callSeq = 1;
|
|
116
|
+
}
|
|
117
|
+
setTableContext(center3, edge3Distances) {
|
|
118
|
+
if (this.contextCenter3 === center3 && this.contextEdge3 === edge3Distances)
|
|
119
|
+
return;
|
|
120
|
+
this.clearTranspositionTable();
|
|
121
|
+
this.contextCenter3 = center3;
|
|
122
|
+
this.contextEdge3 = edge3Distances;
|
|
112
123
|
}
|
|
113
|
-
/** Fresh per-call id for TT-generation tagging
|
|
114
|
-
|
|
115
|
-
const id =
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return 1;
|
|
124
|
+
/** Fresh per-call id for TT-generation tagging. */
|
|
125
|
+
nextCallId() {
|
|
126
|
+
const id = this.callSeq++;
|
|
127
|
+
if (this.callSeq >= FastPhase3Solver.CALL_ID_LIMIT) {
|
|
128
|
+
this.tt.fill(0);
|
|
129
|
+
this.callSeq = 1;
|
|
120
130
|
}
|
|
121
131
|
return id;
|
|
122
132
|
}
|
|
123
133
|
constructor() {
|
|
124
|
-
|
|
125
|
-
|
|
134
|
+
// Preallocated to well above the deepest phase-3 sweep (maxLength3 <= 27):
|
|
135
|
+
// `ensureDepth` stays as a fallback for out-of-band callers.
|
|
136
|
+
this.tempep = Array.from({ length: 48 }, () => new Uint8Array(12));
|
|
137
|
+
this.moveLog = new Array(48);
|
|
126
138
|
this.sym = (0, edge3_js_1.getSymTables)();
|
|
127
139
|
}
|
|
140
|
+
ensureDepth(depth) {
|
|
141
|
+
while (this.tempep.length <= depth) {
|
|
142
|
+
this.tempep.push(new Uint8Array(12));
|
|
143
|
+
this.moveLog.push(0);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
128
146
|
nodeCount = 0;
|
|
129
|
-
/** Cumulative nodes across calls (reset per
|
|
147
|
+
/** Cumulative nodes across calls (reset per solve generation). */
|
|
130
148
|
epochNodes = 0;
|
|
131
149
|
/**
|
|
132
150
|
* Proof bookkeeping for IDA* threshold propagation (reset per exact-length
|
|
@@ -138,9 +156,8 @@ class FastPhase3Solver {
|
|
|
138
156
|
* - `sawGoal`: a goal was reached (even if the validator rejected it —
|
|
139
157
|
* a nearby length may still validate, so only step L+1).
|
|
140
158
|
* - `ttTouched`: a transposition-table hit from ANOTHER exact-length call
|
|
141
|
-
* skipped a subtree
|
|
142
|
-
*
|
|
143
|
-
* candidate, so their stored proof does not transfer).
|
|
159
|
+
* skipped a subtree, and the hit was not certified goal-free across
|
|
160
|
+
* candidate validators.
|
|
144
161
|
* Bound-based axis skips need no flag: the skipped siblings are
|
|
145
162
|
* same-face turns, so noteExcess records their triangle-inequality bound.
|
|
146
163
|
*/
|
|
@@ -149,11 +166,13 @@ class FastPhase3Solver {
|
|
|
149
166
|
sawGoal = false;
|
|
150
167
|
ttTouched = false;
|
|
151
168
|
minExcess = 0;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Root-length coverage of the tightest TT hit this call (depth +
|
|
171
|
+
* storedDepth): the pruned subtree proves no goal at or below that root
|
|
172
|
+
* length under the call's predicate. `Number.MAX_SAFE_INTEGER` when no hit
|
|
173
|
+
* pruned anything. Feeds `validatedThrough`.
|
|
174
|
+
*/
|
|
175
|
+
ttCoverage = Number.MAX_SAFE_INTEGER;
|
|
157
176
|
/**
|
|
158
177
|
* Recursive IDA* worker on table transitions with Transposition Table pruning.
|
|
159
178
|
*/
|
|
@@ -164,18 +183,38 @@ class FastPhase3Solver {
|
|
|
164
183
|
this.capped = true;
|
|
165
184
|
return false;
|
|
166
185
|
}
|
|
167
|
-
|
|
186
|
+
// Goal test at EVERY node (any length <= maxl), not only at maxl === 0
|
|
187
|
+
// leaves: a goal is (edge paired AND center3 at goal) — both PDB
|
|
188
|
+
// distances zero (the parent's gates guarantee leaves are goals, so this
|
|
189
|
+
// strictly generalizes the old leaf test). Exact-length-only detection
|
|
190
|
+
// made shorter goals on the same path invisible ("a nearby length may
|
|
191
|
+
// still validate"), which both blocked proofs on any raw goal and hid
|
|
192
|
+
// shorter solutions.
|
|
193
|
+
if (prun === 0 && center3Tables.pruning[ct] === 0) {
|
|
194
|
+
this.sawGoal = true;
|
|
168
195
|
if (!collect)
|
|
169
196
|
return true;
|
|
170
|
-
this.sawGoal = true;
|
|
171
197
|
collect.examined++;
|
|
172
198
|
const moves = this.moveLog.slice(0, depth).map((m) => exports.SEARCH_MOVES[m]);
|
|
173
199
|
if (collect.validate(moves))
|
|
174
200
|
return true;
|
|
175
|
-
if (collect.examined >= collect.limit)
|
|
201
|
+
if (collect.examined >= collect.limit) {
|
|
176
202
|
this.limitHit = true;
|
|
177
|
-
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
// A rejected goal is not a goal for the validator's predicate: keep
|
|
206
|
+
// exploring the subtree — validated goals may sit deeper on the same
|
|
207
|
+
// branches. At the fringe (maxl === 0) there is no subtree left, so
|
|
208
|
+
// record the branch's coverage claim here (nothing below `depth` on
|
|
209
|
+
// this branch was searched): "no validated goal at <= depth".
|
|
210
|
+
if (maxl === 0 && depth < this.minExcess)
|
|
211
|
+
this.minExcess = depth + 1;
|
|
212
|
+
}
|
|
213
|
+
else if (maxl === 0) {
|
|
214
|
+
return false;
|
|
178
215
|
}
|
|
216
|
+
if (depth >= this.tempep.length)
|
|
217
|
+
this.ensureDepth(depth);
|
|
179
218
|
const sym = this.sym;
|
|
180
219
|
const symMvrot = sym.mvrot;
|
|
181
220
|
const symMvroto = sym.mvroto;
|
|
@@ -189,25 +228,37 @@ class FastPhase3Solver {
|
|
|
189
228
|
else {
|
|
190
229
|
ep.set(eplast);
|
|
191
230
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
231
|
+
const hasHc = hc !== undefined && hcCoord >= 0 && hc.moves.length > 0;
|
|
232
|
+
// HC pruning depends on a second coordinate not represented in the TT
|
|
233
|
+
// key. The root c3-pair check also varies only at depth zero. Do not reuse
|
|
234
|
+
// entries in those contexts; all other phase-3 state is key-complete.
|
|
235
|
+
const useTt = !hasHc && !(depth === 0 && c3PairDist !== undefined);
|
|
236
|
+
// TT: entries claim "no RAW goal at any length <= maxl from (ep, lm)" —
|
|
237
|
+
// coordinate-complete and predicate-independent (the validator's
|
|
238
|
+
// full-state checks are NOT coordinate-complete, so no validator-scoped
|
|
239
|
+
// claims may be cached). Hits are `<=`-claims and therefore
|
|
240
|
+
// proof-compatible for `validatedThrough`/`provenThrough` coverage.
|
|
241
|
+
const ttActive = useTt && maxl >= 2;
|
|
242
|
+
let keyHigh = 0;
|
|
243
|
+
let keyLow = 0;
|
|
244
|
+
let slot = 0;
|
|
245
|
+
if (ttActive) {
|
|
246
|
+
keyHigh = ((ct << 16) | (ep[8] | (ep[9] << 4) | (ep[10] << 8) | (ep[11] << 12))) >>> 0;
|
|
247
|
+
keyLow = (ep[0] | (ep[1] << 4) | (ep[2] << 8) | (ep[3] << 12) |
|
|
248
|
+
(ep[4] << 16) | (ep[5] << 20) | (ep[6] << 24) | (ep[7] << 28)) >>> 0;
|
|
249
|
+
slot = (((keyLow ^ Math.imul(keyHigh, 0x9e3779b9)) & FastPhase3Solver.TT_MASK) * 3) >>> 0;
|
|
250
|
+
const storedHigh = this.tt[slot];
|
|
251
|
+
const storedLow = this.tt[slot + 1];
|
|
252
|
+
const storedInfo = this.tt[slot + 2];
|
|
201
253
|
if (storedHigh === keyHigh && storedLow === keyLow) {
|
|
202
|
-
const
|
|
203
|
-
const storedLm = (storedInfo
|
|
204
|
-
const storedDepth = storedInfo &
|
|
205
|
-
if (
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (FastPhase3Solver.ttGen[(slot / 3) | 0] !== this.callId)
|
|
254
|
+
const storedCallId = (storedInfo >>> 11) & 0xfffff;
|
|
255
|
+
const storedLm = (storedInfo >>> 6) & 0x1f;
|
|
256
|
+
const storedDepth = storedInfo & 0x3f;
|
|
257
|
+
if (storedDepth >= maxl && storedLm === lm) {
|
|
258
|
+
const coverage = depth + storedDepth;
|
|
259
|
+
if (coverage < this.ttCoverage)
|
|
260
|
+
this.ttCoverage = coverage;
|
|
261
|
+
if (storedCallId !== this.callId)
|
|
211
262
|
this.ttTouched = true;
|
|
212
263
|
return false;
|
|
213
264
|
}
|
|
@@ -219,15 +270,16 @@ class FastPhase3Solver {
|
|
|
219
270
|
const sym2Raw = sym.sym2Raw;
|
|
220
271
|
const sym2Mask = sym.sym2Mask;
|
|
221
272
|
const raw2Sym = sym.raw2Sym;
|
|
222
|
-
const hasHc = hc !== undefined && hcCoord >= 0 && hc.moves.length > 0;
|
|
223
273
|
const hcMoves = hasHc ? hc.moves : null;
|
|
224
274
|
const hcDist = hasHc ? hc.distances : null;
|
|
225
275
|
const hcRow = hasHc ? hcCoord * hc_pdb_js_1.HC_PDB_MOVES.length : 0;
|
|
226
276
|
// Byte-expanded (tests/generators) vs nibble-packed (loaders) distances.
|
|
227
277
|
const edgeUnpacked = edge3Distances.length >= 31006080;
|
|
278
|
+
const examined0 = collect === undefined ? 0 : collect.examined;
|
|
279
|
+
const ckmvRow = lm * 20;
|
|
228
280
|
for (let m = 0; m < 17; m++) {
|
|
229
|
-
if (
|
|
230
|
-
m =
|
|
281
|
+
if (ckmv3Flat[ckmvRow + m]) {
|
|
282
|
+
m = skipAxis3Fast[m];
|
|
231
283
|
continue;
|
|
232
284
|
}
|
|
233
285
|
const nc = c3Move[c3Row + m];
|
|
@@ -235,9 +287,11 @@ class FastPhase3Solver {
|
|
|
235
287
|
continue;
|
|
236
288
|
const prun1 = c3Prun[nc];
|
|
237
289
|
if (prun1 >= maxl) {
|
|
238
|
-
|
|
290
|
+
const ex1 = (prun1 > maxl && m < 14) ? depth + prun1 : depth + 1 + prun1;
|
|
291
|
+
if (ex1 < this.minExcess)
|
|
292
|
+
this.minExcess = ex1;
|
|
239
293
|
if (prun1 > maxl && m < 14)
|
|
240
|
-
m =
|
|
294
|
+
m = skipAxis3Fast[m];
|
|
241
295
|
continue;
|
|
242
296
|
}
|
|
243
297
|
let nhc = -1;
|
|
@@ -246,9 +300,11 @@ class FastPhase3Solver {
|
|
|
246
300
|
if (nhc >= 0) {
|
|
247
301
|
const prunh = hcDist[nhc];
|
|
248
302
|
if (prunh >= maxl) {
|
|
249
|
-
|
|
303
|
+
const exH = (prunh > maxl && m < 14) ? depth + prunh : depth + 1 + prunh;
|
|
304
|
+
if (exH < this.minExcess)
|
|
305
|
+
this.minExcess = exH;
|
|
250
306
|
if (prunh > maxl && m < 14)
|
|
251
|
-
m =
|
|
307
|
+
m = skipAxis3Fast[m];
|
|
252
308
|
continue;
|
|
253
309
|
}
|
|
254
310
|
}
|
|
@@ -269,13 +325,36 @@ class FastPhase3Solver {
|
|
|
269
325
|
prunPair = d;
|
|
270
326
|
}
|
|
271
327
|
if (prunPair >= maxl) {
|
|
272
|
-
|
|
328
|
+
const exP = (prunPair > maxl && m < 14) ? depth + prunPair : depth + 1 + prunPair;
|
|
329
|
+
if (exP < this.minExcess)
|
|
330
|
+
this.minExcess = exP;
|
|
273
331
|
if (prunPair > maxl && m < 14)
|
|
274
|
-
m =
|
|
332
|
+
m = skipAxis3Fast[m];
|
|
275
333
|
continue;
|
|
276
334
|
}
|
|
277
335
|
}
|
|
278
|
-
|
|
336
|
+
// Inlined getMvSymFrom(ep, ep, m, ...) (bit-identical; masks here are
|
|
337
|
+
// 12-bit, so one popcount lookup replaces bitCount's three).
|
|
338
|
+
let mrIdx = m << 3;
|
|
339
|
+
let movoBase = mrIdx * 12;
|
|
340
|
+
let raw = 0;
|
|
341
|
+
let bits = 0;
|
|
342
|
+
for (let i = 0; i < 4; i++) {
|
|
343
|
+
const val = symMvroto[movoBase + ep[symMvrot[movoBase + i]]];
|
|
344
|
+
raw = raw * (12 - i) + val - POPC12[bits & ((1 << val) - 1)];
|
|
345
|
+
bits |= 1 << val;
|
|
346
|
+
}
|
|
347
|
+
const symBase = raw2Sym[raw];
|
|
348
|
+
mrIdx |= symBase & 7;
|
|
349
|
+
let symOut = symBase >> 3;
|
|
350
|
+
bits = sym2Mask[symOut];
|
|
351
|
+
movoBase = mrIdx * 12;
|
|
352
|
+
for (let i = 4; i < 10; i++) {
|
|
353
|
+
const val = symMvroto[movoBase + ep[symMvrot[movoBase + i]]];
|
|
354
|
+
symOut = symOut * (12 - i) + val - POPC12[bits & ((1 << val) - 1)];
|
|
355
|
+
bits |= 1 << val;
|
|
356
|
+
}
|
|
357
|
+
const symIdx = ((symOut << 3) | (mrIdx & 7)) >>> 0;
|
|
279
358
|
// Format-aware read: loaders keep the nibble-packed table (15.5MB);
|
|
280
359
|
// tests and generators may pass byte-expanded buffers instead.
|
|
281
360
|
const eIdx = symIdx >> 3;
|
|
@@ -283,9 +362,11 @@ class FastPhase3Solver {
|
|
|
283
362
|
? edge3Distances[eIdx]
|
|
284
363
|
: (edge3Distances[eIdx >> 1] >> ((eIdx & 1) * 4)) & 0x0f;
|
|
285
364
|
if (prunx >= maxl) {
|
|
286
|
-
|
|
365
|
+
const exE = (prunx > maxl && m < 14) ? depth + prunx : depth + 1 + prunx;
|
|
366
|
+
if (exE < this.minExcess)
|
|
367
|
+
this.minExcess = exE;
|
|
287
368
|
if (prunx > maxl && m < 14)
|
|
288
|
-
m =
|
|
369
|
+
m = skipAxis3Fast[m];
|
|
289
370
|
continue;
|
|
290
371
|
}
|
|
291
372
|
this.moveLog[depth] = m;
|
|
@@ -293,36 +374,38 @@ class FastPhase3Solver {
|
|
|
293
374
|
return true;
|
|
294
375
|
}
|
|
295
376
|
}
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
377
|
+
// Cache only subtrees with NO raw goal reached in them (the examined
|
|
378
|
+
// count is the goal counter — rejections included, since the claim is
|
|
379
|
+
// raw). Goal-free subtrees are safe to reuse across calls, candidates
|
|
380
|
+
// and predicates.
|
|
381
|
+
if (ttActive &&
|
|
382
|
+
this.nodeCount <= maxNodes &&
|
|
383
|
+
(collect?.examined ?? 0) === examined0) {
|
|
384
|
+
this.tt[slot] = keyHigh;
|
|
385
|
+
this.tt[slot + 1] = keyLow;
|
|
386
|
+
this.tt[slot + 2] = ((this.callId << 11) | (lm << 6) | maxl) >>> 0;
|
|
302
387
|
}
|
|
303
388
|
return false;
|
|
304
389
|
}
|
|
305
390
|
/**
|
|
306
|
-
* Record an IDA* bound prune
|
|
307
|
-
* `childBound` more moves, so no solution exists
|
|
308
|
-
* below root threshold `depth + 1 + childBound`. When a
|
|
309
|
-
* skip also fires, the skipped siblings are same-face turns
|
|
310
|
-
* every non-empty skipAxis3 run under m < 14 is a same-base
|
|
311
|
-
* hence at most 1 legal move apart — their bound is at least
|
|
391
|
+
* Record an IDA* bound prune (inlined at the four gate sites): the child at
|
|
392
|
+
* `depth + 1` needs at least `childBound` more moves, so no solution exists
|
|
393
|
+
* through this branch below root threshold `depth + 1 + childBound`. When a
|
|
394
|
+
* bound-based axis skip also fires, the skipped siblings are same-face turns
|
|
395
|
+
* (verified: every non-empty skipAxis3 run under m < 14 is a same-base
|
|
396
|
+
* triple), hence at most 1 legal move apart — their bound is at least
|
|
312
397
|
* `childBound - 1` by the triangle inequality on the exact PDBs, so the
|
|
313
398
|
* recorded excess `depth + childBound` stays a true lower bound.
|
|
314
399
|
*/
|
|
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
400
|
/**
|
|
321
401
|
* Search for a phase-3 solution with exact target length (first found).
|
|
322
402
|
*/
|
|
323
403
|
solveLength(edge, ct3, edist, length3, center3Tables, edge3Distances, maxNodes = 15000, hc, hcCoord = -1, c3PairDist) {
|
|
404
|
+
this.setTableContext(center3Tables, edge3Distances);
|
|
324
405
|
this.tempep[0].set(edge);
|
|
325
406
|
this.nodeCount = 0;
|
|
407
|
+
this.callId = this.nextCallId();
|
|
408
|
+
this.ttCoverage = Number.MAX_SAFE_INTEGER;
|
|
326
409
|
if (this.search(this.tempep[0], ct3, edist, length3, 20, 0, center3Tables, edge3Distances, maxNodes, undefined, hc, hcCoord, c3PairDist)) {
|
|
327
410
|
return this.moveLog.slice(0, length3).map((m) => exports.SEARCH_MOVES[m]);
|
|
328
411
|
}
|
|
@@ -348,6 +431,7 @@ class FastPhase3Solver {
|
|
|
348
431
|
* it is `length3 - 1` at most (only the attempted length is settled).
|
|
349
432
|
*/
|
|
350
433
|
solveLengthProven(edge, ct3, edist, length3, center3Tables, edge3Distances, maxNodes = 15000, limit = 16, validate, hc, hcCoord = -1, c3PairDist) {
|
|
434
|
+
this.setTableContext(center3Tables, edge3Distances);
|
|
351
435
|
if (c3PairDist) {
|
|
352
436
|
const c3Base = ct3 * 144;
|
|
353
437
|
let maxPairDist = 0;
|
|
@@ -358,7 +442,7 @@ class FastPhase3Solver {
|
|
|
358
442
|
maxPairDist = d;
|
|
359
443
|
}
|
|
360
444
|
if (maxPairDist > length3) {
|
|
361
|
-
return { moves: null, provenThrough: maxPairDist - 1, nodesUsed: 0 };
|
|
445
|
+
return { moves: null, provenThrough: maxPairDist - 1, validatedThrough: maxPairDist - 1, nodesUsed: 0 };
|
|
362
446
|
}
|
|
363
447
|
}
|
|
364
448
|
this.tempep[0].set(edge);
|
|
@@ -368,7 +452,8 @@ class FastPhase3Solver {
|
|
|
368
452
|
this.sawGoal = false;
|
|
369
453
|
this.ttTouched = false;
|
|
370
454
|
this.minExcess = Number.MAX_SAFE_INTEGER;
|
|
371
|
-
this.
|
|
455
|
+
this.ttCoverage = Number.MAX_SAFE_INTEGER;
|
|
456
|
+
this.callId = this.nextCallId();
|
|
372
457
|
let accepted = null;
|
|
373
458
|
const collect = {
|
|
374
459
|
limit,
|
|
@@ -378,15 +463,15 @@ class FastPhase3Solver {
|
|
|
378
463
|
accepted = moves;
|
|
379
464
|
return true;
|
|
380
465
|
}
|
|
381
|
-
|
|
382
|
-
|
|
466
|
+
// A rejected goal is not a validated solution. Enumeration callers
|
|
467
|
+
// may collect it themselves, but cannot use it as a search proof.
|
|
383
468
|
return false;
|
|
384
469
|
}
|
|
385
470
|
};
|
|
386
471
|
this.search(this.tempep[0], ct3, edist, length3, 20, 0, center3Tables, edge3Distances, maxNodes, collect, hc, hcCoord, c3PairDist);
|
|
387
472
|
if (accepted)
|
|
388
|
-
return { moves: accepted, provenThrough: length3, nodesUsed: this.nodeCount };
|
|
389
|
-
if (process.env
|
|
473
|
+
return { moves: accepted, provenThrough: length3, validatedThrough: length3, nodesUsed: this.nodeCount };
|
|
474
|
+
if (typeof process !== "undefined" && process.env?.CUBE4_PROOF_STATS === "1") {
|
|
390
475
|
exports.proofStats.total++;
|
|
391
476
|
if (this.capped)
|
|
392
477
|
exports.proofStats.capped++;
|
|
@@ -397,19 +482,41 @@ class FastPhase3Solver {
|
|
|
397
482
|
if (this.ttTouched)
|
|
398
483
|
exports.proofStats.ttTouched++;
|
|
399
484
|
}
|
|
485
|
+
// `provenThrough` keeps its conservative raw-goal contract: it may only
|
|
486
|
+
// claim a depth when NO raw goal was reached at all (a rejected goal IS
|
|
487
|
+
// a raw goal), no TT hit intervened, and the bound prunes cover past the
|
|
488
|
+
// attempted length.
|
|
489
|
+
let provenThrough = -1;
|
|
400
490
|
if (!this.capped &&
|
|
401
491
|
!this.limitHit &&
|
|
402
492
|
!this.sawGoal &&
|
|
403
493
|
!this.ttTouched &&
|
|
404
494
|
this.minExcess > length3 &&
|
|
405
495
|
this.minExcess !== Number.MAX_SAFE_INTEGER) {
|
|
406
|
-
if (process.env
|
|
496
|
+
if (typeof process !== "undefined" && process.env?.CUBE4_PROOF_STATS === "1") {
|
|
407
497
|
exports.proofStats.clean++;
|
|
408
498
|
exports.proofStats.jumpSum += this.minExcess - 1 - length3;
|
|
409
499
|
}
|
|
410
|
-
|
|
500
|
+
provenThrough = this.minExcess - 1;
|
|
501
|
+
}
|
|
502
|
+
// `validatedThrough` is scoped to THIS call's validator predicate (all
|
|
503
|
+
// calls sharing one solver instance use one predicate): rejected raw
|
|
504
|
+
// goals are not goals for it, and TT hits are proof-compatible (entries
|
|
505
|
+
// mean "no goal under the call's predicate at any length <= stored").
|
|
506
|
+
// Every walked branch leaves a claim: bound prunes record their
|
|
507
|
+
// threshold (noteExcess), fringe rejections record `length3 + 1` below,
|
|
508
|
+
// and TT-hit subtrees record `ttCoverage`.
|
|
509
|
+
let validatedThrough = -1;
|
|
510
|
+
if (!this.capped && !this.limitHit) {
|
|
511
|
+
let bound = length3;
|
|
512
|
+
if (this.minExcess !== Number.MAX_SAFE_INTEGER && this.minExcess - 1 > bound) {
|
|
513
|
+
bound = this.minExcess - 1;
|
|
514
|
+
}
|
|
515
|
+
if (this.ttCoverage < bound)
|
|
516
|
+
bound = this.ttCoverage;
|
|
517
|
+
validatedThrough = bound;
|
|
411
518
|
}
|
|
412
|
-
return { moves: null, provenThrough
|
|
519
|
+
return { moves: null, provenThrough, validatedThrough, nodesUsed: this.nodeCount };
|
|
413
520
|
}
|
|
414
521
|
}
|
|
415
522
|
exports.FastPhase3Solver = FastPhase3Solver;
|
|
@@ -432,11 +539,11 @@ function resetProofStats() {
|
|
|
432
539
|
exports.proofStats.clean = 0;
|
|
433
540
|
exports.proofStats.jumpSum = 0;
|
|
434
541
|
}
|
|
435
|
-
const defaultSolver = new FastPhase3Solver();
|
|
436
542
|
/**
|
|
437
543
|
* Solve Phase 3 with iterative deepening on a single Phase3Start.
|
|
438
544
|
*/
|
|
439
|
-
async function solvePhase3(start, center3Tables, edge3Tables, maxLength = 20) {
|
|
545
|
+
async function solvePhase3(start, center3Tables, edge3Tables, maxLength = 20, maxNodes = 80_000) {
|
|
546
|
+
const solver = new FastPhase3Solver();
|
|
440
547
|
const sym = (0, edge3_js_1.getSymTables)();
|
|
441
548
|
const symIdx = (0, edge3_js_1.getMvSymFrom)(start.edge, start.edge, 20, sym.sym2Raw, sym.sym2Mask, sym.raw2Sym, sym.mvrot, sym.mvroto);
|
|
442
549
|
const edist = (edge3Tables.distances[(symIdx >> 3) >> 1] >> (((symIdx >> 3) & 1) * 4)) & 0x0f;
|
|
@@ -445,7 +552,7 @@ async function solvePhase3(start, center3Tables, edge3Tables, maxLength = 20) {
|
|
|
445
552
|
return null;
|
|
446
553
|
const minLength = Math.max(cdist, edist);
|
|
447
554
|
for (let l = minLength; l <= maxLength; l++) {
|
|
448
|
-
const moves =
|
|
555
|
+
const moves = solver.solveLength(start.edge, start.ct3, edist, l, center3Tables, edge3Tables.distances, maxNodes);
|
|
449
556
|
if (moves) {
|
|
450
557
|
return {
|
|
451
558
|
moves,
|