motely-wasm 23.3.0 → 24.1.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.
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,
@@ -36,7 +38,7 @@ await bootsharp.boot();
36
38
 
37
39
  ## Parse and validate JAML
38
40
 
39
- Use `fromYaml` or `fromJson` once, then pass the returned `JamlConfig` to analyzer/search calls. Invalid filters fail loudly; unknown keys are not silently ignored.
41
+ Use `fromYaml` or `fromJson` once, then pass the returned `JamlConfig` to analyzer/search calls. Invalid filters fail loudly, and unknown keys raise errors too, so every mistake surfaces at parse time.
40
42
 
41
43
  ```js
42
44
  const jaml = MotelyJaml.fromYaml(`
@@ -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.
@@ -124,7 +178,7 @@ MotelyJaml.canonicalizeLine("Showman in antes 1, 2"); // "Showman in ant
124
178
 
125
179
  ## Vocabulary
126
180
 
127
- `MotelyJaml.listItems(kind, query)` serves the real engine vocabulary — jokers, vouchers, tags, bosses, and the rest — for autocomplete and agent grounding. Names come straight from the engine enums, so nothing hand-maintained can drift.
181
+ `MotelyJaml.listItems(kind, query)` serves the real engine vocabulary — jokers, vouchers, tags, bosses, and the rest — for autocomplete and agent grounding. Names come straight from the engine enums, so the vocabulary stays in lockstep with the engine by construction.
128
182
 
129
183
  ```js
130
184
  MotelyJaml.listItems("joker", "lucky"); // ["LuckyCat", ...] — case-insensitive substring match
@@ -152,7 +206,7 @@ npm run serve # hand-drive the test UI at http://127.0.0.1:4173/
152
206
  npm run pack:check
153
207
  ```
154
208
 
155
- The test UI (`testui/index.html`) is a plain ES-module page with a CodeMirror 6 editor: live engine-driven lint and completion while you type, a Jimmolate filter box compiled straight into the browser search, and a results table — feedback is continuous, so there is no validate button. The Playwright specs in `tests-ui/` prove the package where UX lives: a real browser.
209
+ The test UI (`testui/index.html`) is a plain ES-module page with a CodeMirror 6 editor: live engine-driven lint and completion while you type, a Jimmolate filter box compiled straight into the browser search, and a results table — feedback is continuous — live linting validates every keystroke, which is why the UI stays button-free. The Playwright specs in `tests-ui/` prove the package where UX lives: a real browser.
156
210
 
157
211
  Releasing, from `Motely.Wasm/`: sync `"version"` in `package.json` to `<MotelyVersion>` in the repo-root `Directory.Packages.props`, run `npm test` and `npm run test:ui` green, then `npm publish`.
158
212