[RFC] Single-file multi-axis

We now have single-file multi-axis option builtin directly in the site


For scripters

  • Just upload the .sway .twist etc files same as before, no need to merge them
  • Avoid having two scripts with exactly same name (e.g. example.funscript in one section and example.funscript + example.twist.funscript in other section) - the merger will get confused
    • I may implement a fix to allow multiple files with same name, it’s up for discussion
  • If you have two exactly same scripts for different axes, add a space to the file so they are uploaded separately
  • Please don’t upload heatmap images, as they are now auto-generated
  • Handy scripts (550 max speed, 30ms min between actions) will show axis
  • If your script is above 800 you definitely scripted something wrong. The displayed max speed is speed device needs to hit all the peak points, intermediate speed may be higher then that

Player support

  • MultiFunPlayer:
    • supports single-file multi-axis scripts (in both free and Patreon versions)
    • supports .max extension prefix via configurable extra default script axes
  • XTPlayer - not supported
  • OpenFunScripter - not supported

File format:

{
  "actions": [ { "at": 0, "pos": 50 } ],
  "axes": [
    { "id": "R0", "actions": [ { "at": 0, "pos": 50 } ] }
  ]
}

where “id” is one of TCode axes excluding L0

Implementation details:

  • Not having "actions" in root object is undefined behavior (it may e.g. crash the player)
    • Root "actions" may be empty [] it’s fine
  • "L0" axis is safe undefined behavior
    • it may be ignored, used instead of root, or whatever
  • Unknown axes, e.g. "F7" should be safely ignored (it they are not valid axis objects it’s UB)
  • Extra properties in axes SHOULD be ignored
    • This means { ...L0Script, "axes": [ { ...R0Script, "id": "R0" } ] } is a valid multi-axis script
    • range and inverted are not supported by anyone afaik so please ignore them
  • The root object may have "id": "L0" or whatever feel free to ignore it.
    • If it doesn’t match extension prefix (e.g. .twist file having "id": "R0") it’s UB
  • Decimal metadata.duration MUST NOT crash the player (it may be rounded)
  • Decimal at / pos better not crash the player (they may be rounded)
    • Negative at better not crash the player (they may be ignored)
    • Duplicate at better not crash the player (they may be ignored)
    • Out-of-0-100-bounds pos better not crash the player (they may be ignored or trimmed)
  • If there is a video example.max.mp4 it SHIULD be matched with example.max.funscript
    • So .max extension prefix should be safely ignored
    • .max prefix may be threated as a primary script, but that’s up for discussion

This format was discovered by me in MFP code (which, it turn, was added for SLR compat) so I mostly describe how it already works


Unroll plan

  • Multi-axis merging
  • Multi-axis script shows download count
  • Downloading multi-axis script bumps all download counts
  • Optional .max extension when downloading
  • Dropdown with single-axis scripts
  • Set option to merge multi-axis ON by default
    • Set.max extension OFF by default?

Future development

  • allow axis names for axes as TCode axes are less humanly readable
  • extend device support for vibes etc
  • maybe allow “variants” of the same axis (classic “with empty” vs “with filler”)
  • Inspire by Buttplug.io
6 Likes

cc @Yoooi @Khrull @Tempest @moderators

This is Request For Comments, if you have comments on how we should implement this please discuss

I want to point out that this format is in MFP because this is SLR format for multi-axis scripts and since MFP supports SLR script streaming it had to be added.
So I’m not behind this format or pushing for it to be some standard.

Back to the topic, if this format was to be popularized then IMO merge option needs to be on by default, with .max option off by default. The .max option requires either renaming of video files or configuration changes in script players so that they handle .max funscripts. This is a different workflow than normal .funscript and imo will cause some confusion.

Also I think .max could be renamed to something more descriptive, what that is exactly I don’t know but I suggested .multi. For me .max means maximum.

6 Likes

How does this affect usability of scripts (the L0 axis of it) with other players, like ScriptPlayer or even just handyfeeling.com?

Also, does it negatively impact the compatibility with The Handy in general (due to script file size limitation uploading all the other axis the Handy can’t handle will bloat it)?

