@cubingcenter/scrambler 0.6.3 → 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 (191) hide show
  1. package/README.md +17 -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/cube2/solver/search.d.ts.map +1 -1
  10. package/dist/puzzles/cube2/solver/search.js +3 -3
  11. package/dist/puzzles/cube2/solver/tables.d.ts +6 -0
  12. package/dist/puzzles/cube2/solver/tables.d.ts.map +1 -1
  13. package/dist/puzzles/cube2/solver/tables.js +16 -2
  14. package/dist/puzzles/cube3/index.d.ts +1 -0
  15. package/dist/puzzles/cube3/index.d.ts.map +1 -1
  16. package/dist/puzzles/cube3/moves.d.ts.map +1 -1
  17. package/dist/puzzles/cube3/moves.js +104 -18
  18. package/dist/puzzles/cube3/scrambler.d.ts +16 -1
  19. package/dist/puzzles/cube3/scrambler.d.ts.map +1 -1
  20. package/dist/puzzles/cube3/scrambler.js +1 -1
  21. package/dist/puzzles/cube3/solver/browser-loader.d.ts.map +1 -1
  22. package/dist/puzzles/cube3/solver/browser-loader.js +48 -22
  23. package/dist/puzzles/cube3/solver/generated-assets.d.ts.map +1 -1
  24. package/dist/puzzles/cube3/solver/generated-assets.js +9 -3
  25. package/dist/puzzles/cube3/solver/index.d.ts +5 -3
  26. package/dist/puzzles/cube3/solver/index.d.ts.map +1 -1
  27. package/dist/puzzles/cube3/solver/index.js +2 -1
  28. package/dist/puzzles/cube3/solver/node-loader.js +9 -2
  29. package/dist/puzzles/cube3/solver/phase1.d.ts +37 -0
  30. package/dist/puzzles/cube3/solver/phase1.d.ts.map +1 -1
  31. package/dist/puzzles/cube3/solver/phase1.js +156 -15
  32. package/dist/puzzles/cube3/solver/phase2.d.ts +75 -0
  33. package/dist/puzzles/cube3/solver/phase2.d.ts.map +1 -1
  34. package/dist/puzzles/cube3/solver/phase2.js +279 -68
  35. package/dist/puzzles/cube3/solver/search-utils.d.ts +1 -1
  36. package/dist/puzzles/cube3/solver/search-utils.d.ts.map +1 -1
  37. package/dist/puzzles/cube3/solver/symmetry.d.ts +24 -0
  38. package/dist/puzzles/cube3/solver/symmetry.d.ts.map +1 -0
  39. package/dist/puzzles/cube3/solver/symmetry.js +74 -0
  40. package/dist/puzzles/cube3/solver/tables.d.ts +6 -1
  41. package/dist/puzzles/cube3/solver/tables.d.ts.map +1 -1
  42. package/dist/puzzles/cube3/solver/tables.js +14 -12
  43. package/dist/puzzles/cube3/validation.d.ts.map +1 -1
  44. package/dist/puzzles/cube3/validation.js +15 -7
  45. package/dist/puzzles/cube4/browser-worker-pool.d.ts +82 -0
  46. package/dist/puzzles/cube4/browser-worker-pool.d.ts.map +1 -0
  47. package/dist/puzzles/cube4/browser-worker-pool.js +205 -0
  48. package/dist/puzzles/cube4/browser-worker.d.ts +2 -0
  49. package/dist/puzzles/cube4/browser-worker.d.ts.map +1 -0
  50. package/dist/puzzles/cube4/browser-worker.js +31 -0
  51. package/dist/puzzles/cube4/index.d.ts +10 -2
  52. package/dist/puzzles/cube4/index.d.ts.map +1 -1
  53. package/dist/puzzles/cube4/index.js +6 -2
  54. package/dist/puzzles/cube4/moves.d.ts +5 -0
  55. package/dist/puzzles/cube4/moves.d.ts.map +1 -1
  56. package/dist/puzzles/cube4/moves.js +212 -11
  57. package/dist/puzzles/cube4/reduction/browser-loader.d.ts +29 -1
  58. package/dist/puzzles/cube4/reduction/browser-loader.d.ts.map +1 -1
  59. package/dist/puzzles/cube4/reduction/browser-loader.js +227 -32
  60. package/dist/puzzles/cube4/reduction/center2.d.ts +44 -3
  61. package/dist/puzzles/cube4/reduction/center2.d.ts.map +1 -1
  62. package/dist/puzzles/cube4/reduction/center2.js +152 -10
  63. package/dist/puzzles/cube4/reduction/edge3.d.ts +4 -0
  64. package/dist/puzzles/cube4/reduction/edge3.d.ts.map +1 -1
  65. package/dist/puzzles/cube4/reduction/edge3.js +23 -3
  66. package/dist/puzzles/cube4/reduction/finish3.d.ts +53 -0
  67. package/dist/puzzles/cube4/reduction/finish3.d.ts.map +1 -0
  68. package/dist/puzzles/cube4/reduction/finish3.js +393 -0
  69. package/dist/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
  70. package/dist/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
  71. package/dist/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
  72. package/dist/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
  73. package/dist/puzzles/cube4/reduction/generated/hc-manifest.json +44 -0
  74. package/dist/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
  75. package/dist/puzzles/cube4/reduction/hc-pdb.d.ts +52 -0
  76. package/dist/puzzles/cube4/reduction/hc-pdb.d.ts.map +1 -0
  77. package/dist/puzzles/cube4/reduction/hc-pdb.js +220 -0
  78. package/dist/puzzles/cube4/reduction/node-loader.d.ts +15 -1
  79. package/dist/puzzles/cube4/reduction/node-loader.d.ts.map +1 -1
  80. package/dist/puzzles/cube4/reduction/node-loader.js +199 -25
  81. package/dist/puzzles/cube4/reduction/pair-transversal.d.ts +28 -0
  82. package/dist/puzzles/cube4/reduction/pair-transversal.d.ts.map +1 -0
  83. package/dist/puzzles/cube4/reduction/pair-transversal.js +107 -0
  84. package/dist/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
  85. package/dist/puzzles/cube4/reduction/phase3-search.d.ts +116 -7
  86. package/dist/puzzles/cube4/reduction/phase3-search.d.ts.map +1 -1
  87. package/dist/puzzles/cube4/reduction/phase3-search.js +458 -32
  88. package/dist/puzzles/cube4/reduction/rotation-fix.d.ts +28 -0
  89. package/dist/puzzles/cube4/reduction/rotation-fix.d.ts.map +1 -0
  90. package/dist/puzzles/cube4/reduction/rotation-fix.js +212 -0
  91. package/dist/puzzles/cube4/reduction/solve.d.ts +172 -2
  92. package/dist/puzzles/cube4/reduction/solve.d.ts.map +1 -1
  93. package/dist/puzzles/cube4/reduction/solve.js +1201 -180
  94. package/dist/puzzles/cube4/scramble-cache.d.ts +93 -0
  95. package/dist/puzzles/cube4/scramble-cache.d.ts.map +1 -0
  96. package/dist/puzzles/cube4/scramble-cache.js +150 -0
  97. package/dist/puzzles/cube4/scrambler.d.ts +201 -6
  98. package/dist/puzzles/cube4/scrambler.d.ts.map +1 -1
  99. package/dist/puzzles/cube4/scrambler.js +414 -176
  100. package/dist/puzzles/cube4/state.d.ts.map +1 -1
  101. package/dist/puzzles/cube4/state.js +4 -1
  102. package/dist/puzzles/cube4/worker-pool.d.ts +139 -0
  103. package/dist/puzzles/cube4/worker-pool.d.ts.map +1 -0
  104. package/dist/puzzles/cube4/worker-pool.js +405 -0
  105. package/dist/puzzles/cube4/worker-protocol.d.ts +58 -0
  106. package/dist/puzzles/cube4/worker-protocol.d.ts.map +1 -0
  107. package/dist/puzzles/cube4/worker-protocol.js +58 -0
  108. package/dist/puzzles/cube4/worker.d.ts +26 -0
  109. package/dist/puzzles/cube4/worker.d.ts.map +1 -0
  110. package/dist/puzzles/cube4/worker.js +49 -0
  111. package/dist/puzzles/pyraminx/solver/browser-loader.d.ts +4 -8
  112. package/dist/puzzles/pyraminx/solver/browser-loader.d.ts.map +1 -1
  113. package/dist/puzzles/pyraminx/solver/browser-loader.js +44 -15
  114. package/dist/puzzles/pyraminx/solver/generated-assets.d.ts.map +1 -1
  115. package/dist/puzzles/pyraminx/solver/generated-assets.js +8 -3
  116. package/dist/puzzles/pyraminx/solver/search.d.ts.map +1 -1
  117. package/dist/puzzles/pyraminx/solver/search.js +3 -6
  118. package/dist/puzzles/pyraminx/solver/tables.d.ts +6 -0
  119. package/dist/puzzles/pyraminx/solver/tables.d.ts.map +1 -1
  120. package/dist/puzzles/pyraminx/solver/tables.js +17 -0
  121. package/dist/puzzles/shared/compressed-fetch.d.ts +37 -1
  122. package/dist/puzzles/shared/compressed-fetch.d.ts.map +1 -1
  123. package/dist/puzzles/shared/compressed-fetch.js +226 -31
  124. package/dist/puzzles/shared/load-once.d.ts +16 -0
  125. package/dist/puzzles/shared/load-once.d.ts.map +1 -0
  126. package/dist/puzzles/shared/load-once.js +11 -0
  127. package/dist/puzzles/skewb/solver/browser-loader.d.ts +4 -11
  128. package/dist/puzzles/skewb/solver/browser-loader.d.ts.map +1 -1
  129. package/dist/puzzles/skewb/solver/browser-loader.js +44 -16
  130. package/dist/puzzles/skewb/solver/generated-assets.d.ts.map +1 -1
  131. package/dist/puzzles/skewb/solver/generated-assets.js +8 -3
  132. package/dist/puzzles/skewb/solver/search.js +3 -3
  133. package/dist/puzzles/skewb/solver/tables.d.ts +6 -0
  134. package/dist/puzzles/skewb/solver/tables.d.ts.map +1 -1
  135. package/dist/puzzles/skewb/solver/tables.js +17 -0
  136. package/dist/puzzles/sq1/solver/browser-loader.d.ts.map +1 -1
  137. package/dist/puzzles/sq1/solver/browser-loader.js +58 -26
  138. package/dist-cjs/core/api.js +10 -2
  139. package/dist-cjs/puzzles/cube2/solver/browser-loader.js +42 -14
  140. package/dist-cjs/puzzles/cube2/solver/generated-assets.js +8 -3
  141. package/dist-cjs/puzzles/cube2/solver/search.js +2 -2
  142. package/dist-cjs/puzzles/cube2/solver/tables.js +17 -3
  143. package/dist-cjs/puzzles/cube3/moves.js +104 -18
  144. package/dist-cjs/puzzles/cube3/scrambler.js +1 -1
  145. package/dist-cjs/puzzles/cube3/solver/browser-loader.js +48 -22
  146. package/dist-cjs/puzzles/cube3/solver/generated-assets.js +9 -3
  147. package/dist-cjs/puzzles/cube3/solver/index.js +8 -1
  148. package/dist-cjs/puzzles/cube3/solver/node-loader.js +9 -2
  149. package/dist-cjs/puzzles/cube3/solver/phase1.js +157 -15
  150. package/dist-cjs/puzzles/cube3/solver/phase2.js +276 -65
  151. package/dist-cjs/puzzles/cube3/solver/symmetry.js +80 -0
  152. package/dist-cjs/puzzles/cube3/solver/tables.js +14 -12
  153. package/dist-cjs/puzzles/cube3/validation.js +15 -7
  154. package/dist-cjs/puzzles/cube4/browser-worker-pool.js +211 -0
  155. package/dist-cjs/puzzles/cube4/index.js +23 -1
  156. package/dist-cjs/puzzles/cube4/moves.js +213 -11
  157. package/dist-cjs/puzzles/cube4/reduction/browser-loader.js +227 -32
  158. package/dist-cjs/puzzles/cube4/reduction/center2.js +155 -10
  159. package/dist-cjs/puzzles/cube4/reduction/edge3.js +24 -3
  160. package/dist-cjs/puzzles/cube4/reduction/finish3.js +398 -0
  161. package/dist-cjs/puzzles/cube4/reduction/generated/c3_pair_dist.bin +1 -0
  162. package/dist-cjs/puzzles/cube4/reduction/generated/center2-manifest.json +36 -0
  163. package/dist-cjs/puzzles/cube4/reduction/generated/center2-pruning-wide.bin +1 -0
  164. package/dist-cjs/puzzles/cube4/reduction/generated/hc-distances.bin +1 -0
  165. package/dist-cjs/puzzles/cube4/reduction/generated/hc-manifest.json +44 -0
  166. package/dist-cjs/puzzles/cube4/reduction/generated/hc-move.bin +0 -0
  167. package/dist-cjs/puzzles/cube4/reduction/hc-pdb.js +232 -0
  168. package/dist-cjs/puzzles/cube4/reduction/node-loader.js +200 -25
  169. package/dist-cjs/puzzles/cube4/reduction/pair-transversal.js +112 -0
  170. package/dist-cjs/puzzles/cube4/reduction/pairing-coordinate.js +5 -5
  171. package/dist-cjs/puzzles/cube4/reduction/phase3-search.js +460 -33
  172. package/dist-cjs/puzzles/cube4/reduction/rotation-fix.js +216 -0
  173. package/dist-cjs/puzzles/cube4/reduction/solve.js +1203 -172
  174. package/dist-cjs/puzzles/cube4/scramble-cache.js +156 -0
  175. package/dist-cjs/puzzles/cube4/scrambler.js +416 -174
  176. package/dist-cjs/puzzles/cube4/state.js +4 -1
  177. package/dist-cjs/puzzles/cube4/worker-pool.js +447 -0
  178. package/dist-cjs/puzzles/cube4/worker-protocol.js +62 -0
  179. package/dist-cjs/puzzles/pyraminx/solver/browser-loader.js +44 -15
  180. package/dist-cjs/puzzles/pyraminx/solver/generated-assets.js +8 -3
  181. package/dist-cjs/puzzles/pyraminx/solver/search.js +2 -5
  182. package/dist-cjs/puzzles/pyraminx/solver/tables.js +18 -1
  183. package/dist-cjs/puzzles/shared/compressed-fetch.js +229 -31
  184. package/dist-cjs/puzzles/shared/load-once.js +14 -0
  185. package/dist-cjs/puzzles/skewb/solver/browser-loader.js +44 -16
  186. package/dist-cjs/puzzles/skewb/solver/generated-assets.js +8 -3
  187. package/dist-cjs/puzzles/skewb/solver/search.js +2 -2
  188. package/dist-cjs/puzzles/skewb/solver/tables.js +18 -1
  189. package/dist-cjs/puzzles/sq1/solver/browser-loader.js +58 -26
  190. package/docs/development.md +19 -7
  191. package/package.json +17 -7
