Multi-Element Stroker / AI / Funscript / TCode

I am working on a prototype device that has multiple independent linear stroke elements, of different (fixed) lengths. The relative order of the elements is fixed. Each element could potentially be independently twisted. There will also be other features of the device that depend on the type of contact taking place: hand, mouth etc.

FunGen (AI, see forum post here on EroScripts) can be used to generate the detailed data needed by this device according to one of its programmers.

Unfortunately, these features aren’t handled by the current Funscript or TCode specification and I want to propose changes to enable them while maintaining backwards compatibility.

Hoping for input from other builders and TCode player programmers.

*** Funscripts

Funscripts currently describe what a toy should do. I would like to tweak them to describe what the contact surfaces currently are (as seen by the AI model).

The filename format would become: vidname[.contacttype].[motiontype].funscript

contacttype = h, m, or v, defaults to v
motiontype = pitch, twist etc, defaults to linear ‘forwards’ motion

Some combinations of contacttype and motiontype are nonsensical, obviously.

Within the funscript file, we currently have entries like {“pos”:41,“at”:1567445}. For files with contact type ‘h’ or ‘m’ (hand, mouth), I propose to add an extra attribute “len”, which, using the same units as “pos” would describe the length of the contact taking place, not just its starting point. Length would default to the range maximum when not specified.

*** TCode

TCode currently also only has support for one element on each channel/axis pair and no contact length.

I propose adding extra channels M,N,O,P,Q for linear motion and S,T,U,V,W for rotational motion. A channel index might be better for future expansion, but would make the current command format cumbersome and 6 channels for each motion type seems like enough for the foreseeable future.

Also needed would be a format for device capabilities to be queried, namely: a set of stroke elements with each detailing:
- its length, defaulting to full-length
- an order number if fixed, smaller numbers being closest to user
- available rotational angle

*** TCode Players

The player software would on startup query the device for it’s capabilities. The player would then deploy the device stroker elements to best mimic the contact surfaces described by the funscript files.

This leaves space for some “artistic interpretation” by the player. For instance, for a device with elements of length 3, 1 and 5, if the funscript describes a base hand-hold (hand contact) and blowjob (long mouth contact), the player would use the first element for the hand, the second for the lips, leave a space, then the final element to simulate the throat.

Edit: some refs: @Tempest @Yoooi

1 Like

No
Use L3~L9 for linear, R3~R9 for rotations, and A3~A9 for whatever you want
Also there are custom commands which start with # you can #L123:12345 if you want

TCode is a simple protocol made for sending actions to motors basically
It’s better to process script in player and then just send whatever commands servos shoud execute
nvm thats the same thing you said

I recommend going into single-file multi-axis where you can have any amount of

interface FunscriptJson {
  actions: Array<{at: number, pos: number}> // single-axis fallback for Handy
  axes: Array<{
    id: string, // L0 for multi-axis stroke, L1~L2 R0~R2 A0~A2 for the rest, may be any string
    actions: Array<{at: number, pos: number}>
  }>
}

Buuut yes, I’d very much prefer if TCode would allow to do stupid simple things as sending R axes in degrees (okay maybe not R0 to not limit it to ±99 degreed)
and have simple curve motions
and be able to send command to queue into the future

Almost all of such information is metadata and configuration interaction and not the script itself.

Metadata could say that 0-100 in the roll axis refers to a degree range of 90 (-45 to 45). Its metadata since even without it, the script will behave fine. But it does describe the context in which it was made. This is backward compatible in funscripts in the way it is (funscripts have a metadata block).

The configuration then can make this -45 and +45 to be angles you can use on the device. Maybe it turns out that its limited to -45 and +30. Or you actualy want to map them more excessive as your sleeve is very stretchy so -60 to +60 works.
It also allows you to set a default for when metadata lacks (which is very likely).

This configuration system then could just be a plugin for multifunplayer, or maybe even scriptplayer, or whatever tool you use. Each tool might have its own limitations, but it doesnt matter.
Most important: it doesnt matter as the scripts arent adjusted towards these specific tools. they only provided some metadata.

For axis names i do always prefer fully named styled tags better. L3,L4,L5 etc are inclear. Its just whatever the scriptmaker configured: linear2, roll2, pitch2 are far more configuration flexible. and negate the part of the scriptmakers own idea. Its far less likely that if we have a triple linear axis system that they will conflict with a triple multiaxis system. In full names they would be: linear1, linear2, linear3, and linear1, roll1, ptich1, linear2, roll2, etc. Without those names, you can bet that the triple linear and triple multiaxis will conflict on L4 (linear3 or roll2).