It increases file size 5x but that’s it
Should be totally compatible

I have no idea if Handy actually trims scripts before sending them to device, can you please test it?

massive.funscript (1.6 MB)
little.funscript (4.6 KB)

If those two scripts not upload with 10x difference in time then the handy ignores stuff it doesn’t use and it’s safe to make Handy users dl multi-axis scripts

1 Like

Both handyfeeling.com and ScriptPlayer download both scripts in about 1 second each, so it seems to be filtered before upload.
Good to know it’ll be backward compatible that way :+1:

I do not think we should be interweaving the TCode standard into the Funscript standard. They are separate standards and should stay separate.

R0, R1, L0, L1 etc are TCode channels. They mean nothing to to funscripts. There’s not much reason to merge the two.

Reasons to NOT incorporate TCode into fuinscript:

  • TCode Obsolesence. What if TCode is replaced by something else more effecient down the line. Now funscript is left with these confusing values that mean nothing to almost anyone.
  • Readability, We are using json format and a big part of that is its flexibility. Almost every time I need to know what R1 means I have to stop and go find mapped definition to see which channel it belongs to. I understand its value in TCode where data transmission should be lean as possible but funscript, its not such a big deal.
  • Content communication. I noticed that the actual channel name is printed out at the user end to afford the user what axist is mapped to what channel. This further proves its a bad idea to use TCode channels.
  • Not all devices that use funscript are TCode devices. Just because multi axis devices are Mostly TCode now, doesnt mean this will be the case in the future. Is funscript going to what ever standard those devices too?

Reasons to merge the two:

  • File size. The TCode name will decrease the files size by a few bytes.

If we want this new mfs format to be as lean as possible then shorter names could be better. At the end of the day this is an end user format. Most users will have no clue what these machine names mean. Especially NON TCode users. The data amount added for human readable names as channels would be negligible.

When at the for front of any standard, one has the ability to shape the future applications that utilize this format. For the end user sanity, its best to keep this format as simple as possible so the UI of these applications are understandable and usable by most people without any mapping knowledge required on the developers part.
Yes, the only applications that will be using this format at first will be the current TCode applications like XTP and MFP. This further proves my point. Both of these applications exist for a reason. TCode is NOT funscript and needs to be translated. We can handle the mapping as thats the whole point of our application.

Lets not be lazy and merge the two out of convenience. This is Funscript and should be its own thing.

Alternative:
We already have the users working with nautical axis names along with other module names like vib and lube.
The community have gotten to know these values already I feel and would be the least resistance.
stroke, sway, surge, roll, pitch, twist, vib, lube etc.

For backwards compatibility I think stroke should be implied. If there’s no actions on the root of the document then there is no stroke actions. No reason that I can see to add complexity checking with two paths for stroke in a sub object OR root of the document. A big downside to having an optional stroke sub object is ALL current users/applications wont see it and now we have scripts that wont work on all but the applications that support it.. Leave the optional stoke as it is for compatibility. We can extend this standard, but we shouldn’t be modifying it.

4 Likes

I agree with the max naming. No clue what that even means as is.
I see it as all strokes are maxed out. I guess this isnt the case.

It means multi funscript? odd name for that…

If we do want to create a NEW standard, I’m a fan of .mfs short for multifunscript. If we change the funscript standard in this format it stands to reason to change the name of the extension too.
If we want to keep backwards compatibility then multi.funscript sounds good too.

3 Likes

SFMA is TCode multi-axis funscript format

Counterarguments:

  • SFMA is “existing” standard (as MFP supports it for 2 years already?)
  • SFMA is made intentionally for TCode devices. Vibrator support is not planned (yet)
  • It’s json, noone stops user from adding "_axis": "twist" or something. Future versions may allow "id": "twist" or "axis": "twist"
  • We are making a format for “now”, not for in five years.
  • I think we should discuss this again after buttplug.io has multi-axis TCode support lol, and partially reference buttplug.io protocol when implementing future version

Just because TCode is a multi axis format doesnt mean ALL multi axis content will be TCode. I believe funscript should be standard agnostic just like TCode should be device agnostic.