package/README.md CHANGED
@@ -16,7 +16,7 @@ The package currently ships random-state scramblers for `222` (2x2x2), `333` (3x
16
16
  - 3x3 quality: random-state target + two-phase solver + verified scramble inversion
17
17
  - Pyraminx quality: random-state target + optimal policy-table solver (all 933,120 core states verified)
18
18
  - Skewb quality: random-state target + optimal policy-table solver (all 3,149,280 states; exact uniform sampling)
19
- - 4x4 quality: random-state target (uniform sampler) + reduction solver (4 phases) + verified scramble inversion
19
+ - 4x4 quality: random-state target (uniform sampler) + reduction solver (4 phases) + exact target reproduction, validated against the sampled state on every generation
20
20
 
21
21
  This library is intended for practice, training, and internal CubingCenter use. Do not present it as an official WCA competition scrambler; sanctioned competitions must use the official program required by the delegate/organization.
22
22
 
@@ -59,7 +59,7 @@ console.log(batch.map((item) => item.scramble));
59
59
 
60
60
  `generateScramble("333")` returns a legal random-state 3x3 scramble. Its metadata includes the target cubie state, timer-ready facelets, a 54-character facelet string, and solver diagnostics.
61
61
 
62
- `generateScramble("pymx")` returns a legal random-state Pyraminx scramble. Its metadata includes the target state, facelets, solution moves and depth. Pyraminx/2x2/3x3 accept `{ skipValidation: true }` to skip the per-call verification for maximum throughput (the scramble correctness is covered by exhaustive tests).
62
+ `generateScramble("pymx")` returns a legal random-state Pyraminx scramble. Its metadata includes the target state, facelets, solution moves and depth. Pyraminx/2x2/3x3/444 accept `{ skipValidation: true }` to skip the per-call verification for maximum throughput (correctness is covered by the test suite, and independently by `pnpm verify:scrambles`, which re-samples each target from its seed).
63
63
 
64
64
  ## Scoped imports (web / smaller bundles)
65
65
 
@@ -194,7 +194,17 @@ const paired = cube4.areWingsPaired(state);
194
194
 
195
195
  Supported moves: `U D R L F B` (outer) and `Uw Dw Rw Lw Fw Bw` (wide), each with `'` and `2` suffixes.
196
196
 
197
- The public `generateScramble("444")` path is: sample a uniform random state (`generateRandomCube4State` — corners `8! × 3^7`, wings `24!`, centers as a color multiset `24!/(4!)^6`), solve it with the reduction solver (phases: axis-color face-pairs → ct/rl phase-2 goal → joint center3+pairing → 3x3 finish), and invert the solution — so the scramble always reproduces the sampled target. The pairing pruning table is generated by a C++ tool (`tools/edge3-gen/`).
197
+ The public `generateScramble("444")` path is: sample a uniform random state (`generateRandomCube4State` — corners `8! × 3^7`, wings `24!`, centers as a color multiset `24!/(4!)^6`), solve it with the reduction solver (phases: axis-color face-pairs → ct/rl phase-2 goal → joint center3+pairing → 3x3 finish), and invert the solution — so the scramble always reproduces the sampled target. The production path enforces this on every generation (`assertCube4ScrambleReproducesTarget` re-applies the emitted moves and compares against the state sampled for the seed; a mismatch retries the SAME target — never re-samples, never falls back to random-move). The pairing pruning table is generated by a C++ tool (`tools/edge3-gen/`).
198
+
199
+ How to verify 444, from cheap to heavy:
200
+
201
+ - `pnpm test` — model/round-trip tests, the rotation-sensitive invariant (`test/cube4-solve-invariant.test.ts`), and the uniformity net (`test/cube4-uniformity.test.ts`: χ² over a fixed corpus at two layers — direct sampler N=20,000 and full pipeline N=40 with structural checks on parity quadrants and per-slot diversity).
202
+ - `pnpm verify:scrambles 444` — independent replay: the target is re-sampled from the seed and the solution must genuinely solve it.
203
+ - `node --import tsx scripts/uniformity-444.ts --n=500` — heavy χ² bench over the state **reached** by the pipeline (centers/wings/corners per slot, twist, parity quadrants, joint bins); exits 1 below threshold. The corpora are fixed seeds, so the numbers are deterministic; p-values are reference values, not classical significance.
204
+
205
+ Measured baseline (i5-13420H, warm): 46–53 moves per scramble (avg ≈ 48), latency p50 ≈ 2–3 s / p95 ≈ 9 s, ~37 MB of reduction tables loaded once. Seeded 444 generation is a pure function of `(seed, options)` — search budgets are node counters, not wall deadlines; the only wall-clock code is a pair of hang guards that never bound an observed solve.
206
+
207
+ Shorter 444 scrambles, two mechanisms: `bestOf: N` solves N independently sampled states and keeps the shortest (trades CPU time, biases toward short-solution states), while `restarts: R` re-runs the full pipeline R times over the SAME state with a deterministically reshuffled search order and keeps the shortest total (uniformity-preserving; measured ~-0.45 moves at R=3, ~-0.85 at R=6 on a 24-state corpus, single-attempt mean ~46.1).
198
208
 
199
209
  `validateCube4State` checks the reachability constraints of a normal (non-super) 4x4: corner permutation + orientation sum divisible by 3, wing permutation (wings have no orientation), and 4-of-each-color centers. There is no visible corner/wing parity constraint on a normal 4x4 (all four parity combinations are reachable).
200
210
 
@@ -240,9 +250,12 @@ pnpm package:check
240
250
  pnpm verify
241
251
  ```
242
252
 
243
- Solver tables are versioned as generated binary assets. If cube coordinates, moves, or pruning logic change, regenerate and check them:
253
+ Solver tables are versioned as generated binary assets. If cube coordinates, moves, or pruning logic change, regenerate and check them (the 4x4 reduction tables follow the same pattern, one script per phase):
244
254
 
245
255
  ```sh
246
256
  pnpm cube3:tables
247
257
  pnpm cube3:tables:check
258
+ pnpm cube4:center1:tables:check # likewise center2, center3, edge3, hc
248
259
  ```
260
+
261
+ `pnpm verify` runs lint, the full test suite, the build, every table checksum check (including all five `cube4:*:tables:check`), and the package checks. `pnpm verify:scrambles [--count N] [events…]` additionally replays generated scrambles against independently re-sampled targets and exits 1 on any mismatch.
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/core/api.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCrG;AAED,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,iBAAmC,GAC5C,IAAI,CAEN;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,GAAE,iBAAmC,GAAG,SAAS,EAAE,CAE7F;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,OAAO,EAChB,QAAQ,GAAE,iBAAmC,GAC5C,SAAS,CAQX;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,eAAoB,EAC7B,QAAQ,GAAE,iBAAmC,GAC5C,OAAO,CAAC,cAAc,CAAC,CAiBzB;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,eAAoB,EAC7B,QAAQ,GAAE,iBAAmC,GAC5C,OAAO,CAAC,cAAc,EAAE,CAAC,CAU3B"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/core/api.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4CrG;AAED,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,iBAAmC,GAC5C,IAAI,CAEN;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,GAAE,iBAAmC,GAAG,SAAS,EAAE,CAE7F;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,OAAO,EAChB,QAAQ,GAAE,iBAAmC,GAC5C,SAAS,CAQX;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,eAAoB,EAC7B,QAAQ,GAAE,iBAAmC,GAC5C,OAAO,CAAC,cAAc,CAAC,CAiBzB;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,eAAoB,EAC7B,QAAQ,GAAE,iBAAmC,GAC5C,OAAO,CAAC,cAAc,EAAE,CAAC,CAU3B"}
package/dist/core/api.js CHANGED
@@ -39,8 +39,16 @@ export async function initializeScrambler(baseUrl, eventId) {
39
39
  await loadBrowserTables(baseUrl);
40
40
  }
41
41
  else if (eventId === "444") {
42
- const { loadCube4ReductionTablesBrowser } = await import("../puzzles/cube4/reduction/browser-loader.js");
43
- await loadCube4ReductionTablesBrowser(baseUrl);
42
+ // Reduction ends with a 3x3 solve in the same worker. Initializing 444
43
+ // must also load those required tables, including the caller's base URL.
44
+ const [cube4, cube3] = await Promise.all([
45
+ import("../puzzles/cube4/reduction/browser-loader.js"),
46
+ import("../puzzles/cube3/solver/browser-loader.js")
47
+ ]);
48
+ await Promise.all([
49
+ cube4.loadCube4ReductionTablesBrowser(baseUrl),
50
+ cube3.loadBrowserTables(baseUrl)
51
+ ]);
44
52
  }
45
53
  else if (!eventId) {
46
54
  await Promise.all([
@@ -1,12 +1,9 @@
1
- export declare function getCachedBrowserTables(): {
1
+ export interface Cube2BrowserTables {
2
2
  permutationTable: Uint16Array;
3
3
  orientationTable: Uint16Array;
4
4
  policyTable: Uint8Array;
5
- } | undefined;
5
+ }
6
+ export declare function getCachedBrowserTables(): Cube2BrowserTables | undefined;
6
7
  export declare function clearCachedBrowserTables(): void;
7
- export declare function loadBrowserTables(baseUrl?: string): Promise<{
8
- permutationTable: Uint16Array;
9
- orientationTable: Uint16Array;
10
- policyTable: Uint8Array;
11
- }>;
8
+ export declare function loadBrowserTables(baseUrl?: string): Promise<Cube2BrowserTables>;
12
9
  //# sourceMappingURL=browser-loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/browser-loader.ts"],"names":[],"mappings":"AAWA,wBAAgB,sBAAsB;sBALlB,WAAW;sBACX,WAAW;iBAChB,UAAU;cAKxB;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAED,wBAAsB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACjE,gBAAgB,EAAE,WAAW,CAAC;IAC9B,gBAAgB,EAAE,WAAW,CAAC;IAC9B,WAAW,EAAE,UAAU,CAAC;CACzB,CAAC,CAqBD"}
1
+ {"version":3,"file":"browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/browser-loader.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,kBAAkB;IACjC,gBAAgB,EAAE,WAAW,CAAC;IAC9B,gBAAgB,EAAE,WAAW,CAAC;IAC9B,WAAW,EAAE,UAAU,CAAC;CACzB;AAKD,wBAAgB,sBAAsB,IAAI,kBAAkB,GAAG,SAAS,CAEvE;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C;AAED,wBAAsB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAoBrF"}
@@ -1,23 +1,42 @@
1
1
  import { CUBE2_SOLVER_TABLE_METADATA } from "./table-metadata.js";
2
2
  import { fetchTableBuffer } from "../../shared/compressed-fetch.js";
3
3
  const TABLES_DIR = "puzzles/cube2/solver/generated";
4
+ const CACHE_NAME = "cubingcenter-scrambler-tables-v1";
4
5
  let cachedBrowserTables;
6
+ let cachedBrowserTablesPromise;
5
7
  export function getCachedBrowserTables() {
6
8
  return cachedBrowserTables;
7
9
  }
8
10
  export function clearCachedBrowserTables() {
9
11
  cachedBrowserTables = undefined;
12
+ cachedBrowserTablesPromise = undefined;
10
13
  }
11
14
  export async function loadBrowserTables(baseUrl) {
12
15
  if (cachedBrowserTables) {
13
16
  return cachedBrowserTables;
14
17
  }
18
+ if (cachedBrowserTablesPromise) {
19
+ // One shared in-flight promise: concurrent calls trigger exactly one
20
+ // fetch pass (dedup).
21
+ return cachedBrowserTablesPromise;
22
+ }
23
+ const promise = fetchTables(baseUrl);
24
+ cachedBrowserTablesPromise = promise;
25
+ promise.then(() => { }, () => {
26
+ // A failed load must not poison the dedup slot: purge the rejected
27
+ // promise so a later call retries from scratch.
28
+ if (cachedBrowserTablesPromise === promise)
29
+ cachedBrowserTablesPromise = undefined;
30
+ });
31
+ return promise;
32
+ }
33
+ async function fetchTables(baseUrl) {
15
34
  const resolved = resolveBaseUrl(baseUrl);
16
35
  const manifest = await fetchManifest(resolved);
17
36
  const [permTable, orientTable, dist] = await Promise.all([
18
- fetchUint16Table(resolved, manifest.files.permutationTable, CUBE2_SOLVER_TABLE_METADATA.permutationTable.entries),
19
- fetchUint16Table(resolved, manifest.files.orientationTable, CUBE2_SOLVER_TABLE_METADATA.orientationTable.entries),
20
- fetchUint8Table(resolved, manifest.files.policy, CUBE2_SOLVER_TABLE_METADATA.policy.entries)
37
+ fetchUint16Table(resolved, manifest.files.permutationTable, CUBE2_SOLVER_TABLE_METADATA.permutationTable),
38
+ fetchUint16Table(resolved, manifest.files.orientationTable, CUBE2_SOLVER_TABLE_METADATA.orientationTable),
39
+ fetchUint8Table(resolved, manifest.files.policy, CUBE2_SOLVER_TABLE_METADATA.policy)
21
40
  ]);
22
41
  cachedBrowserTables = {
23
42
  permutationTable: permTable,
@@ -51,22 +70,31 @@ async function fetchManifest(baseUrl) {
51
70
  }
52
71
  return manifest;
53
72
  }
54
- async function fetchUint16Table(baseUrl, entry, expectedEntries) {
55
- const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, { compressed: entry.compressed !== false });
56
- if (buffer.byteLength !== expectedEntries * 2) {
57
- throw new Error(`Invalid byte size for ${entry.file}`);
58
- }
59
- const values = new Uint16Array(expectedEntries);
73
+ /**
74
+ * Fetch + verify with the SAME checks as node-loader.ts: byte length from
75
+ * the manifest entries, FNV-1a checksum against the table-metadata
76
+ * constants (the values node asserts against).
77
+ */
78
+ async function fetchUint16Table(baseUrl, entry, meta) {
79
+ const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
80
+ cacheName: CACHE_NAME,
81
+ compressed: entry.compressed !== false,
82
+ expectedBytes: entry.entries * 2,
83
+ expectedFnv1a: { value: meta.checksum, elementSize: 2 }
84
+ });
85
+ const values = new Uint16Array(entry.entries);
60
86
  const view = new DataView(buffer);
61
87
  for (let i = 0; i < values.length; i++) {
62
88
  values[i] = view.getUint16(i * 2, true);
63
89
  }
64
90
  return values;
65
91
  }
66
- async function fetchUint8Table(baseUrl, entry, expectedEntries) {
67
- const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, { compressed: entry.compressed !== false });
68
- if (buffer.byteLength !== expectedEntries) {
69
- throw new Error(`Invalid byte size for ${entry.file}`);
70
- }
92
+ async function fetchUint8Table(baseUrl, entry, meta) {
93
+ const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
94
+ cacheName: CACHE_NAME,
95
+ compressed: entry.compressed !== false,
96
+ expectedBytes: entry.entries,
97
+ expectedFnv1a: { value: meta.checksum, elementSize: 1 }
98
+ });
71
99
  return new Uint8Array(buffer);
72
100
  }
@@ -1 +1 @@
1
- {"version":3,"file":"generated-assets.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/generated-assets.ts"],"names":[],"mappings":"AAYA,wBAAsB,8BAA8B,IAAI,OAAO,CAAC;IAC9D,gBAAgB,EAAE,WAAW,CAAC;IAC9B,gBAAgB,EAAE,WAAW,CAAC;IAC9B,WAAW,EAAE,UAAU,CAAC;CACzB,CAAC,CAeD"}
1
+ {"version":3,"file":"generated-assets.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/generated-assets.ts"],"names":[],"mappings":"AAkBA,wBAAsB,8BAA8B,IAAI,OAAO,CAAC;IAC9D,gBAAgB,EAAE,WAAW,CAAC;IAC9B,gBAAgB,EAAE,WAAW,CAAC;IAC9B,WAAW,EAAE,UAAU,CAAC;CACzB,CAAC,CAcD"}
@@ -1,5 +1,11 @@
1
1
  import { getCachedBrowserTables } from "./browser-loader.js";
2
- let cachedTables;
2
+ import { loadOnce } from "../../shared/load-once.js";
3
+ // One shared in-flight promise for the node fs load (same pattern as
4
+ // cube3/solver/generated-assets.ts): the previous `cached ??= await …` raced —
5
+ // concurrent callers interleaving on the await both ran `loadNodeTables()`
6
+ // (two filesystem reads, last assignment wins). On rejection the slot is
7
+ // purged so the next call can retry.
8
+ const nodeTablesSlot = { promise: undefined };
3
9
  function isBrowser() {
4
10
  return typeof self !== "undefined";
5
11
  }
@@ -13,8 +19,7 @@ export async function loadGeneratedCube2SolverTables() {
13
19
  "In browser environments, call `initializeScrambler(baseUrl)` " +
14
20
  "before generating scrambles.");
15
21
  }
16
- cachedTables ??= await loadNodeTables();
17
- return cachedTables;
22
+ return loadOnce(nodeTablesSlot, loadNodeTables);
18
23
  }
19
24
  async function loadNodeTables() {
20
25
  const loader = await import(
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAczD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CAQ/E;AAKD,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGjF;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,CAKtE;AAoDD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAUnG;AAGD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAOnG;AAMD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAclG"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAezD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CAQ/E;AAKD,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGjF;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,CAKtE;AAoDD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAUnG;AAGD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAOnG;AAMD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAclG"}
@@ -1,5 +1,5 @@
1
1
  import { createSolvedCube2State } from "../state.js";
2
- import { encodeOrientation, encodePermutation, ensureTablesLoaded, PERMUTATION_TABLE, ORIENTATION_TABLE, POLICY_TABLE, ORIENTATION_COUNT, BFS_MOVES, MOVE_COUNT } from "./tables.js";
2
+ import { encodeOrientation, encodePermutation, ensureTablesLoaded, PERMUTATION_TABLE, ORIENTATION_TABLE, POLICY_TABLE, POLICY_TABLE_EXPANDED, ORIENTATION_COUNT, BFS_MOVES, MOVE_COUNT } from "./tables.js";
3
3
  export async function solveCube2State(state) {
4
4
  await ensureTablesLoaded();
5
5
  const indices = solveWithTablesIndices(state);
@@ -29,7 +29,7 @@ function solveWithTablesIndices(state) {
29
29
  }
30
30
  const permTable = PERMUTATION_TABLE;
31
31
  const orientTable = ORIENTATION_TABLE;
32
- const policy = POLICY_TABLE;
32
+ const policy = POLICY_TABLE_EXPANDED;
33
33
  let permIndex = encodePermutation(state.corners.permutation);
34
34
  let orientIndex = encodeOrientation(state.corners.orientation);
35
35
  const moves = [];
@@ -37,7 +37,7 @@ function solveWithTablesIndices(state) {
37
37
  // candidate scanning (the old BFS-distance gradient walk) is needed.
38
38
  let combined = permIndex * ORIENTATION_COUNT + orientIndex;
39
39
  while (combined !== 0) {
40
- const m = (policy[combined >> 1] >> ((combined & 1) << 2)) & 0x0f;
40
+ const m = policy[combined];
41
41
  moves.push(m);
42
42
  permIndex = permTable[permIndex * MOVE_COUNT + m];
43
43
  orientIndex = orientTable[orientIndex * MOVE_COUNT + m];
@@ -6,6 +6,12 @@ export declare function encodeOrientation(orientation: readonly number[]): numbe
6
6
  export declare let PERMUTATION_TABLE: Uint16Array | null;
7
7
  export declare let ORIENTATION_TABLE: Uint16Array | null;
8
8
  export declare let POLICY_TABLE: Uint8Array | null;
9
+ /**
10
+ * Byte-expanded policy (one optimal move per state, no bit-unpacking in
11
+ * the hot walk). Built at load from the packed table: +3.7 MB RAM,
12
+ * zero wire/format change.
13
+ */
14
+ export declare let POLICY_TABLE_EXPANDED: Uint8Array | null;
9
15
  export declare function ensureTablesLoaded(): Promise<void>;
10
16
  export declare function areCube2TablesLoaded(): boolean;
11
17
  export declare function getDistance(permIndex: number, orientIndex: number): number;
@@ -1 +1 @@
1
- {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/tables.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,UAAU,IAAI,CAAC;AAW5B,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAYxE;AAWD,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAWxE;AAeD,eAAO,IAAI,iBAAiB,EAAE,WAAW,GAAG,IAAW,CAAC;AACxD,eAAO,IAAI,iBAAiB,EAAE,WAAW,GAAG,IAAW,CAAC;AACxD,eAAO,IAAI,YAAY,EAAE,UAAU,GAAG,IAAW,CAAC;AAElD,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAUxD;AAED,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAMD,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAkB1E;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAKhF;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAKlF;AAED,eAAO,MAAM,SAAS,EAAE,SAAS,MAAM,EAItC,CAAC"}
1
+ {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube2/solver/tables.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,UAAU,IAAI,CAAC;AAW5B,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAYxE;AAWD,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAWxE;AAeD,eAAO,IAAI,iBAAiB,EAAE,WAAW,GAAG,IAAW,CAAC;AACxD,eAAO,IAAI,iBAAiB,EAAE,WAAW,GAAG,IAAW,CAAC;AACxD,eAAO,IAAI,YAAY,EAAE,UAAU,GAAG,IAAW,CAAC;AAClD;;;;GAIG;AACH,eAAO,IAAI,qBAAqB,EAAE,UAAU,GAAG,IAAW,CAAC;AAE3D,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAcxD;AAUD,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAMD,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAkB1E;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAKhF;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAKlF;AAED,eAAO,MAAM,SAAS,EAAE,SAAS,MAAM,EAItC,CAAC"}
@@ -54,6 +54,12 @@ let tablesLoaded = false;
54
54
  export let PERMUTATION_TABLE = null;
55
55
  export let ORIENTATION_TABLE = null;
56
56
  export let POLICY_TABLE = null;
57
+ /**
58
+ * Byte-expanded policy (one optimal move per state, no bit-unpacking in
59
+ * the hot walk). Built at load from the packed table: +3.7 MB RAM,
60
+ * zero wire/format change.
61
+ */
62
+ export let POLICY_TABLE_EXPANDED = null;
57
63
  export async function ensureTablesLoaded() {
58
64
  if (tablesLoaded) {
59
65
  return;
@@ -62,8 +68,16 @@ export async function ensureTablesLoaded() {
62
68
  PERMUTATION_TABLE = tables.permutationTable;
63
69
  ORIENTATION_TABLE = tables.orientationTable;
64
70
  POLICY_TABLE = tables.policyTable;
71
+ POLICY_TABLE_EXPANDED = expandNibblePolicy(tables.policyTable, PERMUTATION_COUNT * ORIENTATION_COUNT);
65
72
  tablesLoaded = true;
66
73
  }
74
+ function expandNibblePolicy(packed, count) {
75
+ const expanded = new Uint8Array(count);
76
+ for (let index = 0; index < count; index += 1) {
77
+ expanded[index] = (packed[index >> 1] >> ((index & 1) << 2)) & 0x0f;
78
+ }
79
+ return expanded;
80
+ }
67
81
  export function areCube2TablesLoaded() {
68
82
  return tablesLoaded;
69
83
  }
@@ -71,7 +85,7 @@ export function areCube2TablesLoaded() {
71
85
  // Public API
72
86
  // ---------------------------------------------------------------------------
73
87
  export function getDistance(permIndex, orientIndex) {
74
- if (!POLICY_TABLE) {
88
+ if (!POLICY_TABLE_EXPANDED) {
75
89
  throw new Error("Cube2 solver tables not loaded. Call ensureTablesLoaded() first.");
76
90
  }
77
91
  const permTable = PERMUTATION_TABLE;
@@ -81,7 +95,7 @@ export function getDistance(permIndex, orientIndex) {
81
95
  let combined = p * ORIENTATION_COUNT + o;
82
96
  let distance = 0;
83
97
  while (combined !== 0) {
84
- const m = (POLICY_TABLE[combined >> 1] >> ((combined & 1) << 2)) & 0x0f;
98
+ const m = POLICY_TABLE_EXPANDED[combined];
85
99
  p = permTable[p * MOVE_COUNT + m];
86
100
  o = orientTable[o * MOVE_COUNT + m];
87
101
  combined = p * ORIENTATION_COUNT + o;
@@ -4,6 +4,7 @@ export { cloneCube3State, createSolvedCube3State, cube3StatesEqual, isSolvedCube
4
4
  export { isAllowedRandomCube3State, isOneMoveFromSolvedCube3State, permutationParity, validateCube3State } from "./validation.js";
5
5
  export { generateRandomCube3State } from "./random-state.js";
6
6
  export { cube3ScrambleGenerator, generateCube3Scramble } from "./scrambler.js";
7
+ export type { Cube3ScrambleOptions } from "./scrambler.js";
7
8
  export { formatCube3Facelets, getCube3FaceOrder, toCube3Facelets } from "./facelets.js";
8
9
  export * as solver from "./solver/index.js";
9
10
  export type { Cube3Face, Cube3Facelets, Cube3Move, Cube3FaceMove, Cube3MoveSuffix, Cube3RandomStateOptions, Cube3Sticker, Cube3State, Cube3ValidationResult } from "./types.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube3/index.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,WAAW,EACX,YAAY,EACZ,UAAU,EACX,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,cAAc,EACf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,yBAAyB,EACzB,6BAA6B,EAC7B,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,wBAAwB,EACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EAChB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAC5C,YAAY,EACV,SAAS,EACT,aAAa,EACb,SAAS,EACT,aAAa,EACb,eAAe,EACf,uBAAuB,EACvB,YAAY,EACZ,UAAU,EACV,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube3/index.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,WAAW,EACX,YAAY,EACZ,UAAU,EACX,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,cAAc,EACf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,yBAAyB,EACzB,6BAA6B,EAC7B,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,wBAAwB,EACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACtB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EAChB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAC5C,YAAY,EACV,SAAS,EACT,aAAa,EACb,SAAS,EACT,aAAa,EACb,eAAe,EACf,uBAAuB,EACvB,YAAY,EACZ,UAAU,EACV,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"moves.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube3/moves.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAiB,SAAS,EAAmB,UAAU,EAAE,MAAM,YAAY,CAAC;AAyCxF,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,GAAG,UAAU,CAS7E;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,UAAU,CAE9F;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,EAAE,CAE7E;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,CAoFlE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,CAExE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAMvD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAQ1D"}
1
+ {"version":3,"file":"moves.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube3/moves.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAiB,SAAS,EAAmB,UAAU,EAAE,MAAM,YAAY,CAAC;AAyGxF,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,GAAG,UAAU,CAsB7E;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,UAAU,CA+B9F;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,EAAE,CAE7E;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,CAoFlE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,MAAM,CAExE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAMvD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAQ1D"}
@@ -26,16 +26,114 @@ const FACE_MOVE_TABLES = {
26
26
  edges: { permutation: [0, 1, 2, 11, 4, 5, 6, 10, 8, 9, 3, 7], orientation: [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1] }
27
27
  }
28
28
  };
29
- export function applyCube3Move(state, move) {
29
+ function quarterEffect(face) {
30
+ const table = FACE_MOVE_TABLES[face];
31
+ return {
32
+ cornerPermutation: table.corners.permutation,
33
+ cornerOrientation: table.corners.orientation,
34
+ edgePermutation: table.edges.permutation,
35
+ edgeOrientation: table.edges.orientation
36
+ };
37
+ }
38
+ // Compose two effects (apply `first`, then `second`) into one so a move of
39
+ // any suffix costs exactly one pass over the state — no per-quarter-turn
40
+ // cloning, no intermediate states, no closure allocations.
41
+ function composeEffects(first, second) {
42
+ const cornerPermutation = new Array(8);
43
+ const cornerOrientation = new Array(8);
44
+ const edgePermutation = new Array(12);
45
+ const edgeOrientation = new Array(12);
46
+ for (let i = 0; i < 8; i++) {
47
+ const source = second.cornerPermutation[i];
48
+ cornerPermutation[i] = first.cornerPermutation[source];
49
+ cornerOrientation[i] = (first.cornerOrientation[source] + second.cornerOrientation[i]) % 3;
50
+ }
51
+ for (let i = 0; i < 12; i++) {
52
+ const source = second.edgePermutation[i];
53
+ edgePermutation[i] = first.edgePermutation[source];
54
+ edgeOrientation[i] = (first.edgeOrientation[source] + second.edgeOrientation[i]) % 2;
55
+ }
56
+ return { cornerPermutation, cornerOrientation, edgePermutation, edgeOrientation };
57
+ }
58
+ function powerEffect(face, turns) {
59
+ const quarter = quarterEffect(face);
60
+ if (turns === 1)
61
+ return quarter;
62
+ const half = composeEffects(quarter, quarter);
63
+ if (turns === 2)
64
+ return half;
65
+ return composeEffects(half, quarter);
66
+ }
67
+ // Total effect per move string, built once at module load (mirrors the 4x4
68
+ // fast path): one lookup + one pass per move.
69
+ const COMPOSED_EFFECTS = Object.fromEntries(CUBE3_MOVES.map((move) => {
70
+ const face = move[0];
71
+ const suffix = move.slice(1);
72
+ const turns = suffix === "2" ? 2 : suffix === "'" ? 3 : 1;
73
+ return [move, powerEffect(face, turns)];
74
+ }));
75
+ function effectForMove(move) {
76
+ const hit = COMPOSED_EFFECTS[move];
77
+ if (hit !== undefined)
78
+ return hit;
30
79
  const { face, turns } = parseMoveParts(move);
31
- let next = cloneCube3State(state);
32
- for (let index = 0; index < turns; index += 1) {
33
- next = applyFaceMove(next, FACE_MOVE_TABLES[face]);
80
+ return powerEffect(face, turns);
81
+ }
82
+ export function applyCube3Move(state, move) {
83
+ const effect = effectForMove(move);
84
+ const cornerPermutation = new Array(8);
85
+ const cornerOrientation = new Array(8);
86
+ const edgePermutation = new Array(12);
87
+ const edgeOrientation = new Array(12);
88
+ for (let i = 0; i < 8; i++) {
89
+ const source = effect.cornerPermutation[i];
90
+ cornerPermutation[i] = state.corners.permutation[source];
91
+ cornerOrientation[i] =
92
+ (state.corners.orientation[source] + effect.cornerOrientation[i]) % 3;
34
93
  }
35
- return next;
94
+ for (let i = 0; i < 12; i++) {
95
+ const source = effect.edgePermutation[i];
96
+ edgePermutation[i] = state.edges.permutation[source];
97
+ edgeOrientation[i] =
98
+ (state.edges.orientation[source] + effect.edgeOrientation[i]) % 2;
99
+ }
100
+ return {
101
+ corners: { permutation: cornerPermutation, orientation: cornerOrientation },
102
+ edges: { permutation: edgePermutation, orientation: edgeOrientation }
103
+ };
36
104
  }
37
105
  export function applyCube3Algorithm(state, moves) {
38
- return moves.reduce((current, move) => applyCube3Move(current, move), state);
106
+ if (moves.length === 0)
107
+ return cloneCube3State(state);
108
+ let curCp = [...state.corners.permutation];
109
+ let curCo = [...state.corners.orientation];
110
+ let curEp = [...state.edges.permutation];
111
+ let curEo = [...state.edges.orientation];
112
+ let nxtCp = new Array(8);
113
+ let nxtCo = new Array(8);
114
+ let nxtEp = new Array(12);
115
+ let nxtEo = new Array(12);
116
+ for (const move of moves) {
117
+ const effect = effectForMove(move);
118
+ for (let i = 0; i < 8; i++) {
119
+ const source = effect.cornerPermutation[i];
120
+ nxtCp[i] = curCp[source];
121
+ nxtCo[i] = (curCo[source] + effect.cornerOrientation[i]) % 3;
122
+ }
123
+ for (let i = 0; i < 12; i++) {
124
+ const source = effect.edgePermutation[i];
125
+ nxtEp[i] = curEp[source];
126
+ nxtEo[i] = (curEo[source] + effect.edgeOrientation[i]) % 2;
127
+ }
128
+ [curCp, nxtCp] = [nxtCp, curCp];
129
+ [curCo, nxtCo] = [nxtCo, curCo];
130
+ [curEp, nxtEp] = [nxtEp, curEp];
131
+ [curEo, nxtEo] = [nxtEo, curEo];
132
+ }
133
+ return {
134
+ corners: { permutation: curCp, orientation: curCo },
135
+ edges: { permutation: curEp, orientation: curEo }
136
+ };
39
137
  }
40
138
  export function invertCube3Algorithm(moves) {
41
139
  return [...moves].reverse().map(invertCube3Move);
@@ -190,18 +288,6 @@ export function invertCube3Move(move) {
190
288
  }
191
289
  return `${face}${suffix === "'" ? "" : "'"}`;
192
290
  }
193
- function applyFaceMove(state, table) {
194
- return {
195
- corners: {
196
- permutation: table.corners.permutation.map((source) => state.corners.permutation[source]),
197
- orientation: table.corners.permutation.map((source, target) => ((state.corners.orientation[source] + table.corners.orientation[target]) % 3))
198
- },
199
- edges: {
200
- permutation: table.edges.permutation.map((source) => state.edges.permutation[source]),
201
- orientation: table.edges.permutation.map((source, target) => ((state.edges.orientation[source] + table.edges.orientation[target]) % 2))
202
- }
203
- };
204
- }
205
291
  function parseMoveParts(move) {
206
292
  const face = move[0];
207
293
  const suffix = move.slice(1);
@@ -1,6 +1,21 @@
1
1
  import type { ScrambleGenerator, ScrambleOptions, ScrambleResult } from "../../core/index.js";
2
+ import type { Cube3SolveOptions } from "./solver/index.js";
3
+ /**
4
+ * 333-specific scramble options (pass-through; extra keys are ignored by
5
+ * the generic `generateScramble` API surface).
6
+ */
7
+ export interface Cube3ScrambleOptions {
8
+ /**
9
+ * Solver tuning. Tiers (browser tables): `fast` (~14 ms, ~23.1 moves),
10
+ * `short` default (~36 ms, ~21.9 moves), `best` (~116 ms, ~20.8 moves,
11
+ * max 22). Granular overrides: `phase1ExtraDepth`,
12
+ * `phase1SolutionLimit`, `improvePhase2TimeoutMs`, `orientations`
13
+ * (1-24), `targetLength`, `timeoutMs`.
14
+ */
15
+ solver?: Cube3SolveOptions;
16
+ }
2
17
  export declare const cube3ScrambleGenerator: ScrambleGenerator;
3
- export declare function generateCube3Scramble(options?: ScrambleOptions & {
18
+ export declare function generateCube3Scramble(options?: ScrambleOptions & Cube3ScrambleOptions & {
4
19
  skipValidation?: boolean;
5
20
  }): Promise<ScrambleResult>;
6
21
  //# sourceMappingURL=scrambler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube3/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAW9F,eAAO,MAAM,sBAAsB,EAAE,iBAYpC,CAAC;AAEF,wBAAsB,qBAAqB,CACzC,OAAO,GAAE,eAAe,GAAG;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3D,OAAO,CAAC,cAAc,CAAC,CAgDzB"}
1
+ {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube3/scrambler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAU9F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED,eAAO,MAAM,sBAAsB,EAAE,iBAYpC,CAAC;AAEF,wBAAsB,qBAAqB,CACzC,OAAO,GAAE,eAAe,GAAG,oBAAoB,GAAG;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GAClF,OAAO,CAAC,cAAc,CAAC,CAgDzB"}
@@ -18,7 +18,7 @@ export const cube3ScrambleGenerator = {
18
18
  };
19
19
  export async function generateCube3Scramble(options = {}) {
20
20
  const state = generateRandomCube3State(options);
21
- const solution = await solveCube3State(state);
21
+ const solution = await solveCube3State(state, options.solver ?? {});
22
22
  const scrambleMoves = invertCube3Algorithm(solution.moves);
23
23
  if (!options.skipValidation) {
24
24
  const scrambledState = applyCube3Algorithm(createSolvedCube3State(), scrambleMoves);
@@ -1 +1 @@
1
- {"version":3,"file":"browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/browser-loader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAMV,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAsDrB,wBAAgB,sBAAsB,IAAI,iBAAiB,GAAG,SAAS,CAEtE;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAED,wBAAsB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA2CpF"}
1
+ {"version":3,"file":"browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/cube3/solver/browser-loader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAMV,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAuDrB,wBAAgB,sBAAsB,IAAI,iBAAiB,GAAG,SAAS,CAEtE;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C;AAED,wBAAsB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAoBpF"}