@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
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Background prefetch buffer for 4x4 scrambles — the Node-side analogue of
3
+ * TNoodle's `ScrambleCacher`.
4
+ *
5
+ * The cache keeps up to `size` scrambles generating AHEAD of consumption:
6
+ * - `take()` shifts the OLDEST slot; if it is already settled it resolves
7
+ * immediately (no waiting for later slots), otherwise it awaits only
8
+ * that slot. Order is FIFO of START (not of completion), so a seeded
9
+ * stream hands out `seed:0`, `seed:1`, … in order even when the
10
+ * underlying generator finishes out of order (parallel pool).
11
+ * - every shift triggers a refill, so the buffer repopulates in the
12
+ * background — wire `generate: (o) => pool.generate(o)` and the refill
13
+ * itself runs across worker threads (see the pool integration test).
14
+ * - failures are FIRST-CLASS slots: a slot that rejects is delivered as a
15
+ * rejection to the next `take()` (consumed exactly once) and the cache
16
+ * keeps going with the following slots. Every slot promise is wrapped
17
+ * at creation so a background failure can NEVER surface as an
18
+ * unhandledRejection, and `take()` never hangs: slots always settle
19
+ * (the generator contract is "settles eventually").
20
+ * - `dispose()` stops refilling and drops buffered slots. In-flight
21
+ * generations are left to settle silently (results discarded). There
22
+ * are NO timers anywhere, so a disposed (or even forgotten) cache never
23
+ * keeps a process alive.
24
+ *
25
+ * Seeding: when `options.seed` is defined, slot n is generated with
26
+ * `${seed}:${n}` — byte-identical to core `generateScrambles("444", …)`
27
+ * for any count > 1 (slot 0 ↔ item 0 of a batch). Without a seed every
28
+ * slot uses the base options unchanged (fresh randomness).
29
+ */
30
+ import type { ScrambleResult } from "../../core/types.js";
31
+ import type { Cube4ScrambleOptions } from "./scrambler.js";
32
+ import type { ScrambleOptions } from "../../core/types.js";
33
+ export type Cube4ScrambleGenerator = (options: ScrambleOptions & Cube4ScrambleOptions) => Promise<ScrambleResult>;
34
+ export interface Cube4ScrambleCacheOptions {
35
+ /** Buffer capacity (number of ahead-of-consumption scrambles). Default 4. */
36
+ size?: number;
37
+ /** Base options; a defined `seed` becomes a `${seed}:${n}` stream. */
38
+ options?: ScrambleOptions & Cube4ScrambleOptions;
39
+ /**
40
+ * Generator used for prefetching. Defaults to the inline
41
+ * `generateCube4Scramble`; pass `(o) => pool.generate(o)` to refill
42
+ * across worker threads.
43
+ */
44
+ generate?: Cube4ScrambleGenerator;
45
+ }
46
+ /**
47
+ * Stream slot derivation (cache analogue of core's batch derivation).
48
+ * Exported for tests and for hosts that precompute the same sequence.
49
+ */
50
+ export declare function deriveCube4StreamOptions(options: ScrambleOptions & Cube4ScrambleOptions, index: number): ScrambleOptions & Cube4ScrambleOptions;
51
+ export declare class Cube4ScrambleCache {
52
+ private readonly capacity;
53
+ private readonly baseOptions;
54
+ private readonly generator;
55
+ /** FIFO of slots (start order); settled or still pending. */
56
+ private readonly slots;
57
+ private nextIndex;
58
+ private disposed;
59
+ constructor(options?: Cube4ScrambleCacheOptions);
60
+ /** Configured buffer capacity. */
61
+ get size(): number;
62
+ /** Slots currently buffered (settled + still generating). */
63
+ get buffered(): number;
64
+ /** Buffered slots that have already settled (ready for an instant take()). */
65
+ get ready(): number;
66
+ /** Slots currently generating in the background. */
67
+ get inFlight(): number;
68
+ get isDisposed(): boolean;
69
+ /**
70
+ * Resolves once the buffer holds `size` settled results (i.e. a
71
+ * subsequent `take()` is served instantly). Safe to call at any time;
72
+ * rejects only if called after `dispose()`.
73
+ */
74
+ warmup(): Promise<void>;
75
+ /**
76
+ * Next scramble in FIFO order. Instant when the front slot is settled;
77
+ * otherwise waits only for the front slot (never for later ones).
78
+ * A failed background generation surfaces here as a rejection — exactly
79
+ * once — and the cache continues with subsequent slots.
80
+ */
81
+ take(): Promise<ScrambleResult>;
82
+ /**
83
+ * Stops prefetching, drops buffered slots and ignores in-flight
84
+ * completions. No timers are involved, so nothing can keep a process
85
+ * alive afterwards. Idempotent.
86
+ */
87
+ dispose(): void;
88
+ private pump;
89
+ private generateNow;
90
+ }
91
+ /** Convenience factory mirroring the other cube4 constructors. */
92
+ export declare function createCube4ScrambleCache(options?: Cube4ScrambleCacheOptions): Cube4ScrambleCache;
93
+ //# sourceMappingURL=scramble-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scramble-cache.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/scramble-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,MAAM,MAAM,sBAAsB,GAAG,CACnC,OAAO,EAAE,eAAe,GAAG,oBAAoB,KAC5C,OAAO,CAAC,cAAc,CAAC,CAAC;AAE7B,MAAM,WAAW,yBAAyB;IACxC,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,OAAO,CAAC,EAAE,eAAe,GAAG,oBAAoB,CAAC;IACjD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,sBAAsB,CAAC;CACnC;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,eAAe,GAAG,oBAAoB,EAC/C,KAAK,EAAE,MAAM,GACZ,eAAe,GAAG,oBAAoB,CAGxC;AAWD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyC;IACrE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoD;IAC1E,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,QAAQ,CAAS;gBAEb,OAAO,GAAE,yBAA8B;IAYnD,kCAAkC;IAClC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,6DAA6D;IAC7D,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,8EAA8E;IAC9E,IAAI,KAAK,IAAI,MAAM,CAIlB;IAED,oDAAoD;IACpD,IAAI,QAAQ,IAAI,MAAM,CAIrB;IAED,IAAI,UAAU,IAAI,OAAO,CAExB;IAED;;;;OAIG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAQvB;;;;;OAKG;IACH,IAAI,IAAI,OAAO,CAAC,cAAc,CAAC;IAiB/B;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAOf,OAAO,CAAC,IAAI;IAeZ,OAAO,CAAC,WAAW;CAIpB;AAED,kEAAkE;AAClE,wBAAgB,wBAAwB,CACtC,OAAO,GAAE,yBAA8B,GACtC,kBAAkB,CAEpB"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Background prefetch buffer for 4x4 scrambles — the Node-side analogue of
3
+ * TNoodle's `ScrambleCacher`.
4
+ *
5
+ * The cache keeps up to `size` scrambles generating AHEAD of consumption:
6
+ * - `take()` shifts the OLDEST slot; if it is already settled it resolves
7
+ * immediately (no waiting for later slots), otherwise it awaits only
8
+ * that slot. Order is FIFO of START (not of completion), so a seeded
9
+ * stream hands out `seed:0`, `seed:1`, … in order even when the
10
+ * underlying generator finishes out of order (parallel pool).
11
+ * - every shift triggers a refill, so the buffer repopulates in the
12
+ * background — wire `generate: (o) => pool.generate(o)` and the refill
13
+ * itself runs across worker threads (see the pool integration test).
14
+ * - failures are FIRST-CLASS slots: a slot that rejects is delivered as a
15
+ * rejection to the next `take()` (consumed exactly once) and the cache
16
+ * keeps going with the following slots. Every slot promise is wrapped
17
+ * at creation so a background failure can NEVER surface as an
18
+ * unhandledRejection, and `take()` never hangs: slots always settle
19
+ * (the generator contract is "settles eventually").
20
+ * - `dispose()` stops refilling and drops buffered slots. In-flight
21
+ * generations are left to settle silently (results discarded). There
22
+ * are NO timers anywhere, so a disposed (or even forgotten) cache never
23
+ * keeps a process alive.
24
+ *
25
+ * Seeding: when `options.seed` is defined, slot n is generated with
26
+ * `${seed}:${n}` — byte-identical to core `generateScrambles("444", …)`
27
+ * for any count > 1 (slot 0 ↔ item 0 of a batch). Without a seed every
28
+ * slot uses the base options unchanged (fresh randomness).
29
+ */
30
+ import { generateCube4Scramble } from "./scrambler.js";
31
+ /**
32
+ * Stream slot derivation (cache analogue of core's batch derivation).
33
+ * Exported for tests and for hosts that precompute the same sequence.
34
+ */
35
+ export function deriveCube4StreamOptions(options, index) {
36
+ if (options.seed === undefined)
37
+ return options;
38
+ return { ...options, seed: `${options.seed}:${index}` };
39
+ }
40
+ export class Cube4ScrambleCache {
41
+ capacity;
42
+ baseOptions;
43
+ generator;
44
+ /** FIFO of slots (start order); settled or still pending. */
45
+ slots = [];
46
+ nextIndex = 0;
47
+ disposed = false;
48
+ constructor(options = {}) {
49
+ const size = options.size ?? 4;
50
+ if (!Number.isInteger(size) || size < 1) {
51
+ throw new RangeError(`[cube4] cache size must be an integer >= 1 (got ${size}).`);
52
+ }
53
+ this.capacity = size;
54
+ this.baseOptions = options.options ?? {};
55
+ this.generator = options.generate ?? generateCube4Scramble;
56
+ // Prefetch starts immediately: that is the point of constructing a cache.
57
+ this.pump();
58
+ }
59
+ /** Configured buffer capacity. */
60
+ get size() {
61
+ return this.capacity;
62
+ }
63
+ /** Slots currently buffered (settled + still generating). */
64
+ get buffered() {
65
+ return this.slots.length;
66
+ }
67
+ /** Buffered slots that have already settled (ready for an instant take()). */
68
+ get ready() {
69
+ let count = 0;
70
+ for (const carrier of this.slots)
71
+ if (carrier.slot !== undefined)
72
+ count++;
73
+ return count;
74
+ }
75
+ /** Slots currently generating in the background. */
76
+ get inFlight() {
77
+ let count = 0;
78
+ for (const carrier of this.slots)
79
+ if (carrier.slot === undefined)
80
+ count++;
81
+ return count;
82
+ }
83
+ get isDisposed() {
84
+ return this.disposed;
85
+ }
86
+ /**
87
+ * Resolves once the buffer holds `size` settled results (i.e. a
88
+ * subsequent `take()` is served instantly). Safe to call at any time;
89
+ * rejects only if called after `dispose()`.
90
+ */
91
+ warmup() {
92
+ if (this.disposed) {
93
+ return Promise.reject(new Error("[cube4] scramble cache is disposed (warmup)."));
94
+ }
95
+ this.pump();
96
+ return Promise.all(this.slots.map((carrier) => carrier.promise)).then(() => undefined);
97
+ }
98
+ /**
99
+ * Next scramble in FIFO order. Instant when the front slot is settled;
100
+ * otherwise waits only for the front slot (never for later ones).
101
+ * A failed background generation surfaces here as a rejection — exactly
102
+ * once — and the cache continues with subsequent slots.
103
+ */
104
+ take() {
105
+ if (this.disposed) {
106
+ return Promise.reject(new Error("[cube4] scramble cache is disposed (take)."));
107
+ }
108
+ const carrier = this.slots.shift();
109
+ this.pump(); // refill BEFORE awaiting, so generation overlaps consumption
110
+ if (carrier === undefined) {
111
+ // Only reachable when capacity was somehow drained concurrently;
112
+ // generate one on the spot rather than hang.
113
+ return this.generateNow();
114
+ }
115
+ return carrier.promise.then((slot) => {
116
+ if (slot.ok)
117
+ return slot.result;
118
+ throw slot.error;
119
+ });
120
+ }
121
+ /**
122
+ * Stops prefetching, drops buffered slots and ignores in-flight
123
+ * completions. No timers are involved, so nothing can keep a process
124
+ * alive afterwards. Idempotent.
125
+ */
126
+ dispose() {
127
+ this.disposed = true;
128
+ this.slots.length = 0;
129
+ }
130
+ // ── internals ────────────────────────────────────────────────────────
131
+ pump() {
132
+ while (!this.disposed && this.slots.length < this.capacity) {
133
+ const index = this.nextIndex++;
134
+ const promise = this.generator(deriveCube4StreamOptions(this.baseOptions, index)).then((result) => ({ ok: true, result }), (error) => ({ ok: false, error }));
135
+ const carrier = { promise, slot: undefined };
136
+ void promise.then((slot) => {
137
+ carrier.slot = slot;
138
+ });
139
+ this.slots.push(carrier);
140
+ }
141
+ }
142
+ generateNow() {
143
+ const index = this.nextIndex++;
144
+ return this.generator(deriveCube4StreamOptions(this.baseOptions, index));
145
+ }
146
+ }
147
+ /** Convenience factory mirroring the other cube4 constructors. */
148
+ export function createCube4ScrambleCache(options = {}) {
149
+ return new Cube4ScrambleCache(options);
150
+ }
@@ -1,15 +1,210 @@
1
1
  /**
2
2
  * The 4x4 scramble generator: sample a uniform random state, solve it with
3
- * the reduction solver (phases 1-4), and invert the solution. The scramble
4
- * always reproduces the sampled target state and facelets exactly.
3
+ * the reduction solver (phases 1-4), and invert the solution. The emitted
4
+ * scramble must reproduce the sampled target state EXACTLY — enforced by
5
+ * `assertCube4ScrambleReproducesTarget` on every generated result (unless
6
+ * `skipValidation` is set, which is only for the independent verification
7
+ * harness in `scripts/verify-scrambles.ts`).
5
8
  *
6
- * If the solver fails after 5 attempts it falls back to cubing.js
7
- * `randomScrambleForEvent("444")` (also random-state). If that also fails,
8
- * a WCA-compatible random-moves sequence (40 moves) is returned.
9
+ * On a target mismatch the error is retryable, so the existing retry loop
10
+ * re-runs the solve over the SAME sampled target (deep attempt) and throws
11
+ * once attempts are exhausted. Re-sampling a different target to "make it
12
+ * fit" and degrading to random moves are forbidden: failing loudly (WCA 4g)
13
+ * is strictly preferable to emitting a scramble that lies about its seed.
14
+ *
15
+ * ── Determinism contract: what defines the output ──────────────────────
16
+ * `generateCube4Scramble` / `generateCube4Attempt` are pure functions of
17
+ * (seed, complete options): the emitted `moves` do not depend on wall-clock
18
+ * time, CPU load, warm-up, batch order, or process identity. This holds
19
+ * because every budget that can change the search outcome is a
20
+ * deterministic work counter (nodes visited), never a wall deadline:
21
+ * - reduction phases: node budgets inside `reduction/solve.ts`;
22
+ * - the 3x3 finish: node caps below (`finish.phase2MaxNodes`,
23
+ * `finish.improveMaxNodes`, per-candidate cap), which replace the old
24
+ * 150/50 ms wall budgets that made the same seed yield different move
25
+ * counts under CPU contention.
26
+ * Options are PART of the key: `restarts`/`bestOf`, `threeAxes`, `deep`,
27
+ * the beam knobs and `finish.*` legitimately change the result for the same
28
+ * seed — the output is reproducible within each configuration (documented
29
+ * behavior, not nondeterminism).
30
+ *
31
+ * Residual wall-clock backstop (hang guard only; never observed to bind —
32
+ * max measured solve 36.5 s): the 90 s
33
+ * `Promise.race` below. It cannot preempt the synchronous search — it
34
+ * can only fire at an event-loop tick — so if one ever fired, that attempt
35
+ * fails loudly (as a retryable error) instead of hanging. Making that bound
36
+ * exact deadline support would require cooperative search checkpoints.
9
37
  */
