@cubingcenter/scrambler 0.5.0 → 0.5.1

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 (32) hide show
  1. package/dist/puzzles/pyraminx/facelets.d.ts.map +1 -1
  2. package/dist/puzzles/pyraminx/facelets.js +43 -17
  3. package/dist/puzzles/pyraminx/random-state.d.ts.map +1 -1
  4. package/dist/puzzles/pyraminx/random-state.js +63 -29
  5. package/dist/puzzles/pyraminx/scrambler.d.ts.map +1 -1
  6. package/dist/puzzles/pyraminx/scrambler.js +35 -26
  7. package/dist/puzzles/pyraminx/solver/search.d.ts +3 -0
  8. package/dist/puzzles/pyraminx/solver/search.d.ts.map +1 -1
  9. package/dist/puzzles/pyraminx/solver/search.js +60 -5
  10. package/dist/puzzles/pyraminx/solver/tables.d.ts +1 -0
  11. package/dist/puzzles/pyraminx/solver/tables.d.ts.map +1 -1
  12. package/dist/puzzles/pyraminx/solver/tables.js +3 -0
  13. package/dist/puzzles/skewb/scrambler.d.ts.map +1 -1
  14. package/dist/puzzles/skewb/scrambler.js +21 -7
  15. package/dist/puzzles/skewb/solver/search.d.ts +3 -0
  16. package/dist/puzzles/skewb/solver/search.d.ts.map +1 -1
  17. package/dist/puzzles/skewb/solver/search.js +43 -3
  18. package/dist/puzzles/skewb/solver/tables.d.ts +1 -0
  19. package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -1
  20. package/dist/puzzles/skewb/solver/tables.js +3 -0
  21. package/dist/random/prng.d.ts.map +1 -1
  22. package/dist/random/prng.js +18 -4
  23. package/dist-cjs/puzzles/pyraminx/facelets.js +43 -17
  24. package/dist-cjs/puzzles/pyraminx/random-state.js +63 -29
  25. package/dist-cjs/puzzles/pyraminx/scrambler.js +34 -25
  26. package/dist-cjs/puzzles/pyraminx/solver/search.js +62 -4
  27. package/dist-cjs/puzzles/pyraminx/solver/tables.js +4 -0
  28. package/dist-cjs/puzzles/skewb/scrambler.js +19 -5
  29. package/dist-cjs/puzzles/skewb/solver/search.js +46 -3
  30. package/dist-cjs/puzzles/skewb/solver/tables.js +4 -0
  31. package/dist-cjs/random/prng.js +18 -4
  32. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/tables.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAazD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAanE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAY1E;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAOrE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAS/D;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAWjE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAE1D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAOvD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAY3D;AAYD,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,kBAAkB,IAAI,CAAC;AACpC,eAAO,MAAM,YAAY,IAAI,CAAC;AAC9B,eAAO,MAAM,UAAU,IAAI,CAAC;AAC5B,eAAO,MAAM,WAAW,QAAuD,CAAC;AAChF,eAAO,MAAM,qBAAqB,QAAmC,CAAC;AAEtE,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAAiD,CAAC;AAE9F,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAa7D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAYzD;AAOD,eAAO,IAAI,iBAAiB,EAAE,WAAW,GAAG,IAAW,CAAC;AACxD,eAAO,IAAI,iBAAiB,EAAE,WAAW,GAAG,IAAW,CAAC;AACxD,eAAO,IAAI,aAAa,EAAE,WAAW,GAAG,IAAW,CAAC;AACpD,eAAO,IAAI,YAAY,EAAE,UAAU,GAAG,IAAW,CAAC;AAClD,eAAO,IAAI,gBAAgB,EAAE,WAAW,GAAG,IAAW,CAAC;AACvD,eAAO,IAAI,gBAAgB,EAAE,WAAW,GAAG,IAAW,CAAC;AACvD,eAAO,IAAI,YAAY,EAAE,UAAU,GAAG,IAAW,CAAC;AAClD,eAAO,IAAI,OAAO,EAAE,WAAW,GAAG,IAAW,CAAC;AAC9C,eAAO,IAAI,YAAY,EAAE,UAAU,GAAG,IAAW,CAAC;AAClD,eAAO,IAAI,aAAa,EAAE,UAAU,GAAG,IAAW,CAAC;AACnD,eAAO,IAAI,WAAW,QAAI,CAAC;AAE3B,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAIxD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,gBAAgB,EAAE,WAAW,CAAC;IAC9B,gBAAgB,EAAE,WAAW,CAAC;IAC9B,YAAY,EAAE,WAAW,CAAC;IAC1B,WAAW,EAAE,UAAU,CAAC;IACxB,cAAc,EAAE,WAAW,CAAC;IAC5B,WAAW,EAAE,UAAU,CAAC;IACxB,MAAM,EAAE,WAAW,CAAC;CACrB,GAAG,IAAI,CAyBP;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAY1C;AAMD,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAc7E;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAWtD;AAID,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CA0BzD;AAGD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAMrF;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKvD;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAK9E;AAED,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAKhF;AAED,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAK5E;AAMD,wBAAgB,wBAAwB,IAAI,WAAW,CActD;AAED,wBAAgB,wBAAwB,IAAI,WAAW,CActD;AAED,wBAAgB,oBAAoB,IAAI,WAAW,CAclD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAQjE;AAED,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,WAAW,CAAC;IAC5B,WAAW,EAAE,UAAU,CAAC;IACxB,MAAM,EAAE,WAAW,CAAC;IACpB,cAAc,EAAE,WAAW,CAAC;CAC7B;AAMD,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,WAAW,EACtB,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,WAAW,GACxB,iBAAiB,CAwFnB;AAKD,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,WAAW,EACtB,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,WAAW,EACzB,OAAO,EAAE,iBAAiB,GACzB;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAiE1C;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,MAAM,CAO1D"}
