@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
@@ -4,6 +4,14 @@
4
4
  //
5
5
  // Node never uses this module: node-loader.ts reads the .bin files directly
6
6
  // from the filesystem.
7
+ //
8
+ // Integrity: callers can pass the manifest's expectations — sha256 + byte
9
+ // length (4x4 manifests) or the FNV-1a32 checksums the older manifests ship
10
+ // — and every check runs on the FINAL (decompressed) bytes, i.e. exactly the
11
+ // .bin payload: identical checks to the node loaders. A payload served by
12
+ // the Cache API that fails verification is purged (`cache.delete`) and
13
+ // re-downloaded exactly once, so a corrupt cache entry heals instead of
14
+ // failing every load forever.
7
15
  // DecompressionStream only guarantees "gzip"/"deflate"; "br" support varies,
8
16
  // so probe once and never attempt formats the runtime cannot decompress.
9
17
  const SUPPORTED_FORMATS = (() => {
@@ -23,46 +31,233 @@ const SUPPORTED_FORMATS = (() => {
23
31
  })();
24
32
  export async function fetchTableBuffer(baseUrl, dir, file, options = {}) {
25
33
  const urlFor = (name) => `${baseUrl}/${dir}/${name}`;
26
- const cachedFetch = options.cacheName === undefined ? undefined : makeCachedFetch(options.cacheName);
27
- if (options.compressed !== false && SUPPORTED_FORMATS.length > 0) {
28
- for (const [suffix, format] of SUPPORTED_FORMATS) {
29
- try {
30
- const response = await (cachedFetch ? cachedFetch(urlFor(`${file}.${suffix}`)) : fetch(urlFor(`${file}.${suffix}`)));
31
- if (response.ok && response.body) {
32
- const stream = response.body.pipeThrough(new DecompressionStream(format));
33
- return await new Response(stream).arrayBuffer();
34
+ const canonicalUrl = urlFor(file);
35
+ const cache = options.cacheName === undefined ? undefined : makeCacheChannel(options.cacheName);
36
+ // Cache API URLs that served the current download attempt; when the
37
+ // payload fails verification they are purged before the single retry.
38
+ let servedFromCache = [];
39
+ const get = async (url) => {
40
+ if (!cache)
41
+ return fetch(url);
42
+ const { response, fromCache } = await cache.fetch(url);
43
+ if (fromCache)
44
+ servedFromCache.push(url);
45
+ return response;
46
+ };
47
+ const download = async () => {
48
+ servedFromCache = [];
49
+ if (options.compressed !== false && SUPPORTED_FORMATS.length > 0) {
50
+ for (const [suffix, format] of SUPPORTED_FORMATS) {
51
+ try {
52
+ const response = await get(urlFor(`${file}.${suffix}`));
53
+ if (response.ok && response.body) {
54
+ const stream = response.body.pipeThrough(new DecompressionStream(format));
55
+ return await new Response(stream).arrayBuffer();
56
+ }
57
+ }
58
+ catch {
59
+ // try the next candidate
34
60
  }
35
61
  }
36
- catch {
37
- // try the next candidate
38
- }
39
62
  }
63
+ const response = await get(canonicalUrl);
64
+ if (!response.ok) {
65
+ throw new Error(`Failed to load table ${canonicalUrl}: ${response.status} ${response.statusText}` +
66
+ (options.expectedBytes !== undefined ? ` (expected ${options.expectedBytes} bytes)` : ""));
67
+ }
68
+ return response.arrayBuffer();
69
+ };
70
+ let buffer = await download();
71
+ try {
72
+ await verifyTableBuffer(buffer, canonicalUrl, options);
40
73
  }
41
- const response = await (cachedFetch ? cachedFetch(urlFor(file)) : fetch(urlFor(file)));
42
- if (!response.ok) {
43
- throw new Error(`Failed to load ${file}: ${response.status} ${response.statusText}`);
74
+ catch (error) {
75
+ // A corrupt Cache API entry fails every load until it is purged: delete
76
+ // the cached copies that served this attempt and re-download exactly
77
+ // ONCE. A second verification failure (or a payload that never came
78
+ // from the cache) propagates unchanged.
79
+ if (!cache || servedFromCache.length === 0)
80
+ throw error;
81
+ await cache.purge(servedFromCache);
82
+ buffer = await download();
83
+ await verifyTableBuffer(buffer, canonicalUrl, options);
44
84
  }
45
- return response.arrayBuffer();
85
+ return buffer;
46
86
  }
47
- function makeCachedFetch(cacheName) {
48
- return async (url) => {
49
- if (typeof caches === "undefined") {
50
- return fetch(url);
87
+ /**
88
+ * Verify the downloaded payload against the manifest: byte length first
89
+ * (clearer truncation messages), then sha256 / FNV-1a. Runs on the
90
+ * decompressed bytes, so .br/.gz variants and the raw file are checked
91
+ * identically — and identically to the node loaders.
92
+ */
93
+ async function verifyTableBuffer(buffer, url, options) {
94
+ if (options.expectedBytes !== undefined && buffer.byteLength !== options.expectedBytes) {
95
+ throw new Error(`Table size mismatch: ${url} (expected ${options.expectedBytes} bytes, got ${buffer.byteLength})`);
96
+ }
97
+ if (options.expectedSha256 !== undefined) {
98
+ const actual = await sha256Hex(buffer);
99
+ if (actual !== options.expectedSha256) {
100
+ throw new Error(`Table checksum mismatch: ${url} (expected sha256 ${options.expectedSha256}, got ${actual}, ${buffer.byteLength} bytes)`);
51
101
  }
52
- try {
53
- const cache = await caches.open(cacheName);
54
- const cached = await cache.match(url);
55
- if (cached) {
56
- return cached;
102
+ }
103
+ if (options.expectedFnv1a !== undefined) {
104
+ const spec = options.expectedFnv1a;
105
+ const actual = fnv1a32(buffer, spec.elementSize, spec.signed ?? false);
106
+ if (actual !== spec.value) {
107
+ throw new Error(`Table checksum mismatch: ${url} (expected fnv1a ${spec.value}, got ${actual}, ${buffer.byteLength} bytes)`);
108
+ }
109
+ }
110
+ }
111
+ /**
112
+ * FNV-1a (32-bit) over the elements decoded from a wire buffer: the browser
113
+ * twin of the node loaders' `checksum(values)` (imul multiply, `>>> 0`
114
+ * result). `elementSize` mirrors each loader's decoding of the manifest
115
+ * checksum — see `ExpectedFnv1a`.
116
+ */
117
+ export function fnv1a32(buffer, elementSize, signed = false) {
118
+ if (buffer.byteLength % elementSize !== 0) {
119
+ throw new Error(`FNV-1a input length ${buffer.byteLength} is not a multiple of ${elementSize}-byte elements`);
120
+ }
121
+ let hash = 0x811c9dc5;
122
+ const view = new DataView(buffer);
123
+ const count = buffer.byteLength / elementSize;
124
+ for (let index = 0; index < count; index += 1) {
125
+ let value;
126
+ if (elementSize === 1)
127
+ value = signed ? view.getInt8(index) : view.getUint8(index);
128
+ else if (elementSize === 2)
129
+ value = view.getUint16(index * 2, true);
130
+ else
131
+ value = view.getInt32(index * 4, true);
132
+ hash ^= value;
133
+ hash = Math.imul(hash, 0x01000193);
134
+ }
135
+ return hash >>> 0;
136
+ }
137
+ /** sha256 hex of a buffer; WebCrypto when available, portable JS fallback otherwise. */
138
+ export async function sha256Hex(buffer) {
139
+ const subtle = globalThis.crypto?.subtle;
140
+ if (subtle) {
141
+ const digest = await subtle.digest("SHA-256", buffer);
142
+ const bytes = new Uint8Array(digest);
143
+ let hex = "";
144
+ for (let i = 0; i < bytes.length; i++)
145
+ hex += bytes[i].toString(16).padStart(2, "0");
146
+ return hex;
147
+ }
148
+ // Insecure contexts expose no SubtleCrypto; keep integrity checks working
149
+ // with a self-contained implementation instead of skipping verification.
150
+ return sha256HexJs(buffer);
151
+ }
152
+ // FIPS 180-4 SHA-256, used only when `crypto.subtle` is unavailable.
153
+ const SHA256_K = new Uint32Array([
154
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
155
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
156
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
157
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
158
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
159
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
160
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
161
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
162
+ ]);
163
+ export function sha256HexJs(buffer) {
164
+ const bytes = new Uint8Array(buffer);
165
+ const paddedLength = (bytes.length + 72) & ~63;
166
+ const data = new Uint8Array(paddedLength);
167
+ data.set(bytes);
168
+ data[bytes.length] = 0x80;
169
+ const view = new DataView(data.buffer);
170
+ view.setUint32(paddedLength - 8, Math.floor(bytes.length / 0x20000000));
171
+ view.setUint32(paddedLength - 4, (bytes.length << 3) >>> 0);
172
+ let h0 = 0x6a09e667;
173
+ let h1 = 0xbb67ae85;
174
+ let h2 = 0x3c6ef372;
175
+ let h3 = 0xa54ff53a;
176
+ let h4 = 0x510e527f;
177
+ let h5 = 0x9b05688c;
178
+ let h6 = 0x1f83d9ab;
179
+ let h7 = 0x5be0cd19;
180
+ const w = new Int32Array(64);
181
+ for (let offset = 0; offset < paddedLength; offset += 64) {
182
+ for (let i = 0; i < 16; i += 1)
183
+ w[i] = view.getInt32(offset + i * 4);
184
+ for (let i = 16; i < 64; i += 1) {
185
+ const w15 = w[i - 15];
186
+ const w2 = w[i - 2];
187
+ const s0 = ((w15 >>> 7) | (w15 << 25)) ^ ((w15 >>> 18) | (w15 << 14)) ^ (w15 >>> 3);
188
+ const s1 = ((w2 >>> 17) | (w2 << 15)) ^ ((w2 >>> 19) | (w2 << 13)) ^ (w2 >>> 10);
189
+ w[i] = (w[i - 16] + s0 + w[i - 7] + s1) | 0;
190
+ }
191
+ let a = h0;
192
+ let b = h1;
193
+ let c = h2;
194
+ let d = h3;
195
+ let e = h4;
196
+ let f = h5;
197
+ let g = h6;
198
+ let h = h7;
199
+ for (let i = 0; i < 64; i += 1) {
200
+ const s1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
201
+ const ch = (e & f) ^ (~e & g);
202
+ const t1 = (h + s1 + ch + SHA256_K[i] + w[i]) | 0;
203
+ const s0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
204
+ const maj = (a & b) ^ (a & c) ^ (b & c);
205
+ const t2 = (s0 + maj) | 0;
206
+ h = g;
207
+ g = f;
208
+ f = e;
209
+ e = (d + t1) | 0;
210
+ d = c;
211
+ c = b;
212
+ b = a;
213
+ a = (t1 + t2) | 0;
214
+ }
215
+ h0 = (h0 + a) | 0;
216
+ h1 = (h1 + b) | 0;
217
+ h2 = (h2 + c) | 0;
218
+ h3 = (h3 + d) | 0;
219
+ h4 = (h4 + e) | 0;
220
+ h5 = (h5 + f) | 0;
221
+ h6 = (h6 + g) | 0;
222
+ h7 = (h7 + h) | 0;
223
+ }
224
+ return [h0, h1, h2, h3, h4, h5, h6, h7]
225
+ .map((x) => (x >>> 0).toString(16).padStart(8, "0"))
226
+ .join("");
227
+ }
228
+ function makeCacheChannel(cacheName) {
229
+ if (typeof caches === "undefined") {
230
+ return undefined;
231
+ }
232
+ return {
233
+ async fetch(url) {
234
+ try {
235
+ const cache = await caches.open(cacheName);
236
+ const cached = await cache.match(url);
237
+ if (cached) {
238
+ return { response: cached, fromCache: true };
239
+ }
240
+ const response = await fetch(url);
241
+ if (response.ok) {
242
+ await cache.put(url, response.clone());
243
+ }
244
+ return { response, fromCache: false };
57
245
  }
58
- const response = await fetch(url);
59
- if (response.ok) {
60
- await cache.put(url, response.clone());
246
+ catch {
247
+ return { response: await fetch(url), fromCache: false };
248
+ }
249
+ },
250
+ async purge(urls) {
251
+ try {
252
+ const cache = await caches.open(cacheName);
253
+ for (const url of urls) {
254
+ await cache.delete(url);
255
+ }
256
+ }
257
+ catch {
258
+ // Best effort: a failed purge must not mask the verification error
259
+ // that triggered it — the load still fails loudly either way.
61
260
  }
62
- return response;
63
- }
64
- catch {
65
- return fetch(url);
66
261
  }
67
262
  };
68
263
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * One-in-flight-promise helper: concurrent cold calls share ONE underlying
3
+ * load (a single filesystem read / browser fetch pass) instead of racing
4
+ * several loads and clobbering each other's results. On rejection the slot
5
+ * is purged so the next call can retry — a failure is never cached forever.
6
+ *
7
+ * Used by `cube3/solver/tables.ts` (phase-1 / phase-2 / solver slots) and
8
+ * `cube3/solver/generated-assets.ts` (the node fs load: a plain
9
+ * `cached ??= await …` re-reads the filesystem when two callers — e.g. mixed
10
+ * phase-1 + phase-2 loads — interleave on the `await`).
11
+ */
12
+ export interface LoadOnceSlot<T> {
13
+ promise: Promise<T> | undefined;
14
+ }
15
+ export declare function loadOnce<T>(slot: LoadOnceSlot<T>, load: () => Promise<T>): Promise<T>;
16
+ //# sourceMappingURL=load-once.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-once.d.ts","sourceRoot":"","sources":["../../../src/puzzles/shared/load-once.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;CACjC;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CASrF"}
@@ -0,0 +1,11 @@
1
+ export function loadOnce(slot, load) {
2
+ if (!slot.promise) {
3
+ const wrapped = load().catch((error) => {
4
+ if (slot.promise === wrapped)
5
+ slot.promise = undefined;
6
+ throw error;
7
+ });
8
+ slot.promise = wrapped;
9
+ }
10
+ return slot.promise;
11
+ }
@@ -1,4 +1,4 @@
1
- export declare function getCachedBrowserTables(): {
1
+ export interface SkewbBrowserTables {
2
2
  permutationTable: Uint16Array;
3
3
  orientationTable: Uint16Array;
4
4
  centersTable: Uint16Array;
@@ -6,15 +6,8 @@ export declare function getCachedBrowserTables(): {
6
6
  evenCenterRank: Uint16Array;
7
7
  orientClass: Uint8Array;
8
8
  r7Sets: Uint16Array;
9
- } | undefined;
9
+ }
10
+ export declare function getCachedBrowserTables(): SkewbBrowserTables | undefined;
10
11
  export declare function clearCachedBrowserTables(): void;
11
- export declare function loadBrowserTables(baseUrl?: string): Promise<{
12
- permutationTable: Uint16Array;
13
- orientationTable: Uint16Array;
14
- centersTable: Uint16Array;
15
- policyTable: Uint8Array;
16
- evenCenterRank: Uint16Array;
17
- orientClass: Uint8Array;
18
- r7Sets: Uint16Array;
19
- }>;
12
+ export declare function loadBrowserTables(baseUrl?: string): Promise<SkewbBrowserTables>;
20
13
  //# sourceMappingURL=browser-loader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/browser-loader.ts"],"names":[],"mappings":"AAeA,wBAAgB,sBAAsB;sBATlB,WAAW;sBACX,WAAW;kBACf,WAAW;iBACZ,UAAU;oBACP,WAAW;iBACd,UAAU;YACf,WAAW;cAKpB;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,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,CAAC,CAwCD"}
1
+ {"version":3,"file":"browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/browser-loader.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,kBAAkB;IACjC,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;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,25 +1,44 @@
1
1
  import { SKEWB_SOLVER_TABLE_METADATA } from "./table-metadata.js";
2
2
  import { fetchTableBuffer } from "../../shared/compressed-fetch.js";
3
3
  const TABLES_DIR = "puzzles/skewb/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, centersTable, helpers, policy] = await Promise.all([
18
- fetchUint16Table(resolved, manifest.files.permutationTable, SKEWB_SOLVER_TABLE_METADATA.permutationTable.entries),
19
- fetchUint16Table(resolved, manifest.files.orientationTable, SKEWB_SOLVER_TABLE_METADATA.orientationTable.entries),
20
- fetchUint16Table(resolved, manifest.files.centersTable, SKEWB_SOLVER_TABLE_METADATA.centersTable.entries),
21
- fetchUint8Table(resolved, manifest.files.helpers, SKEWB_SOLVER_TABLE_METADATA.helpers.entries),
22
- fetchUint8Table(resolved, manifest.files.policyTable, SKEWB_SOLVER_TABLE_METADATA.policyTable.entries)
37
+ fetchUint16Table(resolved, manifest.files.permutationTable, SKEWB_SOLVER_TABLE_METADATA.permutationTable),
38
+ fetchUint16Table(resolved, manifest.files.orientationTable, SKEWB_SOLVER_TABLE_METADATA.orientationTable),
39
+ fetchUint16Table(resolved, manifest.files.centersTable, SKEWB_SOLVER_TABLE_METADATA.centersTable),
40
+ fetchUint8Table(resolved, manifest.files.helpers, SKEWB_SOLVER_TABLE_METADATA.helpers),
41
+ fetchUint8Table(resolved, manifest.files.policyTable, SKEWB_SOLVER_TABLE_METADATA.policyTable)
23
42
  ]);
24
43
  const helpersOffset = SKEWB_SOLVER_TABLE_METADATA.helpers;
25
44
  const evenCenterRank = new Uint16Array(helpersOffset.evenCenterRankBytes / 2);
@@ -65,22 +84,31 @@ async function fetchManifest(baseUrl) {
65
84
  }
66
85
  return await response.json();
67
86
  }
68
- async function fetchUint16Table(baseUrl, entry, expectedEntries) {
69
- const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, { compressed: entry.compressed !== false });
70
- if (buffer.byteLength !== expectedEntries * 2) {
71
- throw new Error(`Invalid byte size for ${entry.file}`);
72
- }
73
- const values = new Uint16Array(expectedEntries);
87
+ /**
88
+ * Fetch + verify with the SAME checks as node-loader.ts: byte length from
89
+ * the manifest entries, FNV-1a checksum against the table-metadata
90
+ * constants (the values node asserts against).
91
+ */
92
+ async function fetchUint16Table(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 * 2,
97
+ expectedFnv1a: { value: meta.checksum, elementSize: 2 }
98
+ });
99
+ const values = new Uint16Array(entry.entries);
74
100
  const view = new DataView(buffer);
75
101
  for (let i = 0; i < values.length; i++) {
76
102
  values[i] = view.getUint16(i * 2, true);
77
103
  }
78
104
  return values;
79
105
  }
80
- async function fetchUint8Table(baseUrl, entry, expectedEntries) {
81
- const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, { compressed: entry.compressed !== false });
82
- if (buffer.byteLength !== expectedEntries) {
83
- throw new Error(`Invalid byte size for ${entry.file}`);
84
- }
106
+ async function fetchUint8Table(baseUrl, entry, meta) {
107
+ const buffer = await fetchTableBuffer(baseUrl, TABLES_DIR, entry.file, {
108
+ cacheName: CACHE_NAME,
109
+ compressed: entry.compressed !== false,
110
+ expectedBytes: entry.entries,
111
+ expectedFnv1a: { value: meta.checksum, elementSize: 1 }
112
+ });
85
113
  return new Uint8Array(buffer);
86
114
  }
@@ -1 +1 @@
1
- {"version":3,"file":"generated-assets.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/generated-assets.ts"],"names":[],"mappings":"AAgBA,wBAAsB,8BAA8B,IAAI,OAAO,CAAC;IAC9D,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,CAAC,CAYD"}
1
+ {"version":3,"file":"generated-assets.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/generated-assets.ts"],"names":[],"mappings":"AAsBA,wBAAsB,8BAA8B,IAAI,OAAO,CAAC;IAC9D,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,CAAC,CAWD"}
@@ -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
  }
@@ -12,8 +18,7 @@ export async function loadGeneratedSkewbSolverTables() {
12
18
  "In browser environments, call `initializeScrambler(baseUrl)` " +
13
19
  "before generating scrambles.");
14
20
  }
15
- cachedTables ??= await loadNodeTables();
16
- return cachedTables;
21
+ return loadOnce(nodeTablesSlot, loadNodeTables);
17
22
  }
18
23
  async function loadNodeTables() {
19
24
  const loader = await import(
@@ -1,4 +1,4 @@
1
- import { ensureTablesLoaded, getCenterIndex, getOrientationIndex, getPerm7Index, getPolicyMoveFromPacked, MOVE_COUNT, PERMUTATION_TABLE, ORIENTATION_TABLE, CENTERS_TABLE, POLICY_TABLE, rankFromComponents, SKEWB_MOVES, SOLVED_RANK } from "./tables.js";
1
+ import { ensureTablesLoaded, getCenterIndex, getOrientationIndex, getPerm7Index, MOVE_COUNT, PERMUTATION_TABLE, ORIENTATION_TABLE, CENTERS_TABLE, POLICY_TABLE, POLICY_TABLE_EXPANDED, rankFromComponents, SKEWB_MOVES, SOLVED_RANK } from "./tables.js";
2
2
  export async function solveSkewb(state) {
3
3
  await ensureTablesLoaded();
4
4
  return solveWithTables(state).moves;
@@ -33,7 +33,7 @@ function solveWithTables(state) {
33
33
  return { moves, depth: moves.length };
34
34
  }
35
35
  function solveWithTablesIndices(state) {
36
- const policy = POLICY_TABLE;
36
+ const policy = POLICY_TABLE_EXPANDED;
37
37
  const permTable = PERMUTATION_TABLE;
38
38
  const orientTable = ORIENTATION_TABLE;
39
39
  const centersTable = CENTERS_TABLE;
@@ -50,7 +50,7 @@ function solveWithTablesIndices(state) {
50
50
  }
51
51
  const moves = [];
52
52
  while (rank !== SOLVED_RANK) {
53
- const m = getPolicyMoveFromPacked(policy, rank);
53
+ const m = policy[rank];
54
54
  moves.push(m);
55
55
  r7 = permTable[r7 * MOVE_COUNT + m];
56
56
  rO = orientTable[rO * MOVE_COUNT + m];
@@ -23,6 +23,12 @@ export declare let PERMUTATION_TABLE: Uint16Array | null;
23
23
  export declare let ORIENTATION_TABLE: Uint16Array | null;
24
24
  export declare let CENTERS_TABLE: Uint16Array | null;
25
25
  export declare let POLICY_TABLE: Uint8Array | null;
26
+ /**
27
+ * Byte-expanded policy (one optimal move per rank, no bit-unpacking in
28
+ * the hot walk). Built at load from the 3-bit packed table: +3.1 MB RAM,
29
+ * zero wire/format change.
30
+ */
31
+ export declare let POLICY_TABLE_EXPANDED: Uint8Array | null;
26
32
  export declare let EVEN_CENTER_RANK: Uint16Array | null;
27
33
  export declare let EVEN_CENTER_FULL: Uint16Array | null;
28
34
  export declare let ORIENT_CLASS: Uint8Array | null;
@@ -1 +1 @@
1
- {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/skewb/solver/tables.ts"],"names":[],"mappings":"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"}
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;;;;GAIG;AACH,eAAO,IAAI,qBAAqB,EAAE,UAAU,GAAG,IAAW,CAAC;AAC3D,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,aAAa,EAAE,UAAU,GAAG,IAAW,CAAC;AACnD,eAAO,IAAI,WAAW,QAAI,CAAC;AAE3B,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAIxD;AAED,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;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,CA+BP;AAYD,wBAAgB,mBAAmB,IAAI,IAAI,CAc1C;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,CAuBzD;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"}
@@ -143,6 +143,12 @@ export let PERMUTATION_TABLE = null; // r7 * MOVE_COUNT + m
143
143
  export let ORIENTATION_TABLE = null; // rO * MOVE_COUNT + m
144
144
  export let CENTERS_TABLE = null; // rC * MOVE_COUNT + m
145
145
  export let POLICY_TABLE = null; // optimal move per rank, 3-bit packed
146
+ /**
147
+ * Byte-expanded policy (one optimal move per rank, no bit-unpacking in
148
+ * the hot walk). Built at load from the 3-bit packed table: +3.1 MB RAM,
149
+ * zero wire/format change.
150
+ */
151
+ export let POLICY_TABLE_EXPANDED = null;
146
152
  export let EVEN_CENTER_RANK = null; // rC -> compact 0..359 (65535 = odd)
147
153
  export let EVEN_CENTER_FULL = null; // compact 0..359 -> rC (derived at load)
148
154
  export let ORIENT_CLASS = null; // rO -> class 0..8
@@ -192,9 +198,19 @@ export function assignTables(tables) {
192
198
  for (let i = 0; i < 7; i++)
193
199
  ORIENT_UNRANK[rO * 7 + i] = orient[i + 1];
194
200
  }
201
+ POLICY_TABLE_EXPANDED = expandPolicy3Bit(tables.policyTable, STATE_COUNT);
195
202
  SOLVED_RANK = rankFromComponents(0, 0, 0);
196
203
  tablesLoaded = true;
197
204
  }
205
+ function expandPolicy3Bit(packed, count) {
206
+ const expanded = new Uint8Array(count);
207
+ for (let index = 0; index < count; index += 1) {
208
+ const bits = index * 3;
209
+ const byteIndex = bits >> 3;
210
+ expanded[index] = (((packed[byteIndex] | ((packed[byteIndex + 1] ?? 0) << 8)) >> (bits & 7)) & 0x07);
211
+ }
212
+ return expanded;
213
+ }
198
214
  export function resetTablesForTests() {
199
215
  tablesLoaded = false;
200
216
  PERMUTATION_TABLE = null;
@@ -208,6 +224,7 @@ export function resetTablesForTests() {
208
224
  PERM7_UNRANK = null;
209
225
  CENTER_UNRANK = null;
210
226
  ORIENT_UNRANK = null;
227
+ POLICY_TABLE_EXPANDED = null;
211
228
  }
212
229
  // ---------------------------------------------------------------------------
213
230
  // Compact rank helpers
@@ -1 +1 @@
1
- {"version":3,"file":"browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/sq1/solver/browser-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAMvD,wBAAgB,sBAAsB,IAAI,mBAAmB,GAAG,SAAS,CAExE;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAED,wBAAsB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA2CtF"}
1
+ {"version":3,"file":"browser-loader.d.ts","sourceRoot":"","sources":["../../../../src/puzzles/sq1/solver/browser-loader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAQvD,wBAAgB,sBAAsB,IAAI,mBAAmB,GAAG,SAAS,CAExE;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C;AAED,wBAAsB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAoBtF"}