laya-pong: a browser paddle decided by Laya at 18.7 ms per frame on Metal
One typed question per frame; the other paddle is three lines of arithmetic as a control. Physics in WebAssembly, decisions in native Laya over POST /decide.
# laya-pong A browser pong game whose left paddle is decided by [Laya](https://github.com/aovestdipaperino/laya-rust), one typed question per frame. The right paddle is three lines of arithmetic, playing the same ball, so you can watch the difference rather than take anyone's word for it. The point of the demo is the clock, not the score. A 421M-parameter encoder answering a `choice` question takes **18.7 ms p50 on Metal**, 56% of a 30 fps frame. Ask it the right way and it matches the arithmetic paddle exactly; ask it the wrong way and it never moves. The right-hand court is the control that keeps both claims honest. ## Why the model is not in the WebAssembly The game is WebAssembly. The model is not, and that is deliberate: - The checkpoint is 847 MB on disk and is upcast to f32 on load, so it wants ~2.4 GB resident. `wasm32` gives you a 4 GB address space and no Metal. - Measured on CPU, one decision over a small state is 138 ms. That is 7 fps before you pay for a single-threaded wasm build, against a 33 ms budget. So the split is: **WebAssembly owns the physics and writes the state down, native Laya owns the decision.** The browser calls `POST /decide` once per frame and gets back a move. ## Building You need Rust with the `wasm32-unknown-unknown` target and `wasm-pack`. The `laya` crate comes in as a git dependency, so there is nothing else to clone. ```sh git clone https://github.com/aovestdipaperino/laya-pong cd laya-pong ``` Download the [checkpoint](https://huggingface.co/convaiinnovations/laya) into `models/laya-base` (~847 MB, Apache-2.0, ungated): ```sh mkdir -p models/laya-base/encoder models/laya-base/tokenizer B=https://huggingface.co/convaiinnovations/laya/resolve/main for f in model.safetensors encoder/config.json tokenizer/tokenizer.json