Deep dive into multi-axis T-code haptic scripting. Learn about the 8 axes (L0-L2, R0-R2, V0, A0), T-code format syntax, advanced devices, and how HaptiQ generates multi-axis output.
Standard funscripts control a single axis of motion: up and down. But advanced haptic devices like the OSR2+, SR6, and SSR1 support up to 8 independent axes of motion, delivering rotation, twist, surge, sway, and vibration alongside the primary stroke. Controlling these axes requires the T-code protocol, a serial command format that sends real-time position updates to multi-axis firmware. This guide covers everything you need to know about multi-axis T-code scripting, from protocol syntax to practical creation workflows with HaptiQ.
T-code (TCode, T-Code Protocol) is a serial communication protocol designed for controlling multi-axis haptic devices. It was created by the open-source hardware community alongside devices like the OSR2 (Open Source Robotic Stroker) to provide a standardized way to send position commands to firmware that controls multiple servos or motors simultaneously.
Unlike funscript, which is a file format stored as JSON, T-code is a real-time command protocol. Commands are sent over a serial connection (USB or Bluetooth Serial) as plain-text strings. Each command specifies an axis identifier and a position value that the firmware immediately executes.
The relationship between funscript and T-code is complementary: funscript defines what the device should do over time (the script), and T-code is the real-time protocol that delivers those commands to the device's firmware at the correct moments during playback.
The T-code specification defines 8 standard axes, organized into four groups. Each axis controls a different dimension of device motion:
| Axis | Name | Motion Type | Description |
|---|---|---|---|
| L0 | Stroke | Linear | Primary up/down stroke (equivalent to standard funscript) |
| L1 | Surge | Linear | Forward/backward push motion |
| L2 | Sway | Linear | Left/right lateral motion |
| R0 | Twist | Rotational | Clockwise/counterclockwise twist around the vertical axis |
| R1 | Roll | Rotational | Roll rotation (tilt side to side) |
| R2 | Pitch | Rotational | Pitch rotation (tilt forward/backward) |
| V0 | Vibrate | Vibration | Primary vibration intensity |
| A0 | Valve | Auxiliary | Air valve or suction control |
The three linear axes represent translational motion in three dimensions. L0 is the primary stroke (what standard funscripts control), L1 adds a forward/backward push that creates a thrusting sensation, and L2 adds lateral sway that mimics side-to-side movement. Together, these three axes can reproduce complex spatial movement patterns that single-axis devices cannot approximate.
The three rotational axes add twist and tilt to the device's motion. R0 (twist) is the most commonly used rotation axis, creating a wringing or twisting sensation. R1 (roll) and R2 (pitch) tilt the device along different planes, adding dimensional complexity. On devices like the SR6, which has 6 servo-driven axes, combining rotation with linear motion creates remarkably lifelike movement.
V0 controls vibration intensity from 0 (off) to 100 (maximum). This adds a continuous stimulation layer on top of the mechanical motion. A0 controls auxiliary features like air valves or suction mechanisms, which some advanced devices use for additional sensation types.
T-code commands are plain-text strings sent over serial connections. The basic syntax is:
# Basic format: AxisPositionSpeed
L09999 # L0 axis to position 9999 (99.99%) at maximum speed
L05000I1000 # L0 axis to position 5000 (50%) over 1000ms
R02500 # R0 (twist) to position 2500 (25%)
# Multiple axes in one command (space-separated):
L09000 R05000 V03000
# L0 to 90%, R0 to 50%, V0 to 30% simultaneously
# Device info query:
D0 # Request device identifier
D1 # Request TCode version
D2 # Request axis count
T-code position values range from 0 to 9999, providing much finer granularity than funscript's 0-100 range. The value 0000 represents the minimum position, 5000 is the midpoint, and 9999 is the maximum. When converting from funscript to T-code, the position is scaled: funscript pos 50 becomes T-code position 5000, funscript pos 100 becomes 9999.
The optional I suffix specifies the time interval (in milliseconds) over which the axis should reach the target position. Without the interval, the device moves to the position as fast as its servos allow. With an interval, the firmware interpolates smoothly, producing gentler transitions.
Multi-axis T-code is primarily used with DIY and open-source hardware devices. These are enthusiast-grade machines that provide a level of haptic fidelity far beyond consumer devices:
| Device | Axes | Type | Notes |
|---|---|---|---|
| OSR2+ / OSR2x2 | 2-3 | DIY servo-driven | Most popular T-code device, open-source design |
| SR6 | 6 | DIY servo-driven | Full 6-axis Stewart platform, highest fidelity |
| SSR1 | 6 | DIY servo-driven | Alternative 6-axis design |
| Romeo1 | 6+ | Commercial | Commercial multi-axis with T-code firmware |
| Custom builds | 1-8 | DIY | Any Arduino/ESP32 device running TCode firmware |
While T-code is the real-time protocol for commanding devices, multi-axis content is typically stored as multiple funscript files, one per axis. The naming convention uses the axis identifier as a suffix:
my-video.funscript # L0 (primary stroke) - default
my-video.L1.funscript # L1 (surge/thrust)
my-video.L2.funscript # L2 (sway)
my-video.R0.funscript # R0 (twist)
my-video.R1.funscript # R1 (roll)
my-video.R2.funscript # R2 (pitch)
my-video.V0.funscript # V0 (vibration)
my-video.A0.funscript # A0 (valve/suction)
Each axis file is a standard funscript JSON with an actions array. The only difference is which axis the positions control. Players that support multi-axis playback (like HaptiQ, MultiFunPlayer, and Heresphere) load all matching axis files and convert the timed positions into T-code commands during playback.
HaptiQ generates multi-axis T-code output by analyzing different motion components from the optical flow data during its 5-stage AI pipeline. Here is how each axis is derived:
The L0 axis is generated directly from the dominant vertical motion vectors in the region of interest. This is the same process used for standard single-axis funscript generation, and it produces the primary up/down stroke pattern.
L1 (surge) is derived from the depth component of the optical flow, representing forward/backward motion. L2 (sway) comes from the horizontal component. HaptiQ decomposes the 2D optical flow vectors into directional components and maps each to the appropriate axis with proper scaling.
Rotation axes can be derived algorithmically from the primary motion pattern. R0 (twist) is commonly generated as a phase-shifted derivative of L0 -- when the stroke changes direction, a slight twist adds realism. R1 and R2 can be derived from the lateral and depth motion components respectively, scaled down to create subtle complementary movement.
V0 vibration intensity is typically derived from the overall motion magnitude -- faster motion produces stronger vibration. A0 can be linked to specific motion patterns or left for manual configuration in HaptiQ's multi-axis editor.
HaptiQ's Pro tier includes a multi-lane timeline editor that displays all 8 axes simultaneously. Each axis gets its own lane with independent zoom, selection, and editing controls. You can:
If you already have a single-axis funscript (either hand-made or AI-generated), HaptiQ can derive secondary axes algorithmically. This process analyzes the L0 motion pattern and generates complementary motion on the other axes:
| Aspect | Funscript (.funscript) | T-Code Protocol |
|---|---|---|
| Purpose | Storage and distribution | Real-time device control |
| Format | JSON file | Serial text commands |
| Axes | Single-axis per file | 8 axes in one command stream |
| Position range | 0-100 | 0-9999 |
| Timing | Embedded timestamps (at) | Sent at correct moment by player |
| Device support | All haptic devices | OSR2+, SR6, SSR1, custom builds |
In practice, you create and store scripts as funscript files and the playback software converts them to T-code commands in real-time during playback. HaptiQ handles this conversion automatically when a multi-axis device is connected.
HaptiQ Pro includes full 8-axis T-code generation, multi-lane editing, and export in funscript, T-code, and CSV formats.
T-code defines 8 independent axes for multi-axis haptic devices. L0 is the primary linear axis (up/down stroke), L1 is surge (forward/backward translation), L2 is sway (side-to-side translation). R0 is twist (rotation around the stroke axis), R1 is roll, R2 is pitch. V0 is vibration intensity, and A0 is auxiliary air or suction. Devices like the OSR2+ typically implement L0, R0, R1, and R2. The SR6 adds L1 and L2 for full 6-degrees-of-freedom motion. Higher-end rigs support all 8. Each axis accepts a 0–9999 position value at millisecond timestamps, sent over serial at up to 100Hz update rate. HaptiQ generates multi-axis output by mapping different optical-flow components to different axes — vertical motion to L0, horizontal rotation cues to R0 and R1.
Standard funscript is JSON: an actions array of {at: milliseconds, pos: 0-100} entries, single axis. T-code is a serial command stream sent in real time: each line is a command like L050I100 meaning "move axis L to position 50 over 100 milliseconds" (I for interval), or L050S500 where S is speed. Commands for multiple axes chain on one line (L050I100R075I100V500I50) for coordinated multi-axis moves. HaptiQ can export in either format: standard .funscript for single-axis devices (872 supported), or T-code for the OSR2+, SR6, and SSR1. Multi-axis .funscript extensions (like .funscript plus .roll.funscript and .twist.funscript sidecars) are also supported for backwards compatibility with older multi-axis workflows.
The OSR2+ is the most common entry-level multi-axis device, supporting L0, R0, R1, and R2 over USB-serial. The SR6 adds L1 and L2 surge/sway for a full 6-DOF experience. SSR1 supports similar axis layouts with premium actuators. TempestMAX and TempestMAX Prime are high-end options with all 8 axes. All multi-axis devices run custom firmware based on the T-code reference implementation and accept commands at 50–100Hz. HaptiQ connects via WebSerial in Chrome/Edge or via Buttplug.io with a T-code-aware driver. Secondary axes can be derived algorithmically in HaptiQ: roll (R2) is often a 90-degree-phase-shifted copy of primary stroke motion, and vibration (V0) can be generated from stroke speed magnitude. Pro tier unlocks multi-axis export; free tier includes single-axis output.
Open a project in HaptiQ and load your video. If you want AI-generated multi-axis output, enable "Multi-axis T-code" in the project settings before running generation — the pipeline will analyze horizontal as well as vertical optical-flow components and populate up to all 8 axes. For manual authoring, open the multi-axis editor (EditorPage with multi-axis mode on), which presents separate timeline lanes per axis with synchronized scrubbing. You can derive secondary axes from the primary: select L0, pick "Derive R0" from the axis menu, and HaptiQ generates a rotation track algorithmically. Export options include combined T-code (.tcode), multi-file .funscript sidecars (one per axis), and CSV for analysis. The full workflow is multi-axis-native: 8 lanes, independent keyframes, T-code syntax highlighting.
Create a free HaptiQ account to generate a funscript from your own video.