@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.
Files changed (126) hide show
  1. package/README.md +15 -4
  2. package/dist/core/api.d.ts.map +1 -1
  3. package/dist/core/api.js +10 -2
  4. package/dist/puzzles/cube2/solver/browser-loader.d.ts +4 -7
  5. package/dist/puzzles/cube2/solver/browser-loader.d.ts.map +1 -1
  6. package/dist/puzzles/cube2/solver/browser-loader.js +42 -14
  7. package/dist/puzzles/cube2/solver/generated-assets.d.ts.map +1 -1
  8. package/dist/puzzles/cube2/solver/generated-assets.js +8 -3
  9. package/dist/puzzles/cube3/solver/browser-loader.d.ts.map +1 -1
  10. package/dist/puzzles/cube3/solver/browser-loader.js +42 -20
  11. package/dist/puzzles/cube3/solver/generated-assets.d.ts.map +1 -1
  12. package/dist/puzzles/cube3/solver/generated-assets.js +9 -3
  13. package/dist/puzzles/cube3/solver/phase1.d.ts +19 -0
  14. package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
  15. package/dist/puzzles/cube3/solver/phase1.js +29 -6
  16. package/dist/puzzles/cube3/solver/phase2.d.ts +28 -0
  17. package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
  18. package/dist/puzzles/cube3/solver/phase2.js +56 -15
  19. package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
  20. package/dist/puzzles/cube3/solver/tables.js +14 -12
  21. package/dist/puzzles/cube4/browser-worker-pool.d.ts +82 -0
  22. package/dist/puzzles/cube4/browser-worker-pool.d.ts.map +1 -0
  23. package/dist/puzzles/cube4/browser-worker-pool.js +205 -0
  24. package/dist/puzzles/cube4/browser-worker.d.ts +2 -0
  25. package/dist/puzzles/cube4/browser-worker.d.ts.map +1 -0
  26. package/dist/puzzles/cube4/browser-worker.js +31 -0
  27. package/dist/puzzles/cube4/index.d.ts +7 -0
  28. package/dist/puzzles/cube4/index.d.ts.map +1 -1
  29. package/dist/puzzles/cube4/index.js +4 -0
  30. package/dist/puzzles/cube4/moves.d.ts.map +1 -1
  31. package/dist/puzzles/cube4/moves.js +122 -30
  32. package/dist/puzzles/cube4/reduction/browser-loader.d.ts +26 -1
  33. package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -1
  34. package/dist/puzzles/cube4/reduction/browser-loader.js +212 -48
  35. package/dist/puzzles/cube4/reduction/center2.d.ts +24 -0
  36. package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -1
  37. package/dist/puzzles/cube4/reduction/center2.js +99 -1
  38. package/dist/puzzles/cube4/reduction/finish3.d.ts +53 -0
  39. package/dist/puzzles/cube4/reduction/finish3.d.ts.map +1 -0
  40. package/dist/puzzles/cube4/reduction/finish3.js +393 -0
  41. package/dist/puzzles/cube4/reduction/generated/hc-manifest.json +6 -0
  42. package/dist/puzzles/cube4/reduction/hc-pdb.d.ts +5 -3
  43. package/dist/puzzles/cube4/reduction/hc-pdb.d.ts.map +1 -1
  44. package/dist/puzzles/cube4/reduction/hc-pdb.js +30 -12
  45. package/dist/puzzles/cube4/reduction/node-loader.d.ts +10 -1
  46. package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -1
  47. package/dist/puzzles/cube4/reduction/node-loader.js +170 -36
  48. package/dist/puzzles/cube4/reduction/pair-transversal.d.ts +28 -0
  49. package/dist/puzzles/cube4/reduction/pair-transversal.d.ts.map +1 -0
  50. package/dist/puzzles/cube4/reduction/pair-transversal.js +107 -0
  51. package/dist/puzzles/cube4/reduction/phase3-search.d.ts +33 -30
  52. package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -1
  53. package/dist/puzzles/cube4/reduction/phase3-search.js +211 -104
  54. package/dist/puzzles/cube4/reduction/rotation-fix.d.ts +28 -0
  55. package/dist/puzzles/cube4/reduction/rotation-fix.d.ts.map +1 -0
  56. package/dist/puzzles/cube4/reduction/rotation-fix.js +212 -0
  57. package/dist/puzzles/cube4/reduction/solve.d.ts +72 -13
  58. package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -1
  59. package/dist/puzzles/cube4/reduction/solve.js +374 -92
  60. package/dist/puzzles/cube4/scramble-cache.d.ts +93 -0
  61. package/dist/puzzles/cube4/scramble-cache.d.ts.map +1 -0
  62. package/dist/puzzles/cube4/scramble-cache.js +150 -0
  63. package/dist/puzzles/cube4/scrambler.d.ts +112 -18
  64. package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
  65. package/dist/puzzles/cube4/scrambler.js +271 -83
  66. package/dist/puzzles/cube4/worker-pool.d.ts +139 -0
  67. package/dist/puzzles/cube4/worker-pool.d.ts.map +1 -0
  68. package/dist/puzzles/cube4/worker-pool.js +405 -0
  69. package/dist/puzzles/cube4/worker-protocol.d.ts +58 -0
  70. package/dist/puzzles/cube4/worker-protocol.d.ts.map +1 -0
  71. package/dist/puzzles/cube4/worker-protocol.js +58 -0
  72. package/dist/puzzles/cube4/worker.d.ts +26 -0
  73. package/dist/puzzles/cube4/worker.d.ts.map +1 -0
  74. package/dist/puzzles/cube4/worker.js +49 -0
  75. package/dist/puzzles/pyraminx/solver/browser-loader.d.ts +4 -8
  76. package/dist/puzzles/pyraminx/solver/browser-loader.d.ts.map +1 -1
  77. package/dist/puzzles/pyraminx/solver/browser-loader.js +44 -15
  78. package/dist/puzzles/pyraminx/solver/generated-assets.d.ts.map +1 -1
  79. package/dist/puzzles/pyraminx/solver/generated-assets.js +8 -3
  80. package/dist/puzzles/shared/compressed-fetch.d.ts +37 -1
  81. package/dist/puzzles/shared/compressed-fetch.d.ts.map +1 -1
  82. package/dist/puzzles/shared/compressed-fetch.js +226 -31
  83. package/dist/puzzles/shared/load-once.d.ts +16 -0
  84. package/dist/puzzles/shared/load-once.d.ts.map +1 -0
  85. package/dist/puzzles/shared/load-once.js +11 -0
  86. package/dist/puzzles/skewb/solver/browser-loader.d.ts +4 -11
  87. package/dist/puzzles/skewb/solver/browser-loader.d.ts.map +1 -1
  88. package/dist/puzzles/skewb/solver/browser-loader.js +44 -16
  89. package/dist/puzzles/skewb/solver/generated-assets.d.ts.map +1 -1
  90. package/dist/puzzles/skewb/solver/generated-assets.js +8 -3
  91. package/dist/puzzles/sq1/solver/browser-loader.d.ts.map +1 -1
  92. package/dist/puzzles/sq1/solver/browser-loader.js +58 -26
  93. package/dist-cjs/core/api.js +10 -2
  94. package/dist-cjs/puzzles/cube2/solver/browser-loader.js +42 -14
  95. package/dist-cjs/puzzles/cube2/solver/generated-assets.js +8 -3
  96. package/dist-cjs/puzzles/cube3/solver/browser-loader.js +42 -20
  97. package/dist-cjs/puzzles/cube3/solver/generated-assets.js +9 -3
  98. package/dist-cjs/puzzles/cube3/solver/phase1.js +29 -6
  99. package/dist-cjs/puzzles/cube3/solver/phase2.js +56 -15
  100. package/dist-cjs/puzzles/cube3/solver/tables.js +14 -12
  101. package/dist-cjs/puzzles/cube4/browser-worker-pool.js +211 -0
  102. package/dist-cjs/puzzles/cube4/index.js +21 -1
  103. package/dist-cjs/puzzles/cube4/moves.js +122 -30
  104. package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +212 -48
  105. package/dist-cjs/puzzles/cube4/reduction/center2.js +102 -2
  106. package/dist-cjs/puzzles/cube4/reduction/finish3.js +398 -0
  107. package/dist-cjs/puzzles/cube4/reduction/generated/hc-manifest.json +6 -0
  108. package/dist-cjs/puzzles/cube4/reduction/hc-pdb.js +31 -12
  109. package/dist-cjs/puzzles/cube4/reduction/node-loader.js +171 -36
  110. package/dist-cjs/puzzles/cube4/reduction/pair-transversal.js +112 -0
  111. package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +211 -104
  112. package/dist-cjs/puzzles/cube4/reduction/rotation-fix.js +216 -0
  113. package/dist-cjs/puzzles/cube4/reduction/solve.js +374 -88
  114. package/dist-cjs/puzzles/cube4/scramble-cache.js +156 -0
  115. package/dist-cjs/puzzles/cube4/scrambler.js +271 -80
  116. package/dist-cjs/puzzles/cube4/worker-pool.js +447 -0
  117. package/dist-cjs/puzzles/cube4/worker-protocol.js +62 -0
  118. package/dist-cjs/puzzles/pyraminx/solver/browser-loader.js +44 -15
  119. package/dist-cjs/puzzles/pyraminx/solver/generated-assets.js +8 -3
  120. package/dist-cjs/puzzles/shared/compressed-fetch.js +229 -31
  121. package/dist-cjs/puzzles/shared/load-once.js +14 -0
  122. package/dist-cjs/puzzles/skewb/solver/browser-loader.js +44 -16
  123. package/dist-cjs/puzzles/skewb/solver/generated-assets.js +8 -3
  124. package/dist-cjs/puzzles/sq1/solver/browser-loader.js +58 -26
  125. package/docs/development.md +19 -7
  126. package/package.json +4 -3
