A Stash plugin that drives TCode devices (OSR2/SR6 and similar) over USB using the Web Serial API. It plays a scene's funscripts, including multi-axis ones, on the device in sync with the video. With a Bluetooth heart-rate monitor it can also shorten strokes as your heart rate climbs and hold off at the edge.
Maintained by dtt. Originally based on Stash-TCode by Spunkle, with modifications by Steel.
- Connects over USB via Web Serial at 115200 baud, with no websocket bridge, and reconnects on its own after a page refresh or when the device is plugged back in.
- Handles single-axis and multi-axis funscripts, plus separate per-axis files
(
.surge,.sway,.twist,.roll,.pitch). - Per-axis range, invert and home position, all saved across refreshes.
- Optional per-axis speed limit, as a percentage of full travel per second (300%/s is the same as 3 u/s in MultiFunPlayer). Moves that would be faster are shortened rather than slowed, so playback stays in sync.
- Auto motion: any axis the scene has no script for can follow a smooth sine wave at its own frequency, so a video without a funscript still gets motion.
- Linked axes: an axis can follow another, e.g. twist mirroring stroke: when stroke goes 0-100%, twist goes 0-100% of its own range, in step. Use the follower's Invert to reverse it. It applies when the axis has no script of its own, or always with Override script on.
- Settings live in a dialog built from Stash's own components, so it matches Stash and any custom theme.
- Heart-rate modulation, adaptive ceiling decay and climax markers (below).
- A stats overlay on the video: heart rate, ceiling, edges and what the limiter is doing.
- Handles seeking, including on transcoded streams.
Left, the Heart Rate tab. Right, the Axes tab with twist set to follow stroke.
The stats overlay on the video, two edges in: the ceiling has decayed from 120 to 118 BPM and strokes are easing off at 72%. The navbar button shows the device status and live heart rate.
- Use a Chromium-based browser (Chrome, Edge): Web Serial and Web Bluetooth only exist there.
- Reach Stash on a secure origin:
localhost, or HTTPS. If you reach it by a LAN address over plain HTTP, add that origin underchrome://flags/#unsafely-treat-insecure-origin-as-secureor serve Stash over HTTPS. - The Stash Interactive Tools plugin is optional. Without it, Stash-TCode plays each scene's main
funscript, single- or multi-axis, which Stash serves itself. You need it only for separate per-axis
files (
video.twist.funscript,video.roll.funscript, ...), which Stash doesn't serve on its own, and for picking between several scripts for one scene.
Stash-TCode is tested on Stash v0.31.
- In Stash, go to Settings → Plugins → Available Plugins → Add Source and add
https://dtt-git.github.io/stash-tcode/main/index.yml. - Tick Stash-TCode under that source and click Install. Refresh the page.
- Click Connect in the navbar, then pick your device's serial port. After that first time it reconnects by itself.
It works on whatever address you reach Stash at (port, reverse proxy or LAN name). When there's a new version, it shows under Settings → Plugins → Installed Plugins → Check for updates.
By default each browser keeps its own settings. To share them, turn on Advanced → Save settings to Stash: every browser on that Stash then uses the same settings, private windows included, and a change in one shows up in the others the next time they load. Turning it off in any browser turns it off for all of them, and each keeps the settings it had.
Which serial device and heart-rate monitor to reconnect to always stays with each browser, as the browser's permission to use them does. In a private window you pick the device again each time.
To install by hand instead, copy the plugins/stash-tcode folder into your
Stash plugins directory, then Reload plugins in Stash and refresh.
Stash-TCode used to be a Tampermonkey userscript. Version 0.7.0 is its last release: it keeps working, but gets no more updates.
- Let Tampermonkey update the userscript to 0.7.0 (it does this itself; a pop-up says the userscript is retired). Don't install the plugin alongside an older userscript: both would drive the device.
- Install the plugin as above and refresh. The userscript sees the plugin, copies your settings over and stops running.
- Refresh once more to load your settings into the plugin, then delete the userscript from Tampermonkey.
Your device and heart-rate monitor stay paired: the browser remembers them for Stash's address, not for Tampermonkey.
Connect a monitor under Device → Heart rate monitor. Any monitor with the standard Bluetooth heart-rate service works: chest straps (Polar, Coospo, Garmin, Wahoo, ...) or a watch broadcasting its heart rate. Then turn it on under Heart Rate.
- Edge is the heart rate that counts as an edge, and it's your ceiling. In the approach band below it (default 10 BPM), strokes shorten linearly down to the shortest stroke (default 30%). The stroke shortens from the bottom of its range; other axes shrink toward their home position.
- At the ceiling the stroker parks at the bottom of its range and sits out the script while the video keeps playing. Once your heart rate drops 5 BPM below the ceiling, strokes ramp back in over the rejoin time (default 8 s).
- Adaptive ceiling decay lowers the ceiling 2 BPM every 2 edges, down to a floor (105 BPM by default), to counter fatigue over a long session. It never raises the ceiling above what you typed.
- Signal timeout: if a connected monitor sends no real reading for 8 s, the device stops. That includes a strap that's been taken off but keeps repeating its last BPM: packets flagged "no skin contact", or a strap that was timing beats and stopped, don't count as readings. With no monitor connected at all, scripts play normally.
- Session shows the live state, and Reset clears the edge count and decay. The session survives a page reload but ends when you close the tab.
Chrome only reconnects a Bluetooth monitor after a refresh if
chrome://flags/#enable-web-bluetooth-new-permissions-backend is on. Without it, the Device tab
shows a one-click Reconnect.
Add a Stash scene marker tagged Climax (the tag name is configurable on the Climax tab), or use Mark climax here on that tab while the video is at the right spot. Around a climax marker the limiter lets go, whatever your heart rate: strokes build back to full over 45 s before the marker, stay full for 20 s (or for the marker's whole range, if it has an end time), then hand control back over 15 s. All three times are adjustable.
The plugin is plugins/stash-tcode/stash-tcode.js, loaded by
stash-tcode.yml next to it. stash-tcode.user.js at the repo
root is the retired userscript, kept at 0.7.0 so existing Tampermonkey installs still find it; don't
change it.
To try your changes in a local Stash, copy the plugin into your plugins folder, then Reload plugins in Stash and refresh:
STASH_PLUGINS_DIR=/path/to/stash/plugins dev/deploy-plugin.shSTASH_PLUGINS_DIR can also go in a .env file at the repo root. Run it again after each edit.
Turn on Advanced → Debug mode to get:
- Simulated device: stands in for the hardware. Commands are recorded but nothing moves.
- Simulate heart rate: a steady reading once a second in place of a monitor.
window.__tcodein the page console:commands()(axis commands sent, parsed),tx(),clearTx(),state(),hr(),hrPackets()(raw monitor packets),setHr(bpm)/setHr(null),governor(),resetSession()andsimulateDevice().
The heart-rate governor's math (approach band, edge hysteresis, decay, rejoin ramp, climax window,
watchdog and axis shaping), the auto-motion sine and the speed limit, plus heart-rate packet decoding and
serial port selection, are covered by plain Node tests. version.test.js checks the manifest loads the
script and the version matches in both places.
node tests/governor.test.js
node tests/hr-packets.test.js
node tests/serial.test.js
node tests/linked.test.js
node tests/version.test.js
Bump the version in both places, TCODE_VERSION near the top of stash-tcode.js and version: in
stash-tcode.yml, then push to main. The Deploy workflow runs the tests and publishes the plugin
source index to GitHub Pages (https://dtt-git.github.io/stash-tcode/main/index.yml), where Stash finds
the update.
- dtt: current maintainer.
- Spunkle: wrote the original Stash-TCode userscript.
- Steel: made the modded version this project builds on.
- Heart-rate modulation is inspired by EdgeLoop by marshallmims (AGPL-3.0) and reimplemented independently here.
Licensed under the GNU GPL v3, the same as the original.



