From edb1693151e33d8a41f22935fece4372ab3e6633 Mon Sep 17 00:00:00 2001 From: Lumi Date: Fri, 9 Oct 2026 23:24:46 -0700 Subject: [PATCH] Prove existing Rust prompt rules can run through a JS WASM bridge --- .gitignore | 2 + README.md | 1 + docs/architecture.md | 3 + docs/testing.md | 9 + test/wasm-core-proof/Cargo.lock | 341 ++++++++++++++++++++++++++++++ test/wasm-core-proof/Cargo.toml | 24 +++ test/wasm-core-proof/README.md | 59 ++++++ test/wasm-core-proof/browser.html | 16 ++ test/wasm-core-proof/check.mjs | 50 +++++ test/wasm-core-proof/src/lib.rs | 49 +++++ 10 files changed, 554 insertions(+) create mode 100644 test/wasm-core-proof/Cargo.lock create mode 100644 test/wasm-core-proof/Cargo.toml create mode 100644 test/wasm-core-proof/README.md create mode 100644 test/wasm-core-proof/browser.html create mode 100644 test/wasm-core-proof/check.mjs create mode 100644 test/wasm-core-proof/src/lib.rs diff --git a/.gitignore b/.gitignore index 19592c6..9cad010 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ logs/ node_modules/ dist/ server/target/ +test/wasm-core-proof/target/ +test/wasm-core-proof/pkg/ *.sqlite *.sqlite-shm *.sqlite-wal diff --git a/README.md b/README.md index 76a8a90..d4539a7 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ Mochi: [clock]{21:40 night} it's so late!! [emote:sleepy] [want:snack] | `test/browser-flow.js` | repeatable playwright-cli browser regression flow against a local debug preview | | `test/hosting-flow.js` | real host creation, lost response, closed-page execution, both transfers and offline recovery | | `server/tests/native.rs`, `test/host-startup.py` | opt-in real native CPU/GPU/private-HTTP checks and actual CPU fallback at startup | +| `test/wasm-core-proof/` | opt-in build/interop proof of the existing Rust prompt/lookup code in JavaScript | | `test/dev-site.sh`, `test/serve.py` | local preview: builds the site, stages the runtime and models and serves them with COOP/COEP headers | | `test/bench.html` | brain load time, speed and memory in a real browser (included as unlinked `bench.html` in every site build) | diff --git a/docs/architecture.md b/docs/architecture.md index de4b173..b1d02a0 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -62,6 +62,7 @@ browser | `test/browser-flow.js` | Repeatable playwright-cli flow for a local debug preview with a real brain. | | `test/hosting-flow.js` | Real remote creation, lost activation acknowledgement, closed-page execution, transfers and checkpoint recovery. | | `server/tests/native.rs`, `test/host-startup.py` | Opt-in real CPU/GPU/private-HTTP qualification and startup fallback. | +| `test/wasm-core-proof/` | Isolated build/interop proof of Rust prompt/lookup exports to JS; not imported by the app. | | `train/migrate_v6.py` | Rewrites old chat headers and sleep events, rebuilds loss spans, removes old teen rows and validates the combined dataset. | | `test/data_rules.py` | Regression cases for sleep, offers, validation, loss masks and migration; includes a stand-in sleep teacher. | | `test/site_tools.py` | Stdlib-only regressions that exercise the real build, publish and preview scripts with disposable model/runtime stand-ins. | @@ -175,6 +176,8 @@ browser storage/lifecycle and browser inference remain in TypeScript; HTTP, SQLite, queue routing and native inference remain in the host. Python still needs independent training-format parity. This is a recommendation, not an implemented migration or accepted change to the prompt contract. +An isolated [WASM proof](../test/wasm-core-proof/README.md) builds the existing +Rust world source directly and checks the JavaScript boundary separately. Start with prompt rendering and lookups, preserve byte-for-byte Python/native/ WASM parity, then move simulation. Pass time/calendar information and random diff --git a/docs/testing.md b/docs/testing.md index 6bc6e14..79e4691 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -216,6 +216,15 @@ the latency comparison. The steady phase permits ordinary diary/cancellation behavior. This receipt validates CPU and rules; GPU routing and mixed steady traffic are awaiting a shared-device window. It is not a serving-capacity claim. +## Shared rules WASM proof (2026-10-09) + +The opt-in [proof](../test/wasm-core-proof/README.md) compiles existing Rust +prompt/lookup code to WebAssembly with a JS object bridge. Node.js and a real +desktop Chromium page each matched Python across 300 states and 15 lists. +Generated WASM is about 1.13 MB raw/346 KB gzip, plus 19 KB raw JS glue. +This validates feasibility and the interop boundary; the app still uses its +current TypeScript rules, and the shared-core migration has not started. + ## Preview server ```sh diff --git a/test/wasm-core-proof/Cargo.lock b/test/wasm-core-proof/Cargo.lock new file mode 100644 index 0000000..8d06abc --- /dev/null +++ b/test/wasm-core-proof/Cargo.lock @@ -0,0 +1,341 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "num-traits", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "siphasher" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d62a2e0561533f2ca2561d0cf27fd9fedb640a1bf2616ff5d5c80d99017faadc" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokenling-wasm-proof" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono-tz", + "regex", + "serde", + "serde-wasm-bindgen", + "serde_json", + "wasm-bindgen", +] + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/test/wasm-core-proof/Cargo.toml b/test/wasm-core-proof/Cargo.toml new file mode 100644 index 0000000..9b2a195 --- /dev/null +++ b/test/wasm-core-proof/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "tokenling-wasm-proof" +version = "0.1.0" +edition = "2024" +rust-version = "1.88" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +anyhow = "1" +chrono-tz = "0.10.4" +regex = "1" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-wasm-bindgen = "0.6" +wasm-bindgen = "=0.2.129" + +[profile.release] +opt-level = "s" +lto = true +codegen-units = 1 +strip = "debuginfo" diff --git a/test/wasm-core-proof/README.md b/test/wasm-core-proof/README.md new file mode 100644 index 0000000..c620c37 --- /dev/null +++ b/test/wasm-core-proof/README.md @@ -0,0 +1,59 @@ +# Rust/WASM rules feasibility proof + +This opt-in proof compiles the **existing** `server/src/world.rs` and its +types for JavaScript. It exports prompt rendering, lookups and a parity adapter +through wasm-bindgen/serde-wasm-bindgen. The application does not load it and +the server's native build is unchanged. No model, save or network access is +part of the module. It is a build/interop experiment, not the shared-core migration. + +Use Rust 1.88+ with `wasm32-unknown-unknown`, Node.js 24+ and wasm-bindgen-cli +**0.2.129**, matching the crate exactly. Keep generated packages outside Git +(the local `target/` and `pkg/` directories are ignored). + +```sh +rustup target add wasm32-unknown-unknown +cargo install wasm-bindgen-cli --version 0.2.129 --locked +cargo build --release --locked --manifest-path test/wasm-core-proof/Cargo.toml --target wasm32-unknown-unknown +wasm-bindgen test/wasm-core-proof/target/wasm32-unknown-unknown/release/tokenling_wasm_proof.wasm --target nodejs --out-dir test/wasm-core-proof/pkg/node +python3 -c 'from pathlib import Path; Path("test/wasm-core-proof/pkg/node/package.json").write_text("{\"type\":\"commonjs\"}\n")' +node test/wasm-core-proof/check.mjs test/wasm-core-proof/pkg/node/tokenling_wasm_proof.js +wasm-bindgen test/wasm-core-proof/target/wasm32-unknown-unknown/release/tokenling_wasm_proof.wasm --target web --out-dir test/wasm-core-proof/pkg/web +``` + +The Node check reuses the project's Python fixture generator: 300 cases and +15 closed lists, plus headers, clock/look/recall and wake probabilities. It +also calls the exported JS header/lookup functions individually, and reports +module-load and header-batch times as diagnostics. Measurements are desktop +Node timings and do not establish phone/browser speed. +The package marker is needed because this repository uses `type: module`, +while wasm-bindgen's Node target generates CommonJS. + +For a web smoke check, serve the generated web package with any local static +server and import its ES module. Call its default `init()` before exported +functions. The structured state bridge supports JavaScript objects and arrays; +`json_compatible()` preserves plain-object output and JSON numeric conventions. +Keep a typed TS wrapper around `JsValue` exports for application type checking. + +The included `browser.html` runs the same parity in a browser: copy it to the +generated web package as `index.html` and write the output of +`python3 test/parity_states.py 1` beside it as `cases.json`. Serve that package +locally and read `window.proof`; the page contains only synthetic test fixtures. + +## Functional receipt (2026-10-09) + +Rust 1.98.1/wasm-bindgen 0.2.129/serde-wasm-bindgen 0.6.5 built the existing +Rust sources successfully. Node.js 24 and headless desktop Chromium each +passed 300 states/15 lists with zero mismatches, including individual exported +header/lookup calls and wake probabilities. No app integration was changed. + +The generated WASM was 1,127,760 bytes, 345,732 bytes with gzip; its JS glue +was 19,010 bytes, 4,455 bytes with gzip. Node initialization was about 8 ms; +one desktop browser load was about 15 ms. Node's five 300-header batches +took 4.5–6.1 ms each. These are single-desktop diagnostics with no phone, +download, memory or optimized-asset study, not runtime performance promises. + +An application migration needs a separate rules crate, UI build/hash/offline +integration and initialization/error recovery. Move prompt/lookups first, then +body/save rules with explicit clock/randomness inputs. Keep Preact/canvas, +browser storage and inference outside the rules crate. Python training parity +remains an independent contract check. See [the design option](../../docs/architecture.md#shared-nativebrowser-rules-design-option). diff --git a/test/wasm-core-proof/browser.html b/test/wasm-core-proof/browser.html new file mode 100644 index 0000000..4b4bca5 --- /dev/null +++ b/test/wasm-core-proof/browser.html @@ -0,0 +1,16 @@ +Rust rules WASM proof
loading
\ No newline at end of file diff --git a/test/wasm-core-proof/check.mjs b/test/wasm-core-proof/check.mjs new file mode 100644 index 0000000..d08bf23 --- /dev/null +++ b/test/wasm-core-proof/check.mjs @@ -0,0 +1,50 @@ +// node test/wasm-core-proof/check.mjs NODE_BINDINGS_FILE +// The bindings file is generated by wasm-bindgen --target nodejs. No model, +// private save or application storage is read or written by this proof. +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { createRequire } from "node:module"; +import { resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { performance } from "node:perf_hooks"; +if (process.argv.length !== 3) throw new Error("Supply the generated Node bindings file"); +const expected = JSON.parse( + execFileSync("python3", [fileURLToPath(new URL("../parity_states.py", import.meta.url)), "1"], { + maxBuffer: 1 << 26, + }), +); +const started = performance.now(); +const core = createRequire(import.meta.url)(resolve(process.argv[2])); +const loadedMs = performance.now() - started; +const actual = core.parity(expected); +assert.deepEqual(actual.lists, expected.lists); +for (let i = 0; i < expected.cases.length; i++) { + for (const key of ["chat", "diary", "look", "clock", "recall"]) + assert.equal(actual.cases[i][key], expected.cases[i][key], `${i}: ${key}`); + assert.ok(actual.cases[i].wake.every((p, j) => Math.abs(p - expected.cases[i].wake[j]) < 1e-12)); + assert.equal(core.header("chat", expected.cases[i].st), expected.cases[i].chat); + assert.equal( + core.tool_result(expected.cases[i].st, "recall", expected.cases[i].q), + expected.cases[i].recall, + ); +} +const trials = []; +for (let round = 0; round < 5; round++) { + const begin = performance.now(); + for (const c of expected.cases) core.header("chat", c.st); + trials.push(performance.now() - begin); +} +process.stdout.write( + JSON.stringify( + { + states: expected.cases.length, + lists: Object.keys(expected.lists).length, + mismatches: 0, + loadedMs, + headersPerTrial: 300, + headerBatchMs: trials, + }, + null, + 2, + ) + "\n", +); diff --git a/test/wasm-core-proof/src/lib.rs b/test/wasm-core-proof/src/lib.rs new file mode 100644 index 0000000..415d944 --- /dev/null +++ b/test/wasm-core-proof/src/lib.rs @@ -0,0 +1,49 @@ +//! Isolated feasibility proof: existing Rust world code, callable from JS. +//! The application does not import this module. No model, network or storage. +#[path = "../../../server/src/types.rs"] +#[allow(dead_code)] +mod types; +#[path = "../../../server/src/world.rs"] +#[allow(dead_code)] +mod world; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use wasm_bindgen::prelude::*; +#[derive(Deserialize)] +struct Case { + st: types::WorldState, + q: String, +} +#[derive(Deserialize)] +struct Input { + cases: Vec, +} +fn sorted(values: &[&str]) -> Vec { + let mut result: Vec<_> = values.iter().map(|v| v.to_string()).collect(); + result.sort(); + result +} +#[wasm_bindgen] +pub fn header(kind: &str, state: JsValue) -> Result { + if !["chat", "diary"].contains(&kind) { + return Err(JsError::new("unknown prompt kind")); + } + let state: types::WorldState = serde_wasm_bindgen::from_value(state)?; + Ok(world::header(kind, &state)) +} +#[wasm_bindgen] +pub fn tool_result(state: JsValue, name: &str, arg: &str) -> Result { + let state: types::WorldState = serde_wasm_bindgen::from_value(state)?; + Ok(world::tool_result(&state, name, arg) + .serialize(&serde_wasm_bindgen::Serializer::json_compatible())?) +} +#[wasm_bindgen] +pub fn parity(input: JsValue) -> Result { + let input: Input = serde_wasm_bindgen::from_value(input)?; + let cases:Vec<_>=input.cases.iter().map(|c| { + let wake:Vec<_>=["talk","touch","light"].iter().flat_map(|kind|(1..=6).map(move|n|world::wake_chance(c.st.stats.energy,kind,n))).collect(); + json!({"chat":world::header("chat",&c.st),"diary":world::header("diary",&c.st),"clock":world::tool_result(&c.st,"clock",""),"look":world::tool_result(&c.st,"look",""),"recall":world::tool_result(&c.st,"recall",&c.q),"wake":wake}) + }).collect(); + let output = json!({"cases":cases,"lists":{"STAGES":world::STAGES,"PERSONALITIES":world::PERSONALITIES,"FOODS":world::FOODS,"GIFTS":world::GIFTS,"DREAMS":world::DREAMS,"EMOTES":world::EMOTES,"WANTS":world::WANTS,"WALKS":world::WALKS,"DOODLES":world::DOODLES,"WEATHER_DAY":world::WEATHER_DAY,"WEATHER_NIGHT":world::WEATHER_NIGHT,"TOYS":world::TOYS,"EFFECT_TOOLS":sorted(world::EFFECT_TOOLS),"LOOKUP_TOOLS":sorted(world::LOOKUP_TOOLS),"ANSWER_TOOLS":sorted(world::ANSWER_TOOLS)}}); + Ok(output.serialize(&serde_wasm_bindgen::Serializer::json_compatible())?) +} -- 2.51.2