1
+ {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/tables.ts"],"names":[],"mappings":"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"}
@@ -156,6 +156,9 @@ export async function ensureTablesLoaded() {
156
156
  const tables = await loadGeneratedSkewbSolverTables();
157
157
  assignTables(tables);
158
158
  }
159
+ export function areSkewbTablesLoaded() {
160
+ return tablesLoaded;
161
+ }
159
162
  export function assignTables(tables) {
160
163
  PERMUTATION_TABLE = tables.permutationTable;
161
164
  ORIENTATION_TABLE = tables.orientationTable;
@@ -1 +1 @@
1
- {"version":3,"file":"prng.d.ts","sourceRoot":"","sources":["../../src/random/prng.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,IAAI,IAAI,MAAM,CAAC;IACf,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,CAAC;CACnC;AAED,wBAAgB,kBAAkB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,YAAY,CAMvE"}
1
+ {"version":3,"file":"prng.d.ts","sourceRoot":"","sources":["../../src/random/prng.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,YAAY;IAC3B,IAAI,IAAI,MAAM,CAAC;IACf,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,CAAC;CACnC;AAED,wBAAgB,kBAAkB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,YAAY,CAOvE"}
@@ -1,7 +1,14 @@
1
1
  import { randomBytes } from "node:crypto";
2
+ // 4096 bytes per refill (~682 values per syscall).
3
+ const CRYPTO_BUFFER_BYTES = 4096;
4
+ // Shared unseeded source: the crypto refill costs a syscall per buffer, so a
5
+ // per-call source would pay ~one syscall per scramble. Seeded calls stay
6
+ // isolated (deterministic streams must never be shared).
7
+ let sharedCryptoSource = null;
2
8
  export function createRandomSource(seed) {
3
9
  if (seed === undefined) {
4
- return new CryptoRandomSource();
10
+ sharedCryptoSource ??= new CryptoRandomSource();
11
+ return sharedCryptoSource;
5
12
  }
6
13
  return new SeededRandomSource(hashSeed(seed));
7
14
  }
@@ -11,12 +18,19 @@ class CryptoRandomSource {
11
18
  next() {
12
19
  // Batch the crypto calls: one randomBytes per move is ~2 orders of
13
20
  // magnitude slower in Node (syscall per call). Refill lazily.
14
- if (this.buffer === null || this.offset + 6 > 256) {
15
- this.buffer = randomBytes(256);
21
+ if (this.buffer === null || this.offset + 6 > CRYPTO_BUFFER_BYTES) {
22
+ this.buffer = randomBytes(CRYPTO_BUFFER_BYTES);
16
23
  this.offset = 0;
17
24
  }
18
- const value = this.buffer.readUIntBE(this.offset, 6);
25
+ const bytes = this.buffer;
26
+ const offset = this.offset;
19
27
  this.offset += 6;
28
+ const value = bytes[offset] * 0x10000000000 +
29
+ bytes[offset + 1] * 0x100000000 +
30
+ bytes[offset + 2] * 0x1000000 +
31
+ bytes[offset + 3] * 0x10000 +
32
+ bytes[offset + 4] * 0x100 +
33
+ bytes[offset + 5];
20
34
  return value / 0x1000000000000;
21
35
  }
22
36
  nextInt(maxExclusive) {
@@ -5,6 +5,8 @@ exports.toPyraminxFacelets = toPyraminxFacelets;
5
5
  exports.formatPyraminxFacelets = formatPyraminxFacelets;
6
6
  exports.getPyraminxFaceOrder = getPyraminxFaceOrder;
7
7
  const FACE_ORDER = ["F", "L", "R", "D"];
8
+ const COLORS = ["F", "L", "R", "D"];
9
+ const COLOR_INDEX = { F: 0, L: 1, R: 2, D: 3 };
8
10
  // positionId -> sticker slots (each slot = one triangle cell of the grid).
9
11
  // Positions follow the scrambler piece model (moves.ts):
10
12
  // edges 0..5 = UL, UR, UB, LR, LB, RB
@@ -54,6 +56,33 @@ const TIP_PIECE_STICKERS = [
54
56
  ["F", "R", "D"], // 2 R
55
57
  ["L", "D", "R"], // 3 B
56
58
  ];
59
+ const EDGE_CELLS = buildCells(EDGE_POSITION_SLOTS, EDGE_PIECE_STICKERS, 6, 2);
60
+ const CENTER_CELLS = buildCells(CENTER_POSITION_SLOTS, CENTER_PIECE_STICKERS, 4, 3);
61
+ const TIP_CELLS = buildCells(TIP_POSITION_SLOTS, TIP_PIECE_STICKERS, 4, 3);
62
+ function buildCells(slots, stickersTable, pieceCount, orientationCount) {
63
+ const cells = [];
64
+ for (let position = 0; position < slots.length; position += 1) {
65
+ const byPiece = [];
66
+ for (let piece = 0; piece < pieceCount; piece += 1) {
67
+ const byOrientation = [];
68
+ for (let orientation = 0; orientation < orientationCount; orientation += 1) {
69
+ const stickers = stickersTable[piece];
70
+ const slotList = slots[position];
71
+ const cell = [];
72
+ for (let index = 0; index < slotList.length; index += 1) {
73
+ const [face, stickerIndex] = slotList[index];
74
+ cell.push(FACE_ORDER.indexOf(face));
75
+ cell.push(stickerIndex);
76
+ cell.push(COLOR_INDEX[stickers[(index - orientation + stickers.length) % stickers.length]]);
77
+ }
78
+ byOrientation.push(cell);
79
+ }
80
+ byPiece.push(byOrientation);
81
+ }
82
+ cells.push(byPiece);
83
+ }
84
+ return cells;
85
+ }
57
86
  function createSolvedPyraminxFacelets() {
58
87
  return {
59
88
  F: Array.from({ length: 9 }, () => "F"),
@@ -62,36 +91,33 @@ function createSolvedPyraminxFacelets() {
62
91
  D: Array.from({ length: 9 }, () => "D"),
63
92
  };
64
93
  }
94
+ // Sparse arrays: every one of the 36 sticker cells is overwritten by the
95
+ // three placement loops below (12 edges + 12 centers + 12 tips), so no fill
96
+ // is needed. Coverage is enforced by test/pyraminx-facelets.test.ts.
65
97
  function createEmptyPyraminxFacelets() {
66
98
  return {
67
- F: Array(9).fill("?"),
68
- L: Array(9).fill("?"),
69
- R: Array(9).fill("?"),
70
- D: Array(9).fill("?"),
99
+ F: Array(9),
100
+ L: Array(9),
101
+ R: Array(9),
102
+ D: Array(9),
71
103
  };
72
104
  }
73
- function placeOrientedStickers(facelets, slots, stickers, orientation) {
74
- for (let index = 0; index < slots.length; index += 1) {
75
- const [face, stickerIndex] = slots[index];
76
- const sticker = stickers[(index - orientation + stickers.length) % stickers.length];
77
- const arr = facelets[face];
78
- arr[stickerIndex] = sticker;
105
+ function placeCells(faces, cells) {
106
+ for (let cellIndex = 0; cellIndex < cells.length; cellIndex += 3) {
107
+ faces[cells[cellIndex]][cells[cellIndex + 1]] = COLORS[cells[cellIndex + 2]];
79
108
  }
80
109
  }
81
110
  function toPyraminxFacelets(state) {
82
111
  const facelets = createEmptyPyraminxFacelets();
112
+ const faces = [facelets.F, facelets.L, facelets.R, facelets.D];
83
113
  for (let position = 0; position < 6; position += 1) {
84
- const piece = state.edgesPerm[position];
85
- const orientation = state.edgesOrient[position];
86
- placeOrientedStickers(facelets, EDGE_POSITION_SLOTS[position], EDGE_PIECE_STICKERS[piece], orientation);
114
+ placeCells(faces, EDGE_CELLS[position][state.edgesPerm[position]][state.edgesOrient[position]]);
87
115
  }
88
116
  for (let position = 0; position < 4; position += 1) {
89
- const orientation = state.centers[position];
90
- placeOrientedStickers(facelets, CENTER_POSITION_SLOTS[position], CENTER_PIECE_STICKERS[position], orientation);
117
+ placeCells(faces, CENTER_CELLS[position][position][state.centers[position]]);
91
118
  }
92
119
  for (let position = 0; position < 4; position += 1) {
93
- const orientation = state.tips[position];
94
- placeOrientedStickers(facelets, TIP_POSITION_SLOTS[position], TIP_PIECE_STICKERS[position], orientation);
120
+ placeCells(faces, TIP_CELLS[position][position][state.tips[position]]);
95
121
  }
96
122
  return facelets;
97
123
  }
@@ -2,41 +2,71 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateRandomPyraminxState = generateRandomPyraminxState;
4
4
  const index_js_1 = require("../../random/index.js");
5
- const validation_js_1 = require("./validation.js");
5
+ const state_js_1 = require("./state.js");
6
+ // Compact -> full Lehmer index for the 360 even permutations of 6 edges.
7
+ // Generated once: a permutation's parity is the parity of the digit sum of
8
+ // its Lehmer code (each digit counts smaller elements to its right).
9
+ const COMPACT_TO_FULL = new Uint16Array(360);
10
+ (function buildCompactMapping() {
11
+ let compact = 0;
12
+ for (let full = 0; full < 720 && compact < 360; full += 1) {
13
+ let value = full;
14
+ const c5 = (value / 120) | 0;
15
+ value -= c5 * 120;
16
+ const c4 = (value / 24) | 0;
17
+ value -= c4 * 24;
18
+ const c3 = (value / 6) | 0;
19
+ value -= c3 * 6;
20
+ const c2 = (value / 2) | 0;
21
+ value -= c2 * 2;
22
+ const c1 = value;
23
+ if ((c5 + c4 + c3 + c2 + c1) % 2 === 0) {
24
+ COMPACT_TO_FULL[compact] = full;
25
+ compact += 1;
26
+ }
27
+ }
28
+ })();
6
29
  function generateRandomPyraminxState(options = {}) {
7
30
  const random = (0, index_js_1.createRandomSource)(options.seed);
8
31
  const rejectTrivial = options.rejectTrivial ?? true;
9
32
  for (let attempt = 0; attempt < 100; attempt += 1) {
33
+ // The state is legal by construction (even permutation parity, even
34
+ // orientation sum, values in range), so only the trivial core check is
35
+ // needed here — no full re-validation.
10
36
  const state = createLegalRandomState(random);
11
- if (!rejectTrivial || (0, validation_js_1.isAllowedRandomPyraminxState)(state)) {
37
+ if (!rejectTrivial || !(0, state_js_1.isSolvedPyraminxState)(state)) {
12
38
  return state;
13
39
  }
14
40
  }
15
41
  throw new Error("Unable to generate a non-trivial random Pyraminx state.");
16
42
  }
17
43
  function createLegalRandomState(random) {
18
- // 4 random centers
19
- const centers = Array.from({ length: 4 }, () => random.nextInt(3));
20
- // 4 random tips
21
- const tips = Array.from({ length: 4 }, () => random.nextInt(3));
22
- // 6 edges permutation
23
- const edgesPerm = shuffledRange(6, random);
24
- // Ensure even parity
25
- if ((0, validation_js_1.permutationParity)(edgesPerm) !== 0) {
26
- // Swap the first two elements to flip parity
27
- const temp = edgesPerm[0];
28
- edgesPerm[0] = edgesPerm[1];
29
- edgesPerm[1] = temp;
44
+ // 4 random centers from one uniform value in [0, 81) (4 base-3 digits)
45
+ let centerValue = random.nextInt(81);
46
+ const centers = [0, 0, 0, 0];
47
+ for (let index = 0; index < 4; index += 1) {
48
+ centers[index] = centerValue % 3;
49
+ centerValue = (centerValue / 3) | 0;
50
+ }
51
+ // 4 random tips from one uniform value in [0, 81)
52
+ let tipValue = random.nextInt(81);
53
+ const tips = [0, 0, 0, 0];
54
+ for (let index = 0; index < 4; index += 1) {
55
+ tips[index] = tipValue % 3;
56
+ tipValue = (tipValue / 3) | 0;
30
57
  }
31
- // 6 edges orientation (sum must be even)
32
- const edgesOrient = Array(6).fill(0);
58
+ // 6 edges permutation, even parity, uniform over the 360 even permutations
59
+ const edgesPerm = permutationFromCompact(random.nextInt(360));
60
+ // 6 edges orientation (sum must be even): 5 free bits + parity digit
61
+ let orientationValue = random.nextInt(32);
62
+ const edgesOrient = [0, 0, 0, 0, 0, 0];
33
63
  let sumOrient = 0;
34
- for (let i = 0; i < 5; i++) {
35
- const val = random.nextInt(2);
36
- edgesOrient[i] = val;
37
- sumOrient += val;
64
+ for (let index = 0; index < 5; index += 1) {
65
+ edgesOrient[index] = orientationValue & 1;
66
+ sumOrient += orientationValue & 1;
67
+ orientationValue >>= 1;
38
68
  }
39
- edgesOrient[5] = sumOrient % 2 === 0 ? 0 : 1;
69
+ edgesOrient[5] = sumOrient & 1;
40
70
  return {
41
71
  centers,
42
72
  edgesPerm,
@@ -44,13 +74,17 @@ function createLegalRandomState(random) {
44
74
  tips
45
75
  };
46
76
  }
47
- function shuffledRange(length, random) {
48
- const values = Array.from({ length }, (_, index) => index);
49
- for (let index = values.length - 1; index > 0; index -= 1) {
50
- const swapIndex = random.nextInt(index + 1);
51
- const temporary = values[index];
52
- values[index] = values[swapIndex];
53
- values[swapIndex] = temporary;
77
+ const FACTORIALS = [1, 1, 2, 6, 24, 120, 720];
78
+ function permutationFromCompact(compact) {
79
+ let full = COMPACT_TO_FULL[compact];
80
+ const permutation = [0, 0, 0, 0, 0, 0];
81
+ const remaining = [0, 1, 2, 3, 4, 5];
82
+ for (let position = 0; position < 6; position += 1) {
83
+ const divisor = FACTORIALS[5 - position];
84
+ const choice = (full / divisor) | 0;
85
+ full -= choice * divisor;
86
+ permutation[position] = remaining[choice];
87
+ remaining.splice(choice, 1);
54
88
  }
55
- return values;
89
+ return permutation;
56
90
  }
@@ -3,12 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.pyraminxScrambleGenerator = void 0;
4
4
  exports.generatePyraminxScramble = generatePyraminxScramble;
5
5
  const random_state_js_1 = require("./random-state.js");
6
- const moves_js_1 = require("./moves.js");
7
6
  const index_js_1 = require("./solver/index.js");
8
7
  const facelets_js_1 = require("./facelets.js");
9
8
  const types_js_1 = require("./types.js");
10
- // Face -> tip index (U,L,R,B order, same as PYRAMINX_TIPS).
11
- const FACE_TIP_INDEX = { U: 0, L: 1, R: 2, B: 3 };
9
+ const TIP_INVERSE = ["u'", "l'", "r'", "b'"];
12
10
  exports.pyraminxScrambleGenerator = {
13
11
  info: {
14
12
  eventId: "pymx",
@@ -24,47 +22,58 @@ exports.pyraminxScrambleGenerator = {
24
22
  };
25
23
  async function generatePyraminxScramble(options = {}) {
26
24
  const state = (0, random_state_js_1.generateRandomPyraminxState)(options);
27
- // Solve the core edges and centers
28
- const solution = await (0, index_js_1.solvePyraminx)(state);
29
- const scrambleMoves = (0, moves_js_1.invertPyraminxAlgorithm)(solution);
30
- // Append tips: layer moves already rotate tips, so we need the delta
31
- // between the target tips and the tips produced by the core scramble.
32
- // Computed arithmetically from the move sequence (no state replay).
25
+ // Solve the core edges and centers as move indices (0..7). For index m the
26
+ // inverse is m ^ 1, the face is m >> 1 (0=U, 1=L, 2=R, 3=B) and the
27
+ // direction is m & 1 (1 = clockwise, 0 = counter-clockwise).
28
+ const solution = (0, index_js_1.arePyraminxTablesLoaded)()
29
+ ? (0, index_js_1.solvePyraminxIndicesSync)(state)
30
+ : await (0, index_js_1.solvePyraminxIndices)(state);
31
+ const depth = solution.length;
32
+ // Single pass over the solution (reversed): build the scramble move strings
33
+ // while accumulating the tips rotated by the layer moves arithmetically
34
+ // (no state replay, no string parsing).
35
+ const moves = new Array(depth + 4);
33
36
  const coreTips = [0, 0, 0, 0];
34
- for (const move of scrambleMoves) {
35
- const isInverse = move.endsWith("'");
36
- const face = isInverse ? move.slice(0, -1) : move;
37
- const tipIndex = FACE_TIP_INDEX[face];
38
- if (tipIndex !== undefined) {
39
- coreTips[tipIndex] = (coreTips[tipIndex] + (isInverse ? 2 : 1)) % 3;
40
- }
37
+ let count = 0;
38
+ for (let index = depth - 1; index >= 0; index -= 1) {
39
+ const m = solution[index] ^ 1;
40
+ moves[count++] = index_js_1.PYRAMINX_MOVES[m];
41
+ const face = m >> 1;
42
+ coreTips[face] = (coreTips[face] + (m & 1 ? 2 : 1)) % 3;
41
43
  }
42
- const finalMoves = [...scrambleMoves];
43
- for (let i = 0; i < 4; i++) {
44
+ const tipMoves = [];
45
+ for (let i = 0; i < 4; i += 1) {
44
46
  const delta = (state.tips[i] - coreTips[i] + 3) % 3;
45
47
  if (delta === 1) {
46
- finalMoves.push(types_js_1.PYRAMINX_TIPS[i]);
48
+ moves[count++] = types_js_1.PYRAMINX_TIPS[i];
49
+ tipMoves.push(i * 2);
47
50
  }
48
51
  else if (delta === 2) {
49
- finalMoves.push(`${types_js_1.PYRAMINX_TIPS[i]}'`);
52
+ moves[count++] = TIP_INVERSE[i];
53
+ tipMoves.push(i * 2 + 1);
50
54
  }
51
55
  }
52
- if (!options.skipValidation && !(0, index_js_1.verifyPyraminxScramble)(state, finalMoves)) {
56
+ moves.length = count;
57
+ if (!options.skipValidation && !(0, index_js_1.verifyPyraminxScrambleIndices)(state, solution, tipMoves)) {
53
58
  throw new Error("Generated Pyraminx scramble does not reproduce the target random state.");
54
59
  }
55
60
  const facelets = (0, facelets_js_1.toPyraminxFacelets)(state);
61
+ const solutionMoves = new Array(depth);
62
+ for (let index = 0; index < depth; index += 1) {
63
+ solutionMoves[index] = index_js_1.PYRAMINX_MOVES[solution[index]];
64
+ }
56
65
  return {
57
66
  eventId: "pymx",
58
- scramble: finalMoves.join(" "),
59
- moves: finalMoves,
67
+ scramble: moves.join(" "),
68
+ moves,
60
69
  quality: "random-state",
61
70
  generator: "pyraminx-optimal",
62
71
  metadata: {
63
72
  state,
64
73
  facelets,
65
74
  faceletString: (0, facelets_js_1.formatPyraminxFacelets)(facelets),
66
- solutionMoves: solution,
67
- solutionDepth: solution.length
75
+ solutionMoves,
76
+ solutionDepth: depth
68
77
  }
69
78
  };
70
79
  }
@@ -2,8 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.solvePyraminx = solvePyraminx;
4
4
  exports.solvePyraminxWithInfo = solvePyraminxWithInfo;
5
+ exports.solvePyraminxIndices = solvePyraminxIndices;
6
+ exports.solvePyraminxIndicesSync = solvePyraminxIndicesSync;
5
7
  exports.solvePyraminxSync = solvePyraminxSync;
6
8
  exports.verifyPyraminxScramble = verifyPyraminxScramble;
9
+ exports.verifyPyraminxScrambleIndices = verifyPyraminxScrambleIndices;
7
10
  const tables_js_1 = require("./tables.js");
8
11
  async function solvePyraminx(state) {
9
12
  await (0, tables_js_1.ensureTablesLoaded)();
@@ -13,7 +16,25 @@ async function solvePyraminxWithInfo(state) {
13
16
  await (0, tables_js_1.ensureTablesLoaded)();
14
17
  return solveWithTables(state);
15
18
  }
19
+ // Index-based solve for the hot scramble path: returns the solution as move
20
+ // indices (0..7) in walk order. The inverse of index m is m ^ 1 (moves are
21
+ // paired U/U', L/L', R/R', B/B'), its face is m >> 1 and its direction is
22
+ // m & 1 (1 = clockwise, 0 = counter-clockwise).
23
+ async function solvePyraminxIndices(state) {
24
+ await (0, tables_js_1.ensureTablesLoaded)();
25
+ return solveWithTablesIndices(state);
26
+ }
27
+ function solvePyraminxIndicesSync(state) {
28
+ if (!tables_js_1.POLICY_TABLE)
29
+ throw new Error("Pyraminx tables not loaded");
30
+ return solveWithTablesIndices(state);
31
+ }
16
32
  function solveWithTables(state) {
33
+ const indices = solveWithTablesIndices(state);
34
+ const moves = indices.map((index) => tables_js_1.PYRAMINX_MOVES[index]);
35
+ return { moves, depth: moves.length };
36
+ }
37
+ function solveWithTablesIndices(state) {
17
38
  const centersTable = tables_js_1.CENTERS_TABLE;
18
39
  const permTable = tables_js_1.PERMUTATION_TABLE;
19
40
  const orientTable = tables_js_1.ORIENTATION_TABLE;
@@ -23,17 +44,20 @@ function solveWithTables(state) {
23
44
  let o = (0, tables_js_1.getOrientationIndex)(state.edgesOrient);
24
45
  let stateIndex = c + 81 * (p + tables_js_1.PERMUTATION_COUNT * o);
25
46
  if (stateIndex === 0)
26
- return { moves: [], depth: 0 };
47
+ return [];
27
48
  const moves = [];
28
49
  while (stateIndex !== 0) {
29
- const m = (0, tables_js_1.getPolicyMoveFromPacked)(policy, stateIndex);
30
- moves.push(tables_js_1.PYRAMINX_MOVES[m]);
50
+ const bits = stateIndex * 3;
51
+ const byteIndex = bits >> 3;
52
+ const offset = bits & 7;
53
+ const m = ((policy[byteIndex] | ((policy[byteIndex + 1] ?? 0) << 8)) >> offset) & 0x07;
54
+ moves.push(m);
31
55
  c = centersTable[c * tables_js_1.MOVE_COUNT + m];
32
56
  p = permTable[p * tables_js_1.MOVE_COUNT + m];
33
57
  o = orientTable[o * tables_js_1.MOVE_COUNT + m];
34
58
  stateIndex = c + 81 * (p + tables_js_1.PERMUTATION_COUNT * o);
35
59
  }
36
- return { moves, depth: moves.length };
60
+ return moves;
37
61
  }
38
62
  // Synchronous variant for scripts that have already ensured tables are loaded
39
63
  function solvePyraminxSync(state) {
@@ -86,3 +110,37 @@ function verifyPyraminxScramble(state, finalMoves) {
86
110
  tips[2] === state.tips[2] &&
87
111
  tips[3] === state.tips[3]);
88
112
  }
113
+ // Numeric verification for the hot scramble path: replays the scramble
114
+ // (solution reversed with m ^ 1 inverses, tip moves as tipIndex * 2 + dir
115
+ // with dir 0 = clockwise, 1 = counter-clockwise) without any string parsing.
116
+ function verifyPyraminxScrambleIndices(state, solution, tipMoves) {
117
+ const centersTable = tables_js_1.CENTERS_TABLE;
118
+ const permTable = tables_js_1.PERMUTATION_TABLE;
119
+ const orientTable = tables_js_1.ORIENTATION_TABLE;
120
+ if (!centersTable || !permTable || !orientTable) {
121
+ throw new Error("Pyraminx solver tables not loaded. Call ensureTablesLoaded() first.");
122
+ }
123
+ let c = 0;
124
+ let p = 0;
125
+ let o = 0;
126
+ const tips = [0, 0, 0, 0];
127
+ for (let index = solution.length - 1; index >= 0; index -= 1) {
128
+ const m = solution[index] ^ 1;
129
+ c = centersTable[c * tables_js_1.MOVE_COUNT + m];
130
+ p = permTable[p * tables_js_1.MOVE_COUNT + m];
131
+ o = orientTable[o * tables_js_1.MOVE_COUNT + m];
132
+ const face = m >> 1;
133
+ tips[face] = (tips[face] + (m & 1 ? 2 : 1)) % 3;
134
+ }
135
+ for (const tipMove of tipMoves) {
136
+ const tipIndex = tipMove >> 1;
137
+ tips[tipIndex] = (tips[tipIndex] + (tipMove & 1 ? 2 : 1)) % 3;
138
+ }
139
+ return (c === (0, tables_js_1.getCentersIndex)(state.centers) &&
140
+ p === (0, tables_js_1.getPermutationCompactIndex)(state.edgesPerm) &&
141
+ o === (0, tables_js_1.getOrientationIndex)(state.edgesOrient) &&
142
+ tips[0] === state.tips[0] &&
143
+ tips[1] === state.tips[1] &&
144
+ tips[2] === state.tips[2] &&
145
+ tips[3] === state.tips[3]);
146
+ }
@@ -20,6 +20,7 @@ exports.generateOrientationTable = generateOrientationTable;
20
20
  exports.generatePolicyTable = generatePolicyTable;
21
21
  exports.checksum = checksum;
22
22
  exports.ensureTablesLoaded = ensureTablesLoaded;
23
+ exports.arePyraminxTablesLoaded = arePyraminxTablesLoaded;
23
24
  exports.getPolicyMove = getPolicyMove;
24
25
  exports.getCentersTransition = getCentersTransition;
25
26
  exports.getPermutationTransition = getPermutationTransition;
@@ -298,6 +299,9 @@ async function ensureTablesLoaded() {
298
299
  exports.POLICY_TABLE = tables.policyTable;
299
300
  tablesLoaded = true;
300
301
  }
302
+ function arePyraminxTablesLoaded() {
303
+ return tablesLoaded;
304
+ }
301
305
  function getPolicyMove(stateIndex) {
302
306
  if (!exports.POLICY_TABLE)
303
307
  throw new Error("Pyraminx solver tables not loaded. Call ensureTablesLoaded() first.");
@@ -21,18 +21,32 @@ exports.skewbScrambleGenerator = {
21
21
  };
22
22
  async function generateSkewbScramble(options = {}) {
23
23
  const state = await (0, random_state_js_1.generateRandomSkewbState)(options);
24
- const solution = await (0, index_js_1.solveSkewb)(state);
25
- const scrambleMoves = (0, moves_js_1.invertSkewbAlgorithm)(solution);
24
+ // generateRandomSkewbState guarantees the tables are loaded, so use the
25
+ // sync solve on every call after the first (no extra microtask).
26
+ const solution = (0, index_js_1.areSkewbTablesLoaded)()
27
+ ? (0, index_js_1.solveSkewbIndicesSync)(state)
28
+ : await (0, index_js_1.solveSkewbIndices)(state);
29
+ const depth = solution.length;
30
+ // Scramble = inverse of the solution, reversed. For index m the inverse is
31
+ // m ^ 1 (moves are paired R/R', U/U', L/L', B/B').
32
+ const scrambleMoves = new Array(depth);
33
+ for (let index = 0; index < depth; index += 1) {
34
+ scrambleMoves[index] = index_js_1.SKEWB_MOVES[solution[depth - 1 - index] ^ 1];
35
+ }
26
36
  if (!options.skipValidation) {
27
37
  // If the scramble reproduces the target state, its inverse (the
28
38
  // solution) solves it by construction, so a single walk suffices.
29
- if (!(0, index_js_1.verifySkewbScramble)(state, scrambleMoves)) {
39
+ if (!(0, index_js_1.verifySkewbScrambleIndices)(state, solution)) {
30
40
  throw new Error("Generated Skewb scramble does not reproduce the target random state.");
31
41
  }
32
42
  if (scrambleMoves.length < 2)
33
43
  throw new Error("Generated Skewb scramble is too short.");
34
44
  }
35
45
  const facelets = (0, facelets_js_1.toSkewbFacelets)(state);
46
+ const solutionMoves = new Array(depth);
47
+ for (let index = 0; index < depth; index += 1) {
48
+ solutionMoves[index] = index_js_1.SKEWB_MOVES[solution[index]];
49
+ }
36
50
  const result = {
37
51
  eventId: "skwb",
38
52
  scramble: (0, moves_js_1.formatSkewbAlgorithm)(scrambleMoves),
@@ -43,8 +57,8 @@ async function generateSkewbScramble(options = {}) {
43
57
  state,
44
58
  facelets,
45
59
  faceletString: (0, facelets_js_1.formatSkewbFacelets)(facelets),
46
- solutionMoves: solution,
47
- solutionDepth: solution.length,
60
+ solutionMoves,
61
+ solutionDepth: depth,
48
62
  },
49
63
  };
50
64
  if (options.seed !== undefined)
@@ -2,8 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.solveSkewb = solveSkewb;
4
4
  exports.solveSkewbWithInfo = solveSkewbWithInfo;
5
+ exports.solveSkewbIndices = solveSkewbIndices;
6
+ exports.solveSkewbIndicesSync = solveSkewbIndicesSync;
5
7
  exports.solveSkewbSync = solveSkewbSync;
6
8
  exports.verifySkewbScramble = verifySkewbScramble;
9
+ exports.verifySkewbScrambleIndices = verifySkewbScrambleIndices;
7
10
  exports.verifySkewbSolution = verifySkewbSolution;
8
11
  const tables_js_1 = require("./tables.js");
9
12
  async function solveSkewb(state) {
@@ -14,6 +17,19 @@ async function solveSkewbWithInfo(state) {
14
17
  await (0, tables_js_1.ensureTablesLoaded)();
15
18
  return solveWithTables(state);
16
19
  }
20
+ // Index-based solve for the hot scramble path: returns the solution as move
21
+ // indices (0..7) in walk order. The inverse of index m is m ^ 1 (moves are
22
+ // paired R/R', U/U', L/L', B/B').
23
+ async function solveSkewbIndices(state) {
24
+ await (0, tables_js_1.ensureTablesLoaded)();
25
+ return solveWithTablesIndices(state);
26
+ }
27
+ function solveSkewbIndicesSync(state) {
28
+ if (!tables_js_1.POLICY_TABLE) {
29
+ throw new Error("Skewb solver tables not loaded. Call ensureTablesLoaded() first.");
30
+ }
31
+ return solveWithTablesIndices(state);
32
+ }
17
33
  // Synchronous variant for scripts that have already ensured tables are loaded
18
34
  function solveSkewbSync(state) {
19
35
  if (!tables_js_1.POLICY_TABLE) {
@@ -22,6 +38,11 @@ function solveSkewbSync(state) {
22
38
  return solveWithTables(state).moves;
23
39
  }
24
40
  function solveWithTables(state) {
41
+ const indices = solveWithTablesIndices(state);
42
+ const moves = indices.map((index) => tables_js_1.SKEWB_MOVES[index]);
43
+ return { moves, depth: moves.length };
44
+ }
45
+ function solveWithTablesIndices(state) {
25
46
  const policy = tables_js_1.POLICY_TABLE;
26
47
  const permTable = tables_js_1.PERMUTATION_TABLE;
27
48
  const orientTable = tables_js_1.ORIENTATION_TABLE;
@@ -35,18 +56,18 @@ function solveWithTables(state) {
35
56
  let rC = (0, tables_js_1.getCenterIndex)(state.centers);
36
57
  let rank = (0, tables_js_1.rankFromComponents)(r7, rO, rC);
37
58
  if (rank === tables_js_1.SOLVED_RANK) {
38
- return { moves: [], depth: 0 };
59
+ return [];
39
60
  }
40
61
  const moves = [];
41
62
  while (rank !== tables_js_1.SOLVED_RANK) {
42
63
  const m = (0, tables_js_1.getPolicyMoveFromPacked)(policy, rank);
43
- moves.push(tables_js_1.SKEWB_MOVES[m]);
64
+ moves.push(m);
44
65
  r7 = permTable[r7 * tables_js_1.MOVE_COUNT + m];
45
66
  rO = orientTable[rO * tables_js_1.MOVE_COUNT + m];
46
67
  rC = centersTable[rC * tables_js_1.MOVE_COUNT + m];
47
68
  rank = (0, tables_js_1.rankFromComponents)(r7, rO, rC);
48
69
  }
49
- return { moves, depth: moves.length };
70
+ return moves;
50
71
  }
51
72
  // ---------------------------------------------------------------------------
52
73
  // Coordinate-walking verification (no full state clones)
@@ -77,6 +98,28 @@ function verifySkewbScramble(state, scrambleMoves) {
77
98
  final.rO === (0, tables_js_1.getOrientationIndex)(state.corners.orientation) &&
78
99
  final.rC === (0, tables_js_1.getCenterIndex)(state.centers));
79
100
  }
101
+ // Numeric verification for the hot scramble path: replays the scramble (the
102
+ // solution reversed with m ^ 1 inverses) without any string parsing.
103
+ function verifySkewbScrambleIndices(state, solution) {
104
+ const permTable = tables_js_1.PERMUTATION_TABLE;
105
+ const orientTable = tables_js_1.ORIENTATION_TABLE;
106
+ const centersTable = tables_js_1.CENTERS_TABLE;
107
+ if (!permTable || !orientTable || !centersTable) {
108
+ throw new Error("Skewb solver tables not loaded. Call ensureTablesLoaded() first.");
109
+ }
110
+ let r7 = 0;
111
+ let rO = 0;
112
+ let rC = 0;
113
+ for (let index = solution.length - 1; index >= 0; index -= 1) {
114
+ const m = solution[index] ^ 1;
115
+ r7 = permTable[r7 * tables_js_1.MOVE_COUNT + m];
116
+ rO = orientTable[rO * tables_js_1.MOVE_COUNT + m];
117
+ rC = centersTable[rC * tables_js_1.MOVE_COUNT + m];
118
+ }
119
+ return (r7 === (0, tables_js_1.getPerm7Index)(state.corners.permutation) &&
120
+ rO === (0, tables_js_1.getOrientationIndex)(state.corners.orientation) &&
121
+ rC === (0, tables_js_1.getCenterIndex)(state.centers));
122
+ }
80
123
  function verifySkewbSolution(state, solutionMoves) {
81
124
  const final = walkSkewbCoordinates({
82
125
  r7: (0, tables_js_1.getPerm7Index)(state.corners.permutation),