And them being seperate files again does allow people to remap scripts in videos. Or substitute some by just renaming/copying. Imagine having a dual multiaxis system, but someone made a double linear and someone a fully multi axis system. By copying the multi axis parts of 1 to 2, they now can both be multiaxis.

L0 etc then can just be part of the T-Code for whatever device, and give you the freedom to map them. V0 and V1 already conflict if you have 2 vibrators, but this doesnt matter if scripts are called vib1 and vib2. Sure, playback might say its V0 and V2. but it doesnt matter, the scriptnames map it for you by only forcing you to configure that once.

You’re creating new axis there. There are only 3 axis physically possible.

The whole point of a central standard is so that things stay compatible, custom commands for particular devices don’t achieve that.

I agree, but wanted to keep things clear for potential human editors and match current practices. I had a look at some twist funscripts and they all had “twist” in the title and no relevant metadata.

I also agree on proper naming but again my proposal was on a “working with what we’ve current got” basis.

Say hi to contraption with two L0 axes


But when they change everyone who depends on them has to update
Buttplug still doesn’t support multiaxis


All that’s currently working is {actions, metadata: {bookmarks, chapters}} of funscripts named file.funscript and file.${axisName}.funscript because that’s what always was there
and funscript with {axes: [{id: "L1", actions: [...]}]} because SLR used it and MFP added support for that
Nothing else will work in upcoming two years
metadata.duration lies so often noone uses it
inverted and range ore not supported


So it all comes to one thing

  • I don’t even understand what you are trying to make paint me a picture in paint

There’s only one axis (0) with 2 “sliders” (L & M, as I propose to call them). That’s exactly what I’m trying to capture here. Though fixed order, not overlapping.

Not if the changes are backwards compatible. No one has to use the extra channels.

Could you tell me where that’s described ? Sounds interesting. Sorry, I’m relatively new at this :slight_smile:

It isn’t ¯\_(ツ)_/¯
I wrote my TS types myself
What language do you use btw


export declare const B: unique symbol
export interface B<Brand> {
  [B]?: Brand
}

export type axisPairs = [['L0', 'stroke'], ['L1', 'surge'], ['L2', 'sway'], ['R0', 'twist'], ['R1', 'roll'], ['R2', 'pitch']]

declare global {
    type mantissa = number & B<[any, 'mantissa']>

    // time variants
    type ms = number & B<['time', 'ms']>
    type seconds = number & B<['time', 's']>
    type timeSpan = string & B<['time', 'TimeSpan']> // 00:00:00.000

    type speed = number & B<['speed', 'u/s']>

    // axis value variants
    type axisValue = number & B<['axis', 'u']> // 0-100
    type axisNorm = number & B<['axis', 'mantissa']> // 0-1
    type axisAngle = number & B<['axis', 'deg']> // in degrees
    type axisRaw = number & B<['axis', 'deg' | 'mantissa']>

    // axes
    type axis = `${'L' | 'R'}${0 | 1 | 2}` & B<['axis', 'id']>
    type axisName = 'stroke' | 'surge' | 'sway' | 'twist' | 'roll' | 'pitch'
    type axisLike = axis | axisName
    type AxisToName = { [K in axisPairs[number] as K[0]]: K[1] } & { [K in axisPairs[number] as K[1]]: K[0] }

    type chapterName = string & B<['chapter', 'name']>

    interface JsonAction {
      at: ms
      pos: axisValue
    }
    interface JsonChapter {
      name: chapterName
      startTime: timeSpan
      endTime: timeSpan
    }
    interface JsonMetadata {
      bookmarks?: { name: string, time: timeSpan }[]
      chapters?: JsonChapter[]
      duration?: seconds
      // creator?: string
      // description?: string
      // license?: string
      // notes?: string
      // performers?: []
      // script_url?: string
      // tags?: []
      title?: string
      // type?: string
      // video_url?: string
    }
    interface JsonFunscript {
      actions: JsonAction[]
      // ids are L0..R2
      axes?: { id: axis, actions: JsonAction[] }[]
      metadata?: JsonMetadata
      // inverted?: boolean
      // range?: number
      // version?: string
    }
}

Ah, OK. So SLR are already generating multi-axis scripts with their AI and including everything in one file it looks like ? And using the TCode terminology for the axes naming.

Then for multiple strokers on the same axis we could just add another set of actions to the funscript file with the same axis ID (L0), right ? And add meta data to that set to describe the contact type.

Doesn’t solve the TCode problem though unfortunately.

Edit: no, that’s not going to work sadly because players might mix the two sets together.

What’s “the TCode problem” exactly?