@@ -306,45 +306,137 @@ function parse4Move(m) {
306
306
  (base[0] === "R" || base[0] === "L") ? 1 : 2;
307
307
  return { base, axis, amount };
308
308
  }
309
- function format4Move(base, amount) {
310
- const amt = ((amount % 4) + 4) % 4;
311
- if (amt === 0)
312
- return null;
313
- const suffix = amt === 1 ? "" : amt === 2 ? "2" : "'";
314
- return (base + suffix);
309
+ const AXIS_FACES = [
310
+ ["U", "D"],
311
+ ["R", "L"],
312
+ ["F", "B"]
313
+ ];
314
+ const AXIS_TURN_OPTIONS = [
315
+ ["2", 2],
316
+ ["", 1],
317
+ ["'", 3]
318
+ ];
319
+ function axisVectorKey(vector) {
320
+ return vector[0] | (vector[1] << 2) | (vector[2] << 4) | (vector[3] << 6);
315
321
  }
316
- function movesCommute4(a, b) {
317
- return a.axis === b.axis && a.base !== b.base;
322
+ function applyMoveToAxisVector(vector, base, turns, out) {
323
+ out[0] = vector[0];
324
+ out[1] = vector[1];
325
+ out[2] = vector[2];
326
+ out[3] = vector[3];
327
+ const axis = base[0];
328
+ const [positive, negative] = AXIS_FACES.find(([p, n]) => p === axis || n === axis);
329
+ const isPositive = axis === positive;
330
+ const signedTurns = isPositive ? turns : 4 - turns;
331
+ const wide = base.endsWith("w");
332
+ if (wide) {
333
+ const firstLayer = isPositive ? 0 : 2;
334
+ out[firstLayer] = (out[firstLayer] + signedTurns) & 3;
335
+ out[firstLayer + 1] = (out[firstLayer + 1] + signedTurns) & 3;
336
+ }
337
+ else {
338
+ const layer = isPositive ? 0 : 3;
339
+ out[layer] = (out[layer] + signedTurns) & 3;
340
+ }
341
+ }
342
+ function buildAxisReductionTable(positiveFace, negativeFace) {
343
+ const bases = [positiveFace, negativeFace, `${positiveFace}w`, `${negativeFace}w`];
344
+ const paths = new Array(256);
345
+ const queue = new Uint8Array(256);
346
+ let head = 0;
347
+ let tail = 0;
348
+ paths[0] = [];
349
+ queue[tail++] = 0;
350
+ while (head < tail) {
351
+ const key = queue[head++];
352
+ const vector = [key & 3, (key >> 2) & 3, (key >> 4) & 3, (key >> 6) & 3];
353
+ const prefix = paths[key];
354
+ for (const base of bases) {
355
+ for (const [suffix, turns] of AXIS_TURN_OPTIONS) {
356
+ const nextVector = [0, 0, 0, 0];
357
+ applyMoveToAxisVector(vector, base, turns, nextVector);
358
+ const nextKey = axisVectorKey(nextVector);
359
+ if (paths[nextKey] !== undefined)
360
+ continue;
361
+ paths[nextKey] = [...prefix, `${base}${suffix}`];
362
+ queue[tail++] = nextKey;
363
+ }
364
+ }
365
+ }
366
+ if (tail !== 256 || paths.some((path) => path === undefined)) {
367
+ throw new Error(`4x4 move simplifier: incomplete axis table for ${positiveFace}/${negativeFace}.`);
368
+ }
369
+ return paths;
370
+ }
371
+ const AXIS_REDUCTIONS = AXIS_FACES.map(([positive, negative]) => buildAxisReductionTable(positive, negative));
372
+ function encodeAxisRun(moves, axis) {
373
+ const vector = [0, 0, 0, 0];
374
+ const next = [0, 0, 0, 0];
375
+ for (const move of moves) {
376
+ if (move.axis === axis) {
377
+ applyMoveToAxisVector(vector, move.base, move.amount, next);
378
+ vector[0] = next[0];
379
+ vector[1] = next[1];
380
+ vector[2] = next[2];
381
+ vector[3] = next[3];
382
+ }
383
+ }
384
+ return axisVectorKey(vector);
385
+ }
386
+ function addAxisKeys(a, b) {
387
+ let result = 0;
388
+ for (let layer = 0; layer < 4; layer++) {
389
+ const shift = layer * 2;
390
+ const amount = (((a >> shift) & 3) + ((b >> shift) & 3)) & 3;
391
+ result |= amount << shift;
392
+ }
393
+ return result;
318
394
  }
319
395
  /**
320
396
  * Simplifies a 4x4 algorithm by canceling inverse moves and merging redundant
321
397
  * turns on the same face or across commuting parallel faces.
322
398
  */
323
399
  export function simplifyCube4Algorithm(moves) {
324
- // Single left-to-right pass with an output stack: each move merges with
325
- // the nearest same-base entry across trailing commuting parallel-face
326
- // moves (verified equivalent to the old restart-from-zero loop on 20000
327
- // random algorithms). O(n·window) instead of O(n²) restarts with splices.
328
- const out = [];
329
- for (const move of moves) {
330
- const current = parse4Move(move);
331
- let index = out.length - 1;
332
- while (index >= 0 && movesCommute4(out[index], current))
333
- index--;
334
- if (index >= 0 && out[index].base === current.base) {
335
- const merged = (out[index].amount + current.amount) % 4;
336
- if (merged === 0)
337
- out.splice(index, 1);
338
- else
339
- out[index].amount = merged;
340
- }
341
- else {
342
- out.push(current);
400
+ // Moves on parallel layers commute, and each maximal same-axis run is a
401
+ // four-coordinate vector of quarter turns (one coordinate per layer).
402
+ // A small exact shortest-path table maps that vector back to the fewest
403
+ // legal face/wide moves, including mixed identities such as Rw R' = the
404
+ // corresponding inner-slice turn. Different axes are never reordered.
405
+ const segments = [];
406
+ let run = [];
407
+ let runAxis = -1;
408
+ const flushRun = () => {
409
+ if (run.length === 0)
410
+ return;
411
+ const key = encodeAxisRun(run, runAxis);
412
+ if (key !== 0) {
413
+ const previous = segments[segments.length - 1];
414
+ if (previous?.axis === runAxis) {
415
+ const mergedKey = addAxisKeys(previous.key, key);
416
+ if (mergedKey === 0)
417
+ segments.pop();
418
+ else {
419
+ previous.key = mergedKey;
420
+ previous.moves = AXIS_REDUCTIONS[runAxis][mergedKey];
421
+ }
422
+ }
423
+ else {
424
+ segments.push({ axis: runAxis, key, moves: AXIS_REDUCTIONS[runAxis][key] });
425
+ }
343
426
  }
427
+ run = [];
428
+ runAxis = -1;
429
+ };
430
+ for (const move of moves) {
431
+ const parsed = parse4Move(move);
432
+ if (runAxis !== -1 && parsed.axis !== runAxis)
433
+ flushRun();
434
+ if (runAxis === -1)
435
+ runAxis = parsed.axis;
436
+ run.push(parsed);
344
437
  }
345
- return out
346
- .map((p) => format4Move(p.base, p.amount))
347
- .filter((m) => m !== null);
438
+ flushRun();
439
+ return segments.flatMap((segment) => segment.moves);
348
440
  }
349
441
  // ---------------------------------------------------------------------------
350
442
  // Export the raw effects for use by the reduction solver
@@ -1,6 +1,29 @@
1
1
  /**
2
2
  * Browser loader for the cube4 reduction tables (the committed .bin files).
3
- * The manifests are per-phase (`center1-manifest.json`, ...).
3
+ * The manifests are per-phase (`center1-manifest.json`, ...) plus the shared
4
+ * `hc-manifest.json`, which also seals the optional `c3_pair_dist.bin`.
5
+ *
6
+ * Integrity — identical to node-loader.ts: every downloaded .bin is verified
7
+ * against the manifest sha256 + expected byte length (on the DECOMPRESSED
8
+ * payload, so .br/.gz variants and the raw file are checked the same way).
9
+ *
10
+ * Optional tables (`hc` + `c3PairDist`), NEVER silent:
11
+ * - `hc-manifest.json` present → its entries must download and verify, else
12
+ * the whole load REJECTS (node throws too). There is no silent degradation
13
+ * to an unpruned search: it would change the scramble produced for the
14
+ * same seed, breaking node/browser determinism.
15
+ * - `hc-manifest.json` absent (404) → the optional tables were not deployed;
16
+ * `c3_pair_dist.bin` is then probed to tell "not deployed" (both absent →
17
+ * skip, like node) apart from "stale/half-deployed assets" (bin present,
18
+ * manifest missing → REJECT with an actionable message — node refuses the
19
+ * same asset set).
20
+ * There is deliberately no "degraded" flag: the only valid outcomes are
21
+ * "fully loaded tables" or "loud failure", so both platforms always compute
22
+ * the same scrambles.
23
+ *
24
+ * Caching: concurrent loads share ONE in-flight promise (one fetch pass per
25
+ * table). The promise is kept after success; when it rejects it is purged so
26
+ * the next call can retry instead of being stuck on a poisoned cache.
4
27
  */
5
28
  import type { Center1Tables } from "./center1.js";
6
29
  import type { Center2Tables } from "./center2.js";
@@ -14,6 +37,8 @@ export interface Cube4ReductionTables {
14
37
  edge3: Edge3Tables;
15
38
  /** Half-class exact PDB distances (optional; absent = bound disabled). */
16
39
  hc?: HcDenseTables;
40
+ /** Joint Center3 x Single Pair PDB distances (optional; node parity). */
41
+ c3PairDist?: Uint8Array;
17
42
  }
18
43
  export declare function getCachedCube4Tables(): Cube4ReductionTables | undefined;
19
44
  export declare function clearCachedCube4Tables(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube4/reduction/browser-loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI9C,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,KAAK,EAAE,WAAW,CAAC;IACnB,0EAA0E;IAC1E,EAAE,CAAC,EAAE,aAAa,CAAC;CACpB;AAOD,wBAAgB,oBAAoB,IAAI,oBAAoB,GAAG,SAAS,CAEvE;AAED,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAgBD,wBAAsB,+BAA+B,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAerG"}
1
+ {"version":3,"file":"browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube4/reduction/browser-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI9C,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,KAAK,EAAE,WAAW,CAAC;IACnB,0EAA0E;IAC1E,EAAE,CAAC,EAAE,aAAa,CAAC;IACnB,yEAAyE;IACzE,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAqDD,wBAAgB,oBAAoB,IAAI,oBAAoB,GAAG,SAAS,CAEvE;AAED,wBAAgB,sBAAsB,IAAI,IAAI,CAG7C;AAgBD,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAiC/F"}
@@ -1,18 +1,69 @@
1
1
  /**
2
2
  * Browser loader for the cube4 reduction tables (the committed .bin files).
3
- * The manifests are per-phase (`center1-manifest.json`, ...).
3
+ * The manifests are per-phase (`center1-manifest.json`, ...) plus the shared
4
+ * `hc-manifest.json`, which also seals the optional `c3_pair_dist.bin`.
5
+ *
6
+ * Integrity — identical to node-loader.ts: every downloaded .bin is verified
7
+ * against the manifest sha256 + expected byte length (on the DECOMPRESSED
8
+ * payload, so .br/.gz variants and the raw file are checked the same way).
9
+ *
10
+ * Optional tables (`hc` + `c3PairDist`), NEVER silent:
11
+ * - `hc-manifest.json` present → its entries must download and verify, else
12
+ * the whole load REJECTS (node throws too). There is no silent degradation
13
+ * to an unpruned search: it would change the scramble produced for the
14
+ * same seed, breaking node/browser determinism.
15
+ * - `hc-manifest.json` absent (404) → the optional tables were not deployed;
16
+ * `c3_pair_dist.bin` is then probed to tell "not deployed" (both absent →
17
+ * skip, like node) apart from "stale/half-deployed assets" (bin present,
18
+ * manifest missing → REJECT with an actionable message — node refuses the
19
+ * same asset set).
20
+ * There is deliberately no "degraded" flag: the only valid outcomes are
21
+ * "fully loaded tables" or "loud failure", so both platforms always compute
22
+ * the same scrambles.
23
+ *
24
+ * Caching: concurrent loads share ONE in-flight promise (one fetch pass per
25
+ * table). The promise is kept after success; when it rejects it is purged so
26
+ * the next call can retry instead of being stuck on a poisoned cache.
4
27
  */
28
+ import { CENTER3_STATES } from "./center3.js";
5
29
  import { edge3DistanceLookup } from "./edge3.js";
6
30
  import { fetchTableBuffer } from "../../shared/compressed-fetch.js";
7
31
  import { buildHcDense } from "./hc-pdb.js";
8
32
  const GENERATED_DIRECTORY = "puzzles/cube4/reduction/generated";
9
33
  const CACHE_NAME = "cubingcenter-scrambler-tables-v1";
34
+ const TABLE_MEMORY_BUDGET_BYTES = 150 * 1024 * 1024;
35
+ /**
36
+ * Byte length of `c3_pair_dist.bin`: CENTER3_STATES x 144 nibble entries
37
+ * (index = ct3 * 144 + f * 12 + h, see phase3-search.ts), nibble-packed.
38
+ * Kept in sync with node-loader.ts.
39
+ */
40
+ const C3_PAIR_DIST_BYTES = (CENTER3_STATES * 144) / 2;
10
41
  let cachedTables;
42
+ let cachedTablesPromise;
43
+ function assertCube4ReductionTableBudget(tables) {
44
+ const bytes = tables.center1.distance.byteLength +
45
+ tables.center1.moveMaps.byteLength +
46
+ tables.center2.ctMove.byteLength +
47
+ tables.center2.rlMove.byteLength +
48
+ tables.center2.pruning.byteLength +
49
+ (tables.center2.pruningWide?.byteLength ?? 0) +
50
+ tables.center3.moveTable.byteLength +
51
+ tables.center3.pruning.byteLength +
52
+ tables.edge3.distances.byteLength +
53
+ (tables.hc?.moves.byteLength ?? 0) +
54
+ (tables.hc?.distances.byteLength ?? 0) +
55
+ (tables.hc?.coordinates.byteLength ?? 0) +
56
+ (tables.c3PairDist?.byteLength ?? 0);
57
+ if (bytes > TABLE_MEMORY_BUDGET_BYTES) {
58
+ throw new Error(`Cube4 reduction tables use ${(bytes / (1024 * 1024)).toFixed(1)} MiB, above the 150 MiB table budget.`);
59
+ }
60
+ }
11
61
  export function getCachedCube4Tables() {
12
62
  return cachedTables;
13
63
  }
14
64
  export function clearCachedCube4Tables() {
15
65
  cachedTables = undefined;
66
+ cachedTablesPromise = undefined;
16
67
  }
17
68
  function resolveBaseUrl(provided) {
18
69
  if (provided) {
@@ -27,36 +78,95 @@ function resolveBaseUrl(provided) {
27
78
  }
28
79
  return ".";
29
80
  }
30
- export async function loadCube4ReductionTablesBrowser(baseUrl) {
31
- if (cachedTables) {
32
- return cachedTables;
81
+ export function loadCube4ReductionTablesBrowser(baseUrl) {
82
+ if (cachedTablesPromise) {
83
+ // One shared in-flight promise: concurrent calls trigger exactly one
84
+ // fetch pass (dedup), and the resolved value stays cached afterwards.
85
+ return cachedTablesPromise;
33
86
  }
34
87
  const resolved = resolveBaseUrl(baseUrl);
35
- const [center1, center2, center3, edge3, hc] = await Promise.all([
36
- loadCenter1(resolved),
37
- loadCenter2(resolved),
38
- loadCenter3(resolved),
39
- loadEdge3(resolved),
40
- loadHc(resolved)
41
- ]);
42
- cachedTables = { center1, center2, center3, edge3 };
43
- if (hc)
44
- cachedTables.hc = hc;
45
- return cachedTables;
88
+ const promise = (async () => {
89
+ const [center1, center2, center3, edge3, optional] = await Promise.all([
90
+ loadCenter1(resolved),
91
+ loadCenter2(resolved),
92
+ loadCenter3(resolved),
93
+ loadEdge3(resolved),
94
+ loadOptionalTables(resolved)
95
+ ]);
96
+ const tables = { center1, center2, center3, edge3 };
97
+ if (optional.hc)
98
+ tables.hc = optional.hc;
99
+ if (optional.c3PairDist)
100
+ tables.c3PairDist = optional.c3PairDist;
101
+ assertCube4ReductionTableBudget(tables);
102
+ return tables;
103
+ })();
104
+ cachedTablesPromise = promise;
105
+ promise.then((value) => {
106
+ cachedTables = value;
107
+ }, () => {
108
+ // A failed load must not poison the cache: purge the rejected promise
109
+ // so a later call retries from scratch.
110
+ if (cachedTablesPromise === promise)
111
+ cachedTablesPromise = undefined;
112
+ });
113
+ return promise;
114
+ }
115
+ function requireEntry(entry, manifestUrl, label) {
116
+ if (!entry || typeof entry.file !== "string" || typeof entry.checksum !== "string") {
117
+ throw new Error(`Invalid table manifest ${manifestUrl}: entry "${label}" must declare { file, checksum } — ` +
118
+ "the deployed assets are stale; sync them with pnpm tables:web:sync.");
119
+ }
120
+ return entry;
46
121
  }
47
- async function fetchJson(baseUrl, file) {
122
+ /**
123
+ * Expected byte length of a table file from its manifest entry.
124
+ * `packing` matches how each table is serialized — keep in sync with
125
+ * node-loader.ts.
126
+ */
127
+ function expectedBytes(entry, packing) {
128
+ if (typeof entry.bytes === "number")
129
+ return entry.bytes;
130
+ if (typeof entry.range !== "number")
131
+ return undefined;
132
+ if (packing === "byte")
133
+ return entry.range;
134
+ if (packing === "nibble")
135
+ return Math.ceil(entry.range / 2);
136
+ return entry.range * 4;
137
+ }
138
+ async function fetchVerified(baseUrl, entry, packing, expectedLength) {
139
+ const length = expectedLength ?? expectedBytes(entry, packing);
140
+ return fetchTableBuffer(baseUrl, GENERATED_DIRECTORY, entry.file, {
141
+ cacheName: CACHE_NAME,
142
+ compressed: entry.compressed !== false,
143
+ expectedSha256: entry.checksum,
144
+ ...(length !== undefined ? { expectedBytes: length } : {})
145
+ });
146
+ }
147
+ async function fetchManifest(baseUrl, file) {
48
148
  const url = `${baseUrl}/${GENERATED_DIRECTORY}/${file}`;
49
149
  const response = await fetch(url);
50
150
  if (!response.ok) {
51
- throw new Error(`Failed to load ${file}: ${response.status} ${response.statusText}`);
151
+ throw new Error(`Failed to load table manifest ${url}: ${response.status} ${response.statusText}`);
152
+ }
153
+ const text = await response.text();
154
+ try {
155
+ return JSON.parse(text);
156
+ }
157
+ catch (error) {
158
+ // Always name the URL: a bare JSON.parse error is unactionable.
159
+ throw new Error(`Invalid table manifest JSON: ${url}: ${error instanceof Error ? error.message : String(error)}`);
52
160
  }
53
- return response.json();
54
161
  }
55
162
  async function loadCenter1(baseUrl) {
56
- const manifest = await fetchJson(baseUrl, "center1-manifest.json");
163
+ const manifestUrl = `${baseUrl}/${GENERATED_DIRECTORY}/center1-manifest.json`;
164
+ const manifest = await fetchManifest(baseUrl, "center1-manifest.json");
165
+ const distanceTable = requireEntry(manifest.distanceTable, manifestUrl, "distanceTable");
166
+ const moveMaps = requireEntry(manifest.moveMaps, manifestUrl, "moveMaps");
57
167
  const [distanceBuffer, moveMapsBuffer] = await Promise.all([
58
- fetchTableBuffer(baseUrl, GENERATED_DIRECTORY, manifest.distanceTable.file, { cacheName: CACHE_NAME, compressed: manifest.distanceTable.compressed !== false }),
59
- fetchTableBuffer(baseUrl, GENERATED_DIRECTORY, manifest.moveMaps.file, { cacheName: CACHE_NAME, compressed: manifest.moveMaps.compressed !== false })
168
+ fetchVerified(baseUrl, distanceTable, "nibble"),
169
+ fetchVerified(baseUrl, moveMaps, "int32")
60
170
  ]);
61
171
  return {
62
172
  distance: new Uint8Array(distanceBuffer),
@@ -64,23 +174,30 @@ async function loadCenter1(baseUrl) {
64
174
  };
65
175
  }
66
176
  async function loadCenter2(baseUrl) {
67
- const manifest = await fetchJson(baseUrl, "center2-manifest.json");
177
+ const manifestUrl = `${baseUrl}/${GENERATED_DIRECTORY}/center2-manifest.json`;
178
+ const manifest = await fetchManifest(baseUrl, "center2-manifest.json");
179
+ const ctMove = requireEntry(manifest.ctMove, manifestUrl, "ctMove");
180
+ const rlMove = requireEntry(manifest.rlMove, manifestUrl, "rlMove");
181
+ const pruningTable = requireEntry(manifest.pruningTable, manifestUrl, "pruningTable");
182
+ const pruningWideEntry = manifest.pruningWideTable
183
+ ? requireEntry(manifest.pruningWideTable, manifestUrl, "pruningWideTable")
184
+ : undefined;
68
185
  const [ctMoveBuffer, rlMoveBuffer, pruningBuffer] = await Promise.all([
69
- fetchTableBuffer(baseUrl, GENERATED_DIRECTORY, manifest.ctMove.file, { cacheName: CACHE_NAME, compressed: manifest.ctMove.compressed !== false }),
70
- fetchTableBuffer(baseUrl, GENERATED_DIRECTORY, manifest.rlMove.file, { cacheName: CACHE_NAME, compressed: manifest.rlMove.compressed !== false }),
71
- fetchTableBuffer(baseUrl, GENERATED_DIRECTORY, manifest.pruningTable.file, { cacheName: CACHE_NAME, compressed: manifest.pruningTable.compressed !== false })
186
+ fetchVerified(baseUrl, ctMove, "int32"),
187
+ fetchVerified(baseUrl, rlMove, "int32"),
188
+ fetchVerified(baseUrl, pruningTable, "nibble")
72
189
  ]);
73
- const ctMove = new Int32Array(ctMoveBuffer);
74
- const rlMove = new Int32Array(rlMoveBuffer);
190
+ const ctMoveValues = new Int32Array(ctMoveBuffer);
191
+ const rlMoveValues = new Int32Array(rlMoveBuffer);
75
192
  const pruningPacked = new Uint8Array(pruningBuffer);
76
193
  const pruning = new Uint8Array(450450);
77
194
  for (let i = 0; i < 450450; i++) {
78
195
  const byte = pruningPacked[i >> 1];
79
196
  pruning[i] = (i & 1) === 0 ? byte & 0x0f : byte >> 4;
80
197
  }
81
- const tables = { ctMove, rlMove, pruning };
82
- if (manifest.pruningWideTable) {
83
- const wideBuffer = await fetchTableBuffer(baseUrl, GENERATED_DIRECTORY, manifest.pruningWideTable.file, { cacheName: CACHE_NAME, compressed: manifest.pruningWideTable.compressed !== false });
198
+ const tables = { ctMove: ctMoveValues, rlMove: rlMoveValues, pruning };
199
+ if (pruningWideEntry) {
200
+ const wideBuffer = await fetchVerified(baseUrl, pruningWideEntry, "nibble");
84
201
  const widePacked = new Uint8Array(wideBuffer);
85
202
  const pruningWide = new Uint8Array(450450);
86
203
  for (let i = 0; i < 450450; i++) {
@@ -92,10 +209,13 @@ async function loadCenter2(baseUrl) {
92
209
  return tables;
93
210
  }
94
211
  async function loadCenter3(baseUrl) {
95
- const manifest = await fetchJson(baseUrl, "center3-manifest.json");
212
+ const manifestUrl = `${baseUrl}/${GENERATED_DIRECTORY}/center3-manifest.json`;
213
+ const manifest = await fetchManifest(baseUrl, "center3-manifest.json");
214
+ const moveTable = requireEntry(manifest.moveTable, manifestUrl, "moveTable");
215
+ const pruningTable = requireEntry(manifest.pruningTable, manifestUrl, "pruningTable");
96
216
  const [moveTableBuffer, pruningBuffer] = await Promise.all([
97
- fetchTableBuffer(baseUrl, GENERATED_DIRECTORY, manifest.moveTable.file, { cacheName: CACHE_NAME, compressed: manifest.moveTable.compressed !== false }),
98
- fetchTableBuffer(baseUrl, GENERATED_DIRECTORY, manifest.pruningTable.file, { cacheName: CACHE_NAME, compressed: manifest.pruningTable.compressed !== false })
217
+ fetchVerified(baseUrl, moveTable, "int32"),
218
+ fetchVerified(baseUrl, pruningTable, "byte")
99
219
  ]);
100
220
  return {
101
221
  moveTable: new Int32Array(moveTableBuffer),
@@ -103,24 +223,68 @@ async function loadCenter3(baseUrl) {
103
223
  };
104
224
  }
105
225
  async function loadEdge3(baseUrl) {
106
- const manifest = await fetchJson(baseUrl, "edge3-manifest.json");
107
- const distancesBuffer = await fetchTableBuffer(baseUrl, GENERATED_DIRECTORY, manifest.distances.file, { cacheName: CACHE_NAME, compressed: manifest.distances.compressed !== false });
226
+ const manifestUrl = `${baseUrl}/${GENERATED_DIRECTORY}/edge3-manifest.json`;
227
+ const manifest = await fetchManifest(baseUrl, "edge3-manifest.json");
228
+ const distances = requireEntry(manifest.distances, manifestUrl, "distances");
229
+ // `range` here already counts packed nibble BYTES (classCount / 2), unlike
230
+ // the other manifests — derive the length from classCount instead.
231
+ const length = typeof manifest.classCount === "number" ? Math.ceil(manifest.classCount / 2) : expectedBytes(distances, "byte");
232
+ const distancesBuffer = await fetchVerified(baseUrl, distances, "nibble", length);
108
233
  // Keep nibble-packed (see node-loader.ts): all readers handle both formats.
109
- const distances = new Uint8Array(distancesBuffer);
110
- return { distances, distanceOfEdge: edge3DistanceLookup(distances) };
234
+ const table = new Uint8Array(distancesBuffer);
235
+ return { distances: table, distanceOfEdge: edge3DistanceLookup(table) };
111
236
  }
112
- async function loadHc(baseUrl) {
113
- try {
114
- const manifest = await fetchJson(baseUrl, "hc-manifest.json");
115
- const distancesBuffer = await fetchTableBuffer(baseUrl, GENERATED_DIRECTORY, manifest.distances.file, { cacheName: CACHE_NAME, compressed: manifest.distances.compressed !== false });
116
- const dense = buildHcDense(new Uint8Array(distancesBuffer));
117
- if (manifest.moveTable) {
118
- const moveBuffer = await fetchTableBuffer(baseUrl, GENERATED_DIRECTORY, manifest.moveTable.file, { cacheName: CACHE_NAME, compressed: manifest.moveTable.compressed !== false });
119
- dense.moves = new Int32Array(moveBuffer);
237
+ /**
238
+ * The two optional PDB tables sealed in `hc-manifest.json`. Semantics mirror
239
+ * node-loader.ts exactly (see the header comment): present → verified or
240
+ * reject; absent → probe the c3 bin so stale deployments fail loudly.
241
+ */
242
+ async function loadOptionalTables(baseUrl) {
243
+ const manifestUrl = `${baseUrl}/${GENERATED_DIRECTORY}/hc-manifest.json`;
244
+ const response = await fetch(manifestUrl);
245
+ let manifest;
246
+ if (response.ok) {
247
+ const text = await response.text();
248
+ try {
249
+ manifest = JSON.parse(text);
250
+ }
251
+ catch (error) {
252
+ throw new Error(`Invalid table manifest JSON: ${manifestUrl}: ${error instanceof Error ? error.message : String(error)}`);
120
253
  }
121
- return dense;
122
254
  }
123
- catch {
124
- return undefined;
255
+ else if (response.status !== 404) {
256
+ throw new Error(`Failed to load table manifest ${manifestUrl}: ${response.status} ${response.statusText}`);
257
+ }
258
+ const result = {};
259
+ if (manifest?.c3PairDist !== undefined) {
260
+ const entry = requireEntry(manifest.c3PairDist, manifestUrl, "c3PairDist");
261
+ if (typeof entry.bytes === "number" && entry.bytes !== C3_PAIR_DIST_BYTES) {
262
+ throw new Error(`Invalid table manifest ${manifestUrl}: c3PairDist.bytes is ${entry.bytes}, expected ` +
263
+ `${C3_PAIR_DIST_BYTES} — sync the assets with pnpm tables:web:sync.`);
264
+ }
265
+ const buffer = await fetchVerified(baseUrl, entry, "nibble", C3_PAIR_DIST_BYTES);
266
+ result.c3PairDist = new Uint8Array(buffer);
267
+ }
268
+ else {
269
+ // Mirror node's fs check: bin present but unsealed = broken deployment.
270
+ const probeUrl = `${baseUrl}/${GENERATED_DIRECTORY}/c3_pair_dist.bin`;
271
+ const probe = await fetch(probeUrl);
272
+ if (probe.ok) {
273
+ throw new Error(`c3_pair_dist.bin is deployed (${probeUrl}) but hc-manifest.json has no "c3PairDist" checksum ` +
274
+ `entry (${manifestUrl}) — refusing to load tables without integrity checks (node fails on the ` +
275
+ "same assets). Sync the assets: pnpm tables:web:sync.");
276
+ }
277
+ }
278
+ if (manifest) {
279
+ const distances = requireEntry(manifest.distances, manifestUrl, "distances");
280
+ const packed = new Uint8Array(await fetchVerified(baseUrl, distances, "nibble"));
281
+ const dense = buildHcDense(packed);
282
+ if (manifest.moveTable !== undefined) {
283
+ const moveTable = requireEntry(manifest.moveTable, manifestUrl, "moveTable");
284
+ const moveBuffer = await fetchVerified(baseUrl, moveTable, "int32");
285
+ dense.moves = new Int32Array(moveBuffer);
286
+ }
287
+ result.hc = dense;
125
288
  }
289
+ return result;
126
290
  }
@@ -77,6 +77,30 @@ export interface Center2Tables {
77
77
  pruningWide?: Uint8Array | undefined;
78
78
  }
79
79
  export declare function generateCenter2Tables(): Center2Tables;
80
+ /**
81
+ * The ct rank above is RELATIVE to ct[15], so `ct == 0` accepts both
82
+ * assignments of the two non-R/L axes to the U/D and F/B face-pairs. Only
83
+ * one of them survives the downstream chirality gate (`axisDet`: the U, R, F
84
+ * axes must form a right-handed frame, i.e. U/D must hold axis (a + 2) % 3
85
+ * when R/L holds axis a). The chiral coordinate ranks the ABSOLUTE pattern
86
+ * "which of the 16 U/D/F/B positions hold the U/D-bound axis" (C(16,8) =
87
+ * 12,870) and its pruning measures the distance to the chirality-correct
88
+ * goals only, so the phase-2 search never walks wrong-chirality goal paths.
89
+ * Same goal set as `ct == 0 && axisDet == 1`; generated at runtime (~1.6 MB).
90
+ */
91
+ export declare const CENTER2_CHIRAL_CT_STATES = 12870;
92
+ export declare const CENTER2_CHIRAL_STATES: number;
93
+ export interface Center2ChiralTables {
94
+ /** 12,870 × 28 — chiral ct rank after each move. */
95
+ ctMove: Uint16Array;
96
+ /** 900,900 exact BFS distances to the chiral goals (23-move set). */
97
+ pruning: Uint8Array;
98
+ /** Chiral ct rank of the goal pattern (U/D positions all U/D-bound). */
99
+ goalCt: number;
100
+ }
101
+ /** Chiral ct rank of a coordinate-invariant (bridged) phase-2 state. */
102
+ export declare function encodeCenter2ChiralCt(state: Cube4State): number;
103
+ export declare function generateCenter2ChiralTables(rlMove: Int32Array): Center2ChiralTables;
80
104
  export interface Center2Phase2Result {
81
105
  /** Phase-2 solution moves (standard 4x4 moves). */
82
106
  moves: Cube4Move[];
@@ -1 +1 @@
1
- {"version":3,"file":"center2.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube4/reduction/center2.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"center2.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube4/reduction/center2.ts"],"names":[],"mappings":"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"}