# cymsimweb A browser companion of [cymsim](https://vault1.magland.org/proof-of-concept/cymsim), the Python package for simulating struck cymbals. It solves the same model with the same discretization (a Marguerre-von Karman shallow shell on a triangulated disk, constant-strain membrane triangles and Discrete Kirchhoff bending triangles, explicit leapfrog in time, rigid exciters on power-law contact springs, a Rayleigh-integral listener), but the time loop runs on the GPU through WebGPU, and the configuration is edited in the page. The set-up (mesh, element stiffnesses, stability bound, rigid modes, contact patches) is computed in TypeScript in float64 once per run; the sub-steps run in float32 on the GPU, two compute dispatches per sub-step. The method and the differences from the Python package are described in [THEORY.md](THEORY.md). ## Running Requires Node 20 or later and, for the GPU time loop, a browser with WebGPU (Chrome or Edge 113+; Firefox and Safari with WebGPU enabled). Without WebGPU the same loop runs on the CPU in JavaScript, which is fine for the coarse preview preset and impractical for the full-resolution ones. npm install npm run dev # development server npm run build # static site in dist/ npm test # CPU tests, then the GPU kernels under Dawn (see below) ## Using the app The sidebar holds the preset selector, YAML import and download, the run controls, and three tabs: the YAML editor, a form with the same fields, and the set-up summary of the last run. The YAML schema is that of the Python package (the same file runs in both), and the four presets are its example configurations. Edits in the form rewrite the YAML and edits in the YAML rebuild the form; unknown fields, wrong types and out-of-range values are listed under the editor, and a run does not start while issues remain. Omitted fields take the defaults, which describe an 18 inch medium-thin B20 crash. Start builds the set-up and begins the run. The main area shows the cymbal as a lit height field (drag to orbit, wheel to zoom, the exaggeration slider scales the displacement), the spectrogram of the listener signal growing as chunks of audio arrive, and a playback bar. **Displacement movie.** During a run the transverse displacement of every node is captured at the frame rate in the plate toolbar (2000 frames per second of simulated time by default; 0 disables capture). Afterwards Replay plays those frames back in the plate view, slowed down by the factor on the slider (x50 by default, up to x10000), and the position slider scrubs through them. A frame is stored as one 16-bit value per node against that frame's own peak, so a 2 s run of the default 4657-node mesh at 2000 fps is about 19 MB; capture stops at 256 MB and the toolbar says so. The lowest modes of an 18 inch crash are a few hundred hertz, so x200 to x500 is about right for watching individual cycles, and x2000 or more for the contact itself. **Spectrogram axis.** The frequency axis is linear by default, which shows the upper partials and the noise floor of a crash as they are spaced; the scale selector switches to logarithmic, and the max-f selector zooms the axis to 20, 10, 5 or 2 kHz. Play plays the part of the signal that exists so far from the slider position and stops where the signal ends; press again to hear newer material. The signal selector switches between the unbaffled estimate (front minus diffracted back, the default) and the baffled Rayleigh integral; the WAV download writes the selected one at 16 bits, normalized to 0.9 full scale or at 1 Pa = 0.05 full scale. Under the views, the energy table (kinetic, membrane, bending, exciters, and the nonlinearity meter max|w|/h) and the hit table (peak force, contact time, exciter velocity) update about once a second, and the log reports the set-up and the run. URL parameters: `?preset=` selects a preset (`crash_mallet`, `crash_stick`, `hit_train`, `quick_cpu`), `backend=cpu` forces the CPU loop, `autostart=1` starts on load, `chat=1` opens the chat panel, `bridge=ws://host:port` points it at a bridge other than `ws://localhost:8765`. ## Chat with Claude Code The Chat button opens a panel in which Claude Code talks about the app, reads its state, and changes it: it can write a configuration from a description ("a thin 20 inch ride hit with a stick near the edge, then a mallet stroke a second later"), load a preset, start, pause and stop runs, play the sound, replay the displacement movie at a slowdown you ask for, change the spectrogram axis, and interpret a finished run (contact force, nonlinearity, band onsets and centroid, that is, whether the upward cascade happened). It has read access to this repository, so it can answer questions about the method from the source and from THEORY.md. A web page cannot run Claude Code, so the panel talks to a small bridge process on your machine, which uses your own Claude Code login (subscription or API key, whatever `claude` is logged in with): git clone https://vault1.magland.org/proof-of-concept/cymsimweb cd cymsimweb && npm install && npm run bridge The bridge listens on `ws://localhost:8765` (127.0.0.1 only) and accepts connections from the deployed site and from localhost pages; it also serves the built app at `http://localhost:8765` after `npm run build`. Open the site, press Chat, and the panel connects. `node bridge/server.mjs --port 9000 --model claude-sonnet-5 --origin https://other.site` changes the port, the model (default: your Claude Code default) and the allowed page origins. Each browser connection is one Claude Code session that persists across turns; New starts a fresh one, Stop interrupts the current turn. The bridge gives Claude the read-only tools (Read, Glob, Grep, WebFetch) plus the app tools `get_app_state`, `set_yaml`, `select_preset`, `start_run`, `pause_run`, `stop_run`, `play_audio`, `set_signal`, `play_movie`, `set_movie_fps` and `set_spectrogram_axis`, which execute in the page; it cannot edit files or run commands. `node tests/bridge.smoke.mjs` runs one real turn against a scripted fake page (it uses your quota, so it is not part of `npm test`). ## Speed At the default resolution (6 mm elements, about 4700 nodes, 54 sub-steps per audio sample) one second of sound is about 2.4 million sub-steps, or 4.8 million compute dispatches. On a discrete GPU this takes tens of seconds; on an integrated GPU several minutes. The chunk size adapts to keep about 0.4 s of wall time per chunk, so the spectrogram and the playback frontier advance a few times per second regardless of the hardware. ## Tests `npm run test:cpu` runs the node:test files: the mesher (manifold, quality, and exact agreement with the Python mesher), the DKT element against the Python element, the shell assembly (mass, stability bound, rigid modes and element checks against Python), the CPU time loop against a Python run (dent-free geometry, float64: peak force, contact time, rebound velocity and energies to 1e-4 in the linear case, and the listener pressure to a few percent rms), the YAML layer, and the STFT. `npm run test:gpu` runs the same WGSL kernels under Node with Google Dawn (the `webgpu` package, software backend) and compares them with the CPU loop. It is a plain script rather than a node:test file because Dawn aborts under the test runner. The reference numbers in `tests/fixtures/python_reference.json` were produced by the Python package. ## Layout | path | contents | |---|---| | `src/config/` | configuration types, defaults, YAML parsing and validation, presets | | `src/model/` | seeded RNG, cymbal geometry, mesher, DKT element, shell assembly, element forces and energies, exciters, radiation, set-up | | `src/cpu/` | float64 reference time loop, also the fallback without WebGPU | | `src/gpu/` | WGSL kernels and the WebGPU driver | | `src/analysis/`, `src/audio/` | incremental STFT and band summary, the displacement-movie store and player; Web Audio playback and WAV export | | `src/ui/`, `src/run.ts`, `src/main.ts` | editor, form, plate and spectrogram views, readouts, chat panel, the run loop, wiring | | `src/chat/`, `bridge/` | WebSocket client of the bridge and the tool executor; the local Claude Code bridge (Agent SDK) | | `tests/` | node:test files, the Dawn check, and the Python fixture |