You can have up to 10 linear, 10 rotation, 10 vibration and and extra 10 auxiliary axes. 40 in total. Even SR6 only uses 6 of them. 7 if there’s a valve.

The whole point of TCode is it’s very simple.

It’s also just a communications protocol, so if you’re wanting to script that’s outside the scope of TCode.

You’re talking about something that sounds like a very a complex device, so it sounds like you might just want to create a bespoke communication protocol.

There will never be any players
Only one that tou make yourself. Noone else is going to support it.
OFS is archived since 2023
MFP maaybe can support it with plugins. Don’t expect builtin support until 100s of users ask for it.
Anything else doesn’t support multi-axis

Also you are constantly confusing TCode (which is a serial protocol used to control servos) which user doesn’t have access to, funscript (which is files players use to calculate curves), and applications (which read second and translate it to first)

tl;dr until you have a barely working but working something, or at least a fine blueprint of it, we can’t really say that we understand you and your suggestions (I personally don’t at least draw me some images)

Yea I’m a little bit confused. It all sounds like over complication of stuff that can already be done.
You can add [A-Z] axes in MFP.
You can have custom filename format for each axis in MFP, just make it .vpitch, .mpitch, .mtwist etc.
You can output to multiple devices at the same time in MFP.
There is not need for “len” in funscripts, just script the actual behavior you want.

You would need a better explanation with pictures of what you are trying to do.

1 Like

@Tempest by the way, could you order someone to make a proper Funscript file definition version 1.0 (minimal stuff everybody supports already anyways) just so different programs stop implementing it differently?
And drop a JSON Schema to MultiAxis repo

I know at least 4 different flavours of funscripts - because they have different metadata.duration format (ms, s, timesspan, 0)

Don’t know why you are asking Tempest to do that. Original funscript is from funjack:

The format depends on the scripter apps. Then only consistent part supported by scripters and players is the actions array.

1 Like

First of all, thanks all for your input, I know a lot more than I did yesterday :slight_smile:

I’ll try to explain the situation again. Here’s a snapshot of the device handling a blowjob:

-SSS—S–SSSSS

S = sleeve. So we have 3 receivers, as Tempest calls them, in a line, going left-right each with sleeves of different lengths. The AI is saying that there’s a hand contact near the base (length 3, small hand) and a mouth contact halfway up (full length). The device has arranged it’s receivers to try and simulate that.

That’s the goal.

I don’t know if I can get 3 receivers working, if the motors can be sufficiently synchronized for that, but I’m quite confident about 2.

@Yoooi, have to say first your player is a very fine piece of software, excellent attention to detail.

I hadn’t noticed the device customization tab, awesome! So with the name matching I can map several funscript files to a single linear axis and indicate with the names the type of physical contact taking place.

I hope you agree though from my picture above, that length data needs to be processed either in the player or in the device so that the receivers can be positioned appropriately.

Edit: in case it’s still not clear, let’s say there’s just a handjob going on. Then the middle receiver can be joined with the first receiver to make a ‘hand’ of the appropriate length if necessary.

I’m happy to do the programming but MFP would need to process and pass the data somehow, if you don’t want to deal with it in MFP.

@Tempest I’m a bit confused by your terminology sometimes.

I had expected that since we live in a world with 3 dimensions, only 3 axes make sense. When you say ‘SR6 uses 6 axes’ what I think you mean is that it uses 3 axes, and 2 channels on each. But it doesn’t sound like that’s what you mean somehow


But nevermind: I realise now with MFP I can make my own connections from funscript file to custom channel-axis pair.

I was just trying to create something more ‘official’.

me lazy to explain heres what I got form t3.chat

ai

The term “6 axes” usually refers to the degrees of freedom a robot or other mechanical system has in movement. These axes represent the different ways the system can move in 3D space:

  1. X-axis (Linear): Movement along the horizontal axis (left/right).
  2. Y-axis (Linear): Movement along the vertical axis (up/down).
  3. Z-axis (Linear): Movement along the depth axis (forward/backward).
  4. Roll (Rotational): Rotation around the X-axis.
  5. Pitch (Rotational): Rotation around the Y-axis.
  6. Yaw (Rotational): Rotation around the Z-axis.

So, a 6-axis robot can move its end effector (the part that does the work) to any position and orientation within its workspace. This makes it very versatile for tasks like welding, painting, assembly, and pick-and-place operations.

You have [L0,L1,L2] for one sleeve, [L3,L4,L5] for second sleeve and [L6,L7,L8] for the third sleeve
robot dumb robot just moves them to L0, L3, L6 you gave it