motely-wasm 24.0.0 → 24.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,8 @@ npm install motely-wasm
14
14
 
15
15
  Bootsharp exports a default runtime object plus named API namespaces. Subscribe to exported events and bind imports before `boot()`.
16
16
 
17
+ This package ships embedded: the boot resources travel inside the module, so `boot()` takes no argument. `getStatus()` reports where the runtime is — boot when it returns `BootStatus.Standby`, and it's ready to use at `BootStatus.Booted`.
18
+
17
19
  ```js
18
20
  import bootsharp, {
19
21
  MotelyJaml,
@@ -94,6 +96,58 @@ const fromWalk = await MotelySearch.searchSequential(jaml, 0n, 1n, 1);
94
96
 
95
97
  `searchSequential` uses bigint batch indices because the C# parameters are `long`.
96
98
 
99
+ ## The fleet — one engine per web worker
100
+
101
+ A batch index is a seed prefix. `batchChars` names how many trailing characters a single batch enumerates, so one batch covers `35 ** batchChars` seeds and the space holds `35 ** (8 - batchChars)` batches. Batch `0` is the block of seeds beginning `1111…`, batch `1` the next block, and so on up through `GGGG…`. Handing a worker a range of batch indices is the same as handing it a range of seed prefixes.
102
+
103
+ Give each worker its own contiguous block and the fleet needs no coordination at all:
104
+
105
+ ```js
106
+ const totalBatches = 35 ** (8 - batchChars);
107
+ const blockSize = Math.floor(totalBatches / fleetSize);
108
+
109
+ // worker w owns [w * blockSize, (w + 1) * blockSize) — the last worker takes the remainder
110
+ const startBatch = w * blockSize;
111
+ const endBatch = w === fleetSize - 1 ? totalBatches : (w + 1) * blockSize;
112
+ await MotelySearch.searchSequential(config, BigInt(startBatch), BigInt(endBatch), batchChars);
113
+ ```
114
+
115
+ Blocks are disjoint by construction, so two workers never report the same seed and the fleet shares nothing: no queue, no locks, no mid-run coordination. A worker computes its own range from its own index, so it never needs to know the fleet size or agree with anyone. Losing a worker simply leaves its block unfinished, and every other block is untouched.
116
+
117
+ Search the seed space for however long the user cares to wait. The full 35⁸ ≈ 2.25 trillion seeds take days; real searches sample, then stop. Blocks make that sampling addressable — you can say which prefixes you covered, resume from a saved cursor, and hand block `w` to a second browser or a server just as easily as to a second worker.
118
+
119
+ Report progress per worker. Each worker owns its own counts, and summing them for a headline number is the consumer's job — a fleet has no single global tick to subscribe to.
120
+
121
+ Two environment rules keep a worker from hanging silently:
122
+
123
+ - **Use a module worker.** `new Worker(url, { type: "module" })`. Chromium forbids dynamic `import()` inside a classic worker, and import maps don't reach workers, so pass `motely-wasm`'s absolute URL and `import()` it inside.
124
+ - **Release `self.onmessage` before `boot()`.** `dotnet.js` instantiates assets over the worker's global message channel and never resolves while an app handler holds it ([dotnet/runtime#114918](https://github.com/dotnet/runtime/issues/114918)). Take the init message once, null the handler, and run all further traffic over a transferred `MessagePort`.
125
+
126
+ Stop a worker with `worker.terminate()` — it ends the batch mid-loop, immediately.
127
+
128
+ ```js
129
+ // worker.js
130
+ self.onmessage = (e) => {
131
+ self.onmessage = null; // free the global channel for dotnet.js
132
+ const { port, motelyUrl, jaml, startBatch, endBatch, batchChars } = e.data;
133
+ (async () => {
134
+ const engine = await import(motelyUrl);
135
+ engine.Jimmolate.filter = () => 1;
136
+ if (engine.default.getStatus() === engine.default.BootStatus.Standby)
137
+ await engine.default.boot();
138
+
139
+ const config = engine.MotelyJaml.fromYaml(jaml);
140
+ const found = [];
141
+ const onScored = (r) => found.push({ seed: r.seed, score: r.score });
142
+ engine.MotelySearch.onScoredResult.subscribe(onScored);
143
+ await engine.MotelySearch.searchSequential(config, BigInt(startBatch), BigInt(endBatch), batchChars);
144
+ port.postMessage({ type: "finished", found });
145
+ })();
146
+ };
147
+ ```
148
+
149
+ Boot each worker's engine once and reuse it: booting is the expensive part, searching is not.
150
+
97
151
  ## Jimmolate
98
152
 
99
153
  Jimmolate is a JS-authored seed filter in the engine filter chain, speaking the real Immolate `.cl` contract: `filter(inst) => score` — a number, and the host sets the bar with a score cutoff. Returning `true`/`false` also works for convenience; booleans coerce to 1/0. The filter receives the live `MotelySingleSearchContext` as an interop instance, so it can drive every query a native C# filter can. Bind it before `boot()`; keep-all (`(inst) => 1`) is the neutral binding.