motely-wasm 19.4.0 → 20.0.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
@@ -1,6 +1,6 @@
1
1
  # motely-wasm
2
2
 
3
- Balatro seed finder and per-seed analyzer, compiled to WebAssembly via [Bootsharp](https://bootsharp.com). Runs in browsers and Node/Deno/Bun.
3
+ WebAssembly build of [MotelyJAML](https://github.com/OptimusPi/MotelyJAML) — the SIMD Balatro seed search engine with JAML filter support. Powers [seedfinder.app](https://seedfinder.app).
4
4
 
5
5
  ## Install
6
6
 
@@ -8,192 +8,119 @@ Balatro seed finder and per-seed analyzer, compiled to WebAssembly via [Bootshar
8
8
  npm install motely-wasm
9
9
  ```
10
10
 
11
- ## Module layout
12
-
13
- The root barrel (`motely-wasm`) re-exports only the Bootsharp runtime (`boot`, `getStatus`, `manifest`, …). The Motely API and enums live in generated submodules and **must be imported directly** via the package's subpath exports:
14
-
15
- ```js
16
- import bootsharp from "motely-wasm";
17
- import { Program as Motely } from "motely-wasm/motely/wasm";
18
- import * as enums from "motely-wasm/motely/enums";
19
- ```
20
-
21
- Additional submodules: `motely-wasm/motely/analysis`, `motely-wasm/motely/filters/jaml`, `motely-wasm/motely/filters`.
22
-
23
11
  ## Boot
24
12
 
25
- Binaries are sideloaded (published to `dist/bin/` as separate files). Pass the wasm bytes directly `boot()` accepts either raw bytes or a URL root.
13
+ The WASM binary is **sideloaded** a separate `dist/bin/motely-wasm.wasm`, **not** base64-embedded — so `boot()` **must** be told where to find it. A no-arg `boot()` will not load the runtime and every search silently does nothing.
26
14
 
27
- ### Node
15
+ In Node, read the bytes and pass them directly (`fetch` can't read `file://` URLs):
28
16
 
29
17
  ```js
30
18
  import { readFile } from "node:fs/promises";
31
- import { resolve, dirname } from "node:path";
19
+ import { dirname, resolve } from "node:path";
32
20
  import { fileURLToPath } from "node:url";
33
21
  import bootsharp from "motely-wasm";
34
- import { Program as Motely } from "motely-wasm/motely/wasm";
35
- import * as enums from "motely-wasm/motely/enums";
36
-
37
- // [Import] callbacks must be assigned BEFORE boot.
38
- Motely.reportWasmError = (message) => console.error("[WASM ERROR]", message);
39
- Motely.jimmolateProbe = () => false; // required even when not using Jimmolate
40
22
 
41
- // import.meta.resolve("motely-wasm") → <pkg>/dist/index.mjs; dirname is already <pkg>/dist.
42
- const distDir = dirname(fileURLToPath(import.meta.resolve("motely-wasm")));
43
- const wasm = await readFile(resolve(distDir, "bin", bootsharp.manifest.wasm));
23
+ const dist = resolve(dirname(fileURLToPath(import.meta.url)), "node_modules/motely-wasm/dist");
24
+ const wasm = await readFile(resolve(dist, "bin", bootsharp.manifest.wasm));
44
25
  await bootsharp.boot({ wasm });
45
26
  ```
46
27
 
47
- ### Browser
28
+ In the browser, pass the root URL your host serves `dist/` at:
48
29
 
49
30
  ```js
50
- import bootsharp from "motely-wasm";
51
- import { Program as Motely } from "motely-wasm/motely/wasm";
52
-
53
- Motely.reportWasmError = (message) => console.error("[WASM ERROR]", message);
54
- Motely.jimmolateProbe = () => false;
55
-
56
- // Pass the URL root where dist/bin/ is served.
57
- await bootsharp.boot("/assets/motely-wasm/dist/bin");
31
+ await bootsharp.boot("/motely-wasm/dist");
58
32
  ```
59
33
 
60
- ## Core API
34
+ Guard re-boots with `bootsharp.getStatus() === bootsharp.BootStatus.Standby`.
61
35
 
62
- All methods are on the `Motely` (i.e. `Program`) namespace.
63
-
64
- ### Parse a JAML filter
36
+ ## Imports
65
37
 
66
38
  ```js
67
- const cfg = Motely.parseJaml(`
68
- name: My Filter
69
- deck: Red
70
- stake: White
71
- must:
72
- - joker: Triboulet
73
- antes: [1, 2, 3]
74
- `);
75
- ```
76
-
77
- `parseJaml` throws on invalid YAML. Use `jamlToJson` / `jsonToJaml` to convert between representations. Use `explainJaml` to get a human-readable summary of a parsed config.
39
+ // Main API — the C# Program class, renamed to Motely
40
+ import { Program as Motely } from "motely-wasm/dist/generated/modules/motely/wasm.g.mjs";
78
41
 
79
- ### Search
42
+ // Enums — MotelyDeck, MotelyStake, etc.
43
+ import * as enums from "motely-wasm/dist/generated/modules/motely/enums.g.mjs";
80
44
 
81
- All `run*Search` methods return an `IMotelySearch` — call `.start()` then listen to events, or call `.runSearchUntilCompletion()` / `await .runSearchAsync()` for synchronous / async use.
82
-
83
- ```js
84
- // Subscribe to results before starting.
85
- Motely.onSeedMatch.subscribe((seed) => console.log("match:", seed));
86
- Motely.onProgress.subscribe((p) => console.log(`${p.percentComplete.toFixed(1)}%`));
87
-
88
- // Sequential: scan all seeds in batch range [0, 1).
89
- const search = Motely.runSequentialSearch(cfg, 0n, 1n);
90
- await search.runSearchAsync();
91
-
92
- // List: check specific seeds.
93
- cfg.seeds = ["PIFREAK1", "DEADBEEF"];
94
- const listSearch = Motely.runSeedListSearch(cfg);
95
- listSearch.runSearchUntilCompletion();
96
- console.log(listSearch.matchingSeeds); // bigint
97
-
98
- // Scored: emits onScoredResult instead of onSeedMatch.
99
- Motely.onScoredResult.subscribe((r) => console.log(r.seed, r.score));
100
- const scored = Motely.runSequentialSearch(cfg, 0n, 1n);
101
- await scored.runSearchAsync();
45
+ // Types MotelyProgress, IMotelySearch, MotelyScoredSeedResult, etc.
46
+ import * as types from "motely-wasm/dist/generated/modules/motely.g.mjs";
102
47
  ```
103
48
 
104
- ### Analyze a seed
105
-
106
- `jamlyzer` runs the JAMLyzer on all seeds in `cfg.seeds` and returns per-ante detail: boss blind, voucher, tags, shop queue, booster packs.
49
+ ## Quick start
107
50
 
108
51
  ```js
109
- cfg.seeds = ["PIFREAK1"];
110
- const result = Motely.jamlyzer(cfg);
111
- for (const { seed, analysis } of result.seeds) {
112
- for (const ante of analysis.antes) {
113
- console.log(`ante ${ante.ante} boss: ${enums.MotelyBossBlind[ante.boss]}`);
114
- for (const { item, matched } of ante.shopQueue) {
115
- console.log(" shop item", item.value, matched ? "(matched)" : "");
116
- }
117
- }
118
- }
119
- ```
52
+ // Optional [Import] hook — surface WASM-side errors. Bind BEFORE boot:
53
+ // Bootsharp snapshots [Import] bindings at boot().
54
+ Motely.reportWasmError = (msg) => console.error("[WASM]", msg);
55
+
56
+ // Subscribe to events
57
+ Motely.onSeedMatch.subscribe(seed => console.log("match:", seed));
58
+ Motely.onProgress.subscribe(p => console.log(`${p.percentComplete.toFixed(1)}%`));
59
+
60
+ // Parse a JAML filter. JAML is YAML, and YAML is a JSON superset — a JSON
61
+ // object string parses too, which is what makes it round-trip cleanly over MCP.
62
+ const config = Motely.parseJaml(`
63
+ name: WeeMonday
64
+ deck: Erratic
65
+ stake: Black
66
+ must:
67
+ - joker: WeeJoker
68
+ antes: [1]
69
+ `);
120
70
 
121
- ### Seed context (low-level)
71
+ // Run a search (blocks until complete — run in a Worker for non-blocking UI).
72
+ const search = Motely.runSequentialSearch(config);
73
+ console.log(search.matchingSeeds, "matches out of", search.totalSeedsSearched);
74
+ ```
122
75
 
123
- For custom logic, get a `MotelySingleSearchContext` and query PRNG streams directly:
76
+ ## API
124
77
 
125
78
  ```js
126
- const ctx = Motely.seedContext("PIFREAK1", enums.MotelyDeck.Red, enums.MotelyStake.White);
127
- const voucher = ctx.getAnteFirstVoucher(1);
128
- console.log("ante 1 voucher:", enums.MotelyVoucher[voucher]);
79
+ Motely.parseJaml(jaml) // string JamlConfig (throws on invalid JAML)
80
+ Motely.explainJaml(config) // human-readable plan summary
81
+ Motely.createPlan(config) // JamlSearchPlan (tally columns, CSV header)
82
+ Motely.jamlToJson(jaml) // JAML string → JSON string
83
+ Motely.jsonToJaml(json) // JSON string → JAML string
84
+ Motely.jamlyzer(seed, lens) // analyze ONE seed through a JamlConfig lens → JamlyzerSnapshot
85
+ Motely.nativeFilterNames() // built-in native filter names
86
+
87
+ Motely.runSequentialSearch(config, ...) // sequential seed search
88
+ Motely.runRandomSearch(config, count) // random seed sample
89
+ Motely.runSeedListSearch(config) // search only config.seeds
90
+ Motely.runAestheticSearch(config, aesthetic) // aesthetic / scored search
91
+ Motely.runNativeListSearch(name, seeds) // named native filter over a seed list
92
+ Motely.runPassthroughListSearch(seeds) // no filter, just iterate seeds
129
93
  ```
130
94
 
131
- ## Jimmolate
132
-
133
- Jimmolate is a custom JS scalar predicate that runs on every seed that passes the base JAML filter. Wire it up and call `enableJimmolate()` after boot:
95
+ ## Events
134
96
 
135
97
  ```js
136
- // Must be set BEFORE boot.
137
- Motely.jimmolateProbe = (ctx) => {
138
- // ctx is MotelySingleSearchContext — same API as seedContext().
139
- return ctx.getSeed().startsWith("PI");
140
- };
141
-
142
- await bootsharp.boot({ wasm });
143
- Motely.enableJimmolate();
144
-
145
- cfg.seeds = ["PIFREAK1", "XYZABCDE"];
146
- Motely.onSeedMatch.subscribe((s) => console.log("jimmolate match:", s));
147
- Motely.runPassthroughListSearch(cfg.seeds).runSearchUntilCompletion();
98
+ Motely.onSeedMatch.subscribe(seed => {}) // string each matching seed
99
+ Motely.onScoredResult.subscribe(result => {}) // MotelyScoredSeedResult { seed, score, tallies }
100
+ Motely.onProgress.subscribe(p => {}) // MotelyProgress
101
+ Motely.onFileChanges.subscribe(changes => {}) // file system changes (browser OPFS)
148
102
  ```
149
103
 
150
- Use `runPassthroughListSearch` to skip the JAML filter entirely and let Jimmolate do all the culling.
151
-
152
- ## JAML quick reference
104
+ ## File system (browser)
153
105
 
154
- ```yaml
155
- name: string # optional display name
156
- deck: Red # MotelyDeck value (case-insensitive)
157
- stake: White # MotelyStake value
158
- seeds: [] # pre-populate for list searches
106
+ `Motely.pickRoot`, `mountRoot`, `unmountRoot`, `readTextFile`, `writeTextFile` use the browser File System Access API via `Bootsharp.FileSystem`. Initialize the JS extension before booting:
159
107
 
160
- must: # all clauses must match
161
- - joker: Triboulet
162
- antes: [1, 2] # ante numbers (1–8)
163
- min: 1 # default 1
164
- max: 2 # optional upper bound
165
-
166
- should: # scoring; doesn't filter, raises score
167
- - voucher: Telescope
168
- antes: [1, 2]
169
- score: 10
108
+ ```js
109
+ import * as fs from "@rewaffle/bootsharp-file-system";
110
+ import { Bootsharp } from "motely-wasm/dist/generated/modules/bootsharp/file-system.g.mjs";
170
111
 
171
- mustNot: # any match rejects the seed
172
- - boss: TheHook
173
- antes: [1]
112
+ fs.init(Bootsharp.FileSystem.FileMounter);
113
+ await bootsharp.boot("/motely-wasm/dist");
174
114
  ```
175
115
 
176
- Clause types: `joker`, `voucher`, `boss`, `tag`, `spectral`, `tarot`, `planet`, `pack`.
177
- Sources can be narrowed via `sources: { shopItems: [0, 1], boosterPacks: [0] }`.
178
-
179
- ## Enums
180
-
181
- All enum values are available in the `enums` module:
182
-
183
- - `MotelyDeck` — Red, Blue, Yellow, Green, Black, Magic, Nebula, Ghost, Abandoned, Checkered, Zodiac, Painted, Anaglyph, Plasma, Erratic
184
- - `MotelyStake` — White, Red, Green, Black, Blue, Purple, Orange, Gold
185
- - `MotelyBossBlind`, `MotelyVoucher`, `MotelyTag`, `MotelyBoosterPack`
116
+ ## Jimmolate
186
117
 
187
- ## Utility
118
+ Scalar predicate filtering after the base SIMD pass. Bind the predicate **before** boot (Bootsharp snapshots `[Import]` bindings at boot), then enable it:
188
119
 
189
120
  ```js
190
- // Convert between JAML text and JSON.
191
- const json = Motely.jamlToJson(jamlText);
192
- const jaml = Motely.jsonToJaml(json);
193
-
194
- // Human-readable description of a parsed config.
195
- const explanation = Motely.explainJaml(cfg);
196
-
197
- // List available native (non-JAML) filter names.
198
- const names = Motely.nativeFilterNames();
121
+ Motely.jimmolatePredicate = (result) => {
122
+ // result is MotelyScoredSeedResult — { seed, score, tallies }
123
+ return result.score > 0; // return true to keep the seed
124
+ };
125
+ Motely.jimmolateEnabled = true;
199
126
  ```
Binary file