10
38
  import type { ScrambleGenerator, ScrambleOptions, ScrambleResult } from "../../core/index.js";
39
+ import type { Cube4Move, Cube4State } from "./types.js";
11
40
  export declare const cube4ScrambleGenerator: ScrambleGenerator;
12
- export declare function generateCube4Scramble(options?: ScrambleOptions & {
41
+ /**
42
+ * 444-specific scramble options (pass-through; extra keys are ignored by
43
+ * the generic `generateScramble` API surface).
44
+ */
45
+ export interface Cube4ScrambleOptions {
46
+ /**
47
+ * Number of reduction passes to run on the SAME sampled state (alias for `restarts`).
48
+ * `1` (default): single deterministic pass.
49
+ * Higher values reshuffle the search traversal order on the same physical cube,
50
+ * returning the shortest valid scramble while strictly preserving uniform random-state
51
+ * distribution (WCA compliant).
52
+ */
53
+ bestOf?: number;
54
+ /**
55
+ * Last-resort effort for hard states: widens every solver work cap
56
+ * (phase-2 depth/nodes, phase-3 budgets/lengths, pool caps).
57
+ * Default false — the fast caps solve essentially everything at the
58
+ * same length (measured: identical totals on 54 states, ~-19% time),
59
+ * and a failed fast attempt automatically retries with `deep: true`
60
+ * (bestOf 1), so hard states still resolve.
61
+ */
62
+ deep?: boolean;
63
+ /**
64
+ * Search all 3 axial views (UD, RL, FB) of the same physical cube.
65
+ * The winning solution is mapped back by prepending the physical rotation
66
+ * sequence before the exact-target gate. This option costs roughly 2x time.
67
+ */
68
+ threeAxes?: boolean;
69
+ /**
70
+ * Phase-4 attempts among valid reductions (default centersBeam * edgesPerCenter, i.e. 5).
71
+ * Passed through to the reduction solver: the first N reductions each get a 3x3
72
+ * finish and the shortest simplified total wins.
73
+ */
74
+ finishTries?: number;
75
+ /**
76
+ * Number of distinct center candidates from phase 2 to explore in phase 3
77
+ * (default 1). Part of hierarchical beam search.
78
+ */
79
+ centersBeam?: number;
80
+ /**
81
+ * Maximum number of distinct edge pairings to collect per center candidate
82
+ * in phase 3 (default 5). Part of hierarchical beam search.
83
+ */
84
+ edgesPerCenter?: number;
85
+ /**
86
+ * F1 enumeration slack beyond the per-axis optimum (default 3) and
87
+ * collection cap (default 60). Passed through to the reduction
88
+ * solver. Reversible A/B knob for the diversity-vs-length trade-off
89
+ * of phase 1.
90
+ */
91
+ phase1ExtraDepths?: number;
92
+ phase1Cap?: number;
93
+ /**
94
+ * Phase-2 wide move set: search all 28 center2 moves (adds
95
+ * Dw2/Lw/Lw2/Lw'/Bw2) instead of the default 23. Default false
96
+ * (measured ~-0.2 moves for ~+15% time on typical states; opt-in).
97
+ * Caution: on some hard states the wider branching can blow a single
98
+ * attempt past 100 s (no in-search cancellation), delaying every later
99
+ * bestOf job — prefer `restarts` for length.
100
+ */
101
+ center2Wide?: boolean;
102
+ /**
103
+ * Deep multi-candidate polish pass in phase 3 (top-3 reductions).
104
+ * Defaults to the value of `threeAxes`.
105
+ */
106
+ deepPolish?: boolean;
107
+ /**
108
+ * Maximum whole-cube rotation fix (rotMoves, before placement) accepted
109
+ * on a phase-3 goal end (default 4 = no filter). The phase-3 goal is
110
+ * scheme-agnostic, so goal ends land 0/2/4 wide moves from the standard
111
+ * color scheme; the solver then places the fix where it cancels against
112
+ * the reduction's wide moves, which made filtering unprofitable.
113
+ */
114
+ maxFixMoves?: number;
115
+ /**
116
+ * Full-pipeline restarts over the SAME sampled state (default 1).
117
+ * Passed through to the reduction solver: each restart reshuffles the
118
+ * search order deterministically and the shortest total wins.
119
+ * Same-state diversity (uniformity-preserving), unlike `bestOf`.
120
+ */
121
+ restarts?: number;
122
+ /**
123
+ * Tuning for the 3x3 finish (cube3 two-phase solver). All fields fall
124
+ * back to the production defaults, which prioritize short finishes:
125
+ * `orientations: 3`, `phase1ExtraDepth: 1`, `phase1SolutionLimit: 24`,
126
+ * `targetLength: 20`, and the deterministic work caps
127
+ * `phase2MaxNodes: 90_000`, `improveMaxNodes: 260_000` (per-candidate
128
+ * phase-2 cap defaults to `phase2MaxNodes`); one retry doubles every cap.
129
+ * A cheaper finish (e.g. `orientations: 1`, `phase1SolutionLimit: 8`)
130
+ * is much faster per call at a small length cost — the finish runs
131
+ * several times per attempt, so this dominates attempt latency.
132
+ *
133
+ * The former wall-clock fields (`timeoutMs`, `phase2TimeoutMs`,
134
+ * `retryTimeoutMs`, `retryPhase2TimeoutMs`) were REMOVED: wall deadlines
135
+ * made the emitted scramble depend on CPU load (measured: the same seed
136
+ * changed move count under contention). The finish now runs under node
137
+ * counters only, restoring the (seed, options) purity contract.
138
+ */
139
+ finish?: {
140
+ /**
141
+ * Finish implementation (default `continuous`): the deterministic
142
+ * continuous two-phase search in `reduction/finish3.ts` (3 axes ×
143
+ * inverse views). `legacy` is the older batch two-phase solver, also used
144
+ * as the fallback if the continuous search exhausts its node cap.
145
+ */
146
+ mode?: "legacy" | "continuous";
147
+ /** Views for continuous mode: 1, 2 (+inverse) or 6 (3 axes × inverse, default). */
148
+ continuousViews?: 1 | 2 | 6;
149
+ /** Continuous phase-1+phase-2 node budget (default 200,000). */
150
+ continuousMaxNodes?: number;
151
+ /** Continuous screening budget per candidate reduction (default 30,000). */
152
+ continuousScreenNodes?: number;
153
+ /** Candidates re-finished at full budget after screening (default 0 = no screening). */
154
+ screenTop?: number;
155
+ /** Absolute continuous node cap (default 20,000,000). */
156
+ continuousHardMaxNodes?: number;
157
+ /** Maximum phase-2 segment in continuous mode (default 12). */
158
+ continuousMaxPhase2Length?: number;
159
+ orientations?: number;
160
+ phase1ExtraDepth?: number;
161
+ phase1SolutionLimit?: number;
162
+ targetLength?: number;
163
+ /** Phase-2 search node cap per finish call (default 90_000). */
164
+ phase2MaxNodes?: number;
165
+ /** Improvement-loop node budget per finish call (default 260_000). */
166
+ improveMaxNodes?: number;
167
+ /** Per-candidate phase-2 node cap in the improvement loop (default: `phase2MaxNodes`). */
168
+ improvePhase2MaxNodes?: number;
169
+ };
170
+ }
171
+ /**
172
+ * Thrown by the target gate when the emitted scramble does not reproduce
173
+ * the independently sampled state. Retryable: the retry loop re-solves the
174
+ * SAME sampled target (deep attempt) — never re-samples.
175
+ */
176
+ export declare class Cube4TargetMismatchError extends Error {
177
+ readonly code: "cube4-target-mismatch";
178
+ constructor(message: string);
179
+ }
180
+ /** Thrown by the 90 s hang guard (wall-clock, fires only at a tick). */
181
+ export declare class Cube4WallTimeoutError extends Error {
182
+ readonly code: "cube4-wall-timeout";
183
+ constructor(message: string);
184
+ }
185
+ /**
186
+ * The real random-state contract for 444 (non-tautological): replaying the
187
+ * emitted scramble on the solved cube must reproduce the exact state that
188
+ * was sampled for the seed — the reference the sampler produced, never a
189
+ * state derived from the scramble itself. Throws when it does not.
190
+ *
191
+ * Used by the production validation path in `solveCube4Attempt` and
192
+ * exported so tests can prove the gate rejects corrupted scrambles.
193
+ */
194
+ export declare function assertCube4ScrambleReproducesTarget(scrambleMoves: readonly Cube4Move[], targetState: Cube4State, seed?: string | number): void;
195
+ export declare function generateCube4Scramble(options?: ScrambleOptions & Cube4ScrambleOptions & {
196
+ skipValidation?: boolean;
197
+ }): Promise<ScrambleResult>;
198
+ /**
199
+ * Single-attempt 444 solve: samples ONE state (from `options.seed`) and
200
+ * solves it with the reduction solver. Throws on failure — including a
201
+ * target mismatch ("scramble does not reproduce the sampled target state")
202
+ * — no resampling; hosts may retry with the same seed, which re-uses the
203
+ * same target.
204
+ * Fan-out primitive for hosts: run with seeds `seed`, `seed:bestN`, ...
205
+ * across workers and keep the shortest valid result.
206
+ */
207
+ export declare function generateCube4Attempt(tables: any, pairTables: any, options?: ScrambleOptions & Cube4ScrambleOptions & {
13
208
  skipValidation?: boolean;
14
209
  }): Promise<ScrambleResult>;
15
210
  //# sourceMappingURL=scrambler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/scrambler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAmJ9F,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,CAiGzB"}
1
+ {"version":3,"file":"scrambler.d.ts","sourceRoot":"","sources":["../../../src/puzzles/cube4/scrambler.ts"],"names":[],"mappings":"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"}