As Yoooi said, this exists in MFP because of SLR. Just because it exists doesn’t mean it makes sense logically.

During the creation of this standard, leave out the ORs as much as possible ( "id": "twist" or "axis": "twist") it only complicates the development documentation and support queries. Pick one a stick with it. “id” is objectively better as its more compact and logically better because “axis” is too definite.

Standards ARE for the future. Only thinking about now is a good way to mess things up and cause issues down the line. This should be avoided for the sake of the people of the community.

As proof of that, because SLR was NOT thinking about this now we are having this discussion and rather to stick with them or defer.

Haste makes waste.

4 Likes

If we standardize the axisses, the first thing to resolve is the name/code conflict. L1 might mean multiple things. And the default in MFP is suboptimal as its not future proof.

The problem is that L0 is the first linear axis, which makes sense, but on that L1 can also mean the 2nd linear axis on a machine which has 2 linear strokers. Making it move forward/backward or left/right conflicts here. Especialy when we would combine the 2: 2 strokers on a device, both with 6 axisses to work with will be a mess.

So on that, the standard should be far more clear on which axis certain movement has to take place. L0/L1/L2/L3 etc are therefor bad and should be discouraged in all cases. Even if we would change it, backward compatibility will cause problems.
A very clear example where it already breaks currently is the lovense edge2. In the standard coding V1 is already used for a valve. So you end up with V0 and V2. Its just ugly and a disaster to manage.

So i would propose forcing the axis id to be an axis name, or having better codes, yet its not as simple as:
linear0, linear1, pitch1, roll1, surge0, sway0, twist2.
Where the number is the device it targets.
It might sound nice, but it has a problem when there is vibrators within those sleeves, combined with external ones. It does work for separated files though as you can rename them. The problem here is that if you would define vib1: which vibrator to trigger? If its defined on device 0 (for example an OSR2), this doesnt have a vibrator, which now gives the question: which device to target instead?

As we dont want to alter the funscripts to contain configs, some information must be stored somewhere, and be dynamic for the device. And sadly, the most reliable way is filenames and adjustable mappings.
And let the current standard already feature both.

The most simple fix to me would be: force funscripts into a container file, where the player can edit the zip filenames, but not the file contents. And for this we can look at how microsoft already handles their office files: they are zip files.

The advantage here is that you can give a file ANY name in it, and have 1 file to map it towards an axis. So the funscript could be named ABC123.funscript, the mapping.json just says what the intention is.

Lets for example view what an OSR targeted mapping can be:

File       | Device | Axis
hexcode_L0 | 0      | L0
hexcode_L1 | 0      | X0
hexcode_L2 | 0      | Y0

Or if we used an other device (2 linear strokers, where 1 of the stokers has 2 vibrators in it)

File       | Device | Axis
hexcode_L0 | 0      | L0
hexcode_L1 | 1      | L0
hexcode_V0 | 0      | V0
hexcode_V1 | 0      | V1

Note that hexcode here can basicly be any filename that a zip does allow, and can easily be human readable. This should make it easier for backward compatible merging.

The advantage here is that regardless of file names, or target device+axis. that in the funscript player you can still remap these, and even set priorities or use other algorithms to fill axisses with other information.

So for example an edge2 designed script could be made like:

File       | Device | Axis
hexcode_V0 | 0      | V0
hexcode_V1 | 0      | V1

While the user might have a single vibrator butplug and cock ring. He might remap 0V1 to 1V0.

But lets say we have 2 seperate vibrator scripts:

File       | Device | Axis
hexcode_V0 | 0      | V0
hexcode_V1 | 1      | V0

And we want to map this to an edge. We could map 1V0 then to become 0V1, and here comes a potentialy nice feature: we map it as 0V1,0V0. Which then could be read as: If 0V1 exists, take that, if not, fall back on 0V0. And you can make that a very big list!

And here is my first suggestion: changing the code to follow this pattern: [DeviceNr][AxisCode]. WIthout devicenr specified, we fallback on the legacy method (V1 is valve0, 0V1 is Vibrator1 on device0). Valve gets its own new code (and this does allow 2 letters in the code so VV can be fine)

And yes, while i showed tables, this should be in a json format.

It is TCode standard for OSR devices

My obvious extension would be that axes are paired into 6-axis sets LR012, LR345, LR678 where each set can describe full any movement in any direction

So if your device has two L0 axes, they must be L0 and L3


If we extends your thought, we basically come to “variants” - there should be a way to make a script have both LR0123 made for SR6, for OSR2 (which has higher R12 scale but no L12), SSR1-Twist (which has maybe R0), Handy 2 Pro Max/SSR1 (which has high-precition L0) and Handy (which has limited-speed limited-rate L0)

Yes, you can just make .osr2.funscript and .sr6.funscript (and I do have some of those), but meh

Again I think the funscript standard should be “standard agnostic” as well as “device agnostic”. The application thats reading the funscript can handle the device parameters.

I know I said we should plan for the future but we cant predict the future. And we probably wont get it perfect. I dont think we should over complicate it for things that dont exist yet if they ever will. I get the point about multiple of the same axis but that doesn’t exist and I’m not sure its likely to happen. Maybe taking things a bit too far. If someone wants to add two strokes the second can be called upperStroke or something. I dont know.

Instead we should plan for what we have currently and make that as future proof as we can.

We do have multi vib system right now and they have been labeled vib.funscript and vib1.funscript. Ive even added 2 more in my firmware. vib2 and vib3. We also have multiple auxiliary (A0-A3) channels for things NOT defined any other channel. I believe this is further proof the standards should be kept separate. TCode doesnt want to maintain funscript and funscript should need to change its self based on changes made to TCode .

MFP and XTP have supported zip containers for scripts since the beginning pretty much. It never really caught on. I recently removed it from XTP due to technical reasons. I liked this idea as it kept scripts modular for the user saving space for channels they don’t want need. It is kind of a pain to maintain on a personal level.

As far as target devices go, the script can include any channels it wants. Then the application reading the funscript can decide what to do based on device it knows about. Funscript is lower level and shouldnt care about the device. This can break down a bit if the device has physical limitations but I still think the application driving that device should handle the translation for the hardware its connected to.

2 Likes

The SLR format just needs to be slightly tweaked so that the id in the merged multi axis funscript is just taken from the files names:

video.funscript
video.twist.funscript
video.zzz.funscript
video.stroke.funscript
video.V3.funscript

merges into:

{
  "actions": [ ... ], // video.funscript
  "axes": [
    { "id": "twist", "actions": [ ... ] },
    { "id": "zzz", "actions": [ ... ] },
    { "id": "stroke", "actions": [ ... ] },
    { "id": "V3", "actions": [ ... ] },
  ]
}

Tho the axes might need to be changed to something else.

I’m biased but TBH I don’t really mind the SLR format even if it mixes TCode. But only because it is supported in more than 2 years of MFP versions so adding it site-wide to Eroscripts should be easier since a lot of people will be able to use it from the start.
The above format is obviously better but it requires everyone to update their MFP/XTP to use it so the adoption will be slow.

Yes, I think channels is a better term than axes for that.
I never jumped on the SLR bandwagon so this will be all new to code for me.
Though in my code base I don’t think it would be too difficult to do R0 vs twist as all this is mapped together already.

My main issue if mixing standards is not code complexity. Its dealing with the maintenance of both standards when they change. It also just smells really bad.

3 Likes

As the main creator of the TCode standard I basically agree with the points Khrull is making. Don’t confuse TCode and Funscript formats. Use the axis names: “twist, roll, pitch, etc” instead of TCode axis names.

7 Likes

Note though, people are lazy, and whatever they see that works, is what they will use. If they see they can use L0, they will. If you dont want that, you should just not give them that option.

Since support is there now, it has the downside that people use it.

And now the only way to get around that, is if you warn that a script uses deprecated features, to make people aware an update to the file should be done.
Even if its never adjusted, a warning will at least discourage new scripts from using this. And scripters might adjust this in existing scripts if never updated just to get rid of the warning.

But this does only work when you do implement better features, since if there arent any. People will just be anoyed and stick to an older version. But it could be worth to still do this in the future.

1 Like