Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix one x way joystick rotary step sometimes rotates character2 steps #12541

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

zorro2055
Copy link

Hello,

For an X-way rotary joystick that presses a virtual Dial or Positional Inc or Dec button for a certain amount of time, the virtual button must be "held" down long enough to guarantee it will be detected at a frame. This means it has to be held down for a time just slightly longer than a frame. This means that one rotary step could register twice if the timing is right across two frames.

This PR adds an "X-Way Joystick" toggle option in the Analog Input menu. When off (default), behavior does not change at all. When on, to avoid the above issue, if an inc or dec press is detected, mame will ignore any press on the next frame. So, this guarantees that one rotary step would always only rotate the character one step. It specifically has to be toggled on for a joystick designed for that to take advantage of it. This does mean the character would only be able to rotate at max, half the frame rate. So if the frame rate is 60fps, the character would rotate at 30fps which is still plenty fast.

I made it so this toggle only shows up for Dial and Positional Analog devices (games that use this type of controller use one or the other of those devices). This option does not show up for other Analog devices such as Pedals, etc.

Picture of option in Ikari Warriors analog menu:
image

This is the x-way (12-way in this case) rotary joystick that uses this input scheme and will benefit from this PR:
https://www.ultimarc.com/arcade-controls/joystick-accessories/ikari-12-way-rotary-upgrade-for-servostik-j-stik/

There are about a dozen games that use this type of rotary joystick. The most famous one is Ikari Warriors.
https://www.reddit.com/media?url=https%3A%2F%2Fi.redd.it%2Fqo4x3p5dm4ry.jpg - note the octagonal joystick controllers
https://www.youtube.com/watch?v=yQYoiZcqURg

Let me know if this looks good to merge. Or if there are any questions or comments. Thanks!

@dinkc64
Copy link

dinkc64 commented Jul 3, 2024

Nice one, been wanting such a feature since forever!

@happppp
Copy link
Member

happppp commented Jul 3, 2024

There is no universal solution for this, the number of animation steps per IPT_DIAL or similar controller distance traveled depends on game, it's not always once per frame.

@dinkc64
Copy link

dinkc64 commented Jul 3, 2024

happppp, maybe some sort of digital / quadrature-encoder-ish style input type can be made to handle this?

@zorro2055
Copy link
Author

Thanks for the feedback @happppp and @dinkc64 .

Hmm, for me, from my testing, this PR fixes the issue referred to in the title for all the games that use the rotary joystick that I know of. Most of them are Analog type Positional, only 3 are type Dial. See what analog settings I used at the end of this message. With these settings, one rotational step on the rotary joystick would sometimes rotate the character 2 steps if the X-Way Joystick option I added was off. If that option was turned on, the character would never rotate 2 steps if the rotary joystick was rotated one step. My rotary joystick is configured to press the "virtual" Positional or Dial Increment and Deincrement Buttons for 20 ms. Longer than one frame, but less than 2 frames.

@happppp Are there other IPT_DIAL games that have multiple animation steps (between frames I guess)? Or do input polling multiple times per frame? That would give me something to look at. I'm all for making this PR work for scenarios I am not aware of as well. Would adding another option such as "Ignore Number of Input Polls" or "Time to ignore next button press" in the analog menu help?

@dinkc64 i'll wait for @happppp response to your suggestion to get a better understanding before asking about it.

I look forward to your answers to help clarify things.

Analog Settings for Games I tested that support an X-way rotary joystick
For the following games with Positional Analog type, I used the settings of:
Positional Increment / Deincrement Speed: 1
Positional Sensitivity: 100
Bermuda Triangle
DownTown
Gondomania
Guerilla War
Heavy Barrel
Ikari Warriors
Victory Road
Ikari III
Midnight Resistance
SAR - Search and Rescue
T.A.N.K
Time Soldiers

Then I used these individual settings for the Dial games that support a rotary joystick:
Caliber 50
Dial Increment / Deincrement Speed: 4
Dial Sensitivity: 100

Touchdown Fever
Dial Increment / Deincrement Speed: 2
Dial Sensitivity: 100

Touchdown Fever 2
Dial Increment / Deincrement Speed: 2
Dial Sensitivity: 100

@happppp
Copy link
Member

happppp commented Jul 4, 2024

Ah right, inc/dec speed is the number of incs/dec steps per frame for IPT_DIAL, so yeah I suppose it will work better than I first thought (user will need to find the correct speed setting per game). I'm ok with the concept and my disbelief is gone, and it is not feature creep.

Is "X-Way Joystick" meaningful? Something like "One-shot Increment/Decrement" or "Single Step Increment/Decrement" makes more sense.
Is it user friendly to limit it to IPT_POSITIONAL and IPT_DIAL? It can in theory be used for anything that does not autocenter (aka center speed of 0), so basically any analog input.
Source changes: I didn't look much. Keep in mind to only use tabs for left indentation, use spaces in if it's in the middle of a line instead of "bool (tab) (tab) (tab) something;"

@zorro2055
Copy link
Author

Thanks @happppp , good to hear!

Ok, so basically, more generalize it. That makes sense. I'm more partial to "Single Step Increment/Decrement". Here are some other possible suggestions. I would not call these final suggestions, but more fuel to try to come up with the best description.

The most complete, but too long description would probably be "Prevent double detection of increment/decrement fixed time pulse of 1.x frames duration. Changes max input detection rate to game frames per second divided by 2.".
Other descriptions to provide possible inspiration:
"Prevent Extra Increment/Decrement Detection"
"Prevent Double Increment/Decrement Detection"
"Ignore Next Frame Increment/Decrement Detection"

If none of those inspire another description, I'm fine with going with "Single Step Increment/Decrement". Just let me know what you think.

I don't know the other analog types as well, but looking at the code now, I see what you are saying. Some of the types can be set to auto centering, but centering can also be set to 0. Ok, I will remove the if statements so that this option is visible for all analog device types. And that this option is always applied for the device if the option is set to yes.

Will do on the source changes. I'll be removing the "internal" tabs and replacing with spaces.

So, I can push the updated changes once there is agreement on the menu option name. I'll change the xwayjoystick variable based on that final menu name option as well.

@rb6502
Copy link
Contributor

rb6502 commented Jul 4, 2024

With regard to the tabs/spaces, if you build MAME with TOOLS=1 it'll make a utility called "srcclean" that you can run on a file to clean everything up.

@cuavas
Copy link
Member

cuavas commented Jul 4, 2024

I agree that there’s a problem, but I don’t think this is the solution. The issue is that the handling of “sensitivity” for positional controls is completely broken.

Moving “N steps per frame” isn’t useful behaviour for positional controls. You really want to be able to configure it to move “one step every N frames”. If you hold the button/direction, it should move continuously, but at a reasonable speed.

It may be possible to just apply different scaling for positional inputs when the source is digital.

We already apply scaling to the sensitivity when an absolute control is used for a relative axis input. See the code here – we divide by eight to get more sane scaling:
https://github.com/mamedev/mame/blob/mame0267/src/emu/ioport.cpp#L3832

I think that function really needs to be updated to treat sensitivity for positional inputs differently when an absolute control is used for a wrapping positional input, or a digital control is used for any positional input.

@happppp
Copy link
Member

happppp commented Jul 4, 2024

Those other setting descriptions you suggested? IYAM they don't strike my fancy.

Tweaking analog sensitivity won't get these type of controllers to work, the one posted by zorro: https://www.ultimarc.com/arcade-controls/joystick-accessories/ikari-12-way-rotary-upgrade-for-servostik-j-stik/

@cuavas
Copy link
Member

cuavas commented Jul 4, 2024

Those other setting descriptions you suggested? IYAM they don't strike my fancy.

Tweaking analog sensitivity won't get these type of controllers to work, the one posted by zorro: https://www.ultimarc.com/arcade-controls/joystick-accessories/ikari-12-way-rotary-upgrade-for-servostik-j-stik/

Those will never work well because they don’t show as an analog axis from the PC’s point of view. MAME doesn’t respond to button presses and releases, it polls the state of buttons on each emulated frame update.

MAME will miss “clicks” in various situations, for example:

  • Emulated frame rate is slow enough that the simulated press and release happen between frame updates. (Either a system with slow video refresh, or a system that’s too demanding to run at full speed.)
  • Spinning the control fast enough that there isn’t a frame update during each pressed and released interval.

You’ll also currently get bad results if the emulated system is running fast enough that it does more than one frame update between the simulated press and release.

MAME’s approach of polling once per frame is fundamentally incompatible with things that are physically relative axis controls but work by simulating button pushes.

Note that this PR only handles the the case where the emulated system is running fast enough that there are multiple frame updates between the simulated press and release. It doesn’t deal with the press and release happening between frame updates, or spinning the control too fast to see every pressed and released interval.

And all that doesn’t change the fact that the current behaviour is basically useless for positional controls. You never want “N positions per frame” when holding down a key/button assigned to one of these inputs – you want it to move one position when you press the key/button, and then continue to more at a reasonable rate if you continue to hold the key/button (and one position per frame is never a reasonable rate).

@zorro2055
Copy link
Author

zorro2055 commented Jul 5, 2024

@rb6502 Thanks, I'll use that.

@happppp Sounds good. I'll use "Single Step Increment/Decrement". I'll replace the xwayjoystick variable with singlestepincdec and the enumerated constant ANALOG_ITEM_XWAYJOYSTICK with ANALOG_ITEM_SINGLESTEPINCDEC. And I'll fix the comments accordingly. I should have these changes pushed by the end of this weekend if not before.

@cuavas The way this PR is right now does work fine for me right now. The 12-way rotary joystick that I have that happppp just mentioned simulates a virtual inc or dec button press (depending on which direction rotated) for a fixed time. Right now, I have that fixed time set to 20ms. The joystick snaps 30 degrees to trigger this "virtual" button press. That is longer than a frame, but less than two frames. So Mame is guaranteed to detect it. I have added the option in this PR to essentially keep double detection from happening. Any press that happens a frame after a press was just recorded is ignored if this option is on.

It's true that the game has to run at a consistent native frame rate, in this case, about 60fps, for this to work properly. Since all the games that use a rotary joystick which I tested are older, this is not an issue. For newer games that run slower, this would not be an issue if the game runs slower if the fixed time the "button" is held down is still slightly longer than one frame. If the frame rate is inconsistent, then this option would not work as well.

I can see your issue with Positional if real physical buttons are used to try to rotate the character in Ikari Warriors or similar game. I can only think of one new option (separate and not related to this PR) that might help with that, but would take some effort to determine if it could work and implement.

Basically add another on/off option to the analog menu called something like "Interpret Sensitivity as Frames per Inc/Dec". When that is turned on, then if a button is first pressed, the game inc or dec something, then if the button is continually pressed, it waits that many frames before applying the key inc/dec again....

@zorro2055
Copy link
Author

@happppp That went faster than anticipated. I have updated this PR per the changes discussed, so it should be ready to merge. Let me know if I missed something though. Below are some screenshots with the updated option description. Thanks.

From Ikari Warriors:
image

From Hard Drivin' that has several analog devices, none of them Positional or Dial:
image

@happppp
Copy link
Member

happppp commented Jul 5, 2024

Sorry, you'd have to convince cuavas here first, he's not as lenient. If someone merges it now (or after a few cleanups), he will very likely revert it.

@zorro2055
Copy link
Author

Ah, ok, I did respond of course. We'll see what his response to my response will be....

@zorro2055
Copy link
Author

@cuavas Okay, I've got an idea how to fix your issue at the same time solving my issue. Basically, instead of adding the option to the analog menu in this original PR, add an option called something like "Frames to ignore Inc/Dec after Pressed".

If this option is set to 0, no behavior is modified. It is essentially turned off.

If it is set to 3, then that button is ignored for 3 frames after it is detected and acted upon. So Frame 1 is detected and the button press triggers whatever game action. Frames 2-4 would be ignored. If the button is still down at Frame 5, the button would trigger the game action again.

For my case, I would set it to 1. So, if I twist my rotary joystick so that it snaps one 30deg rotation, that triggers it to send out a virtual button press that lasts 20ms. At 60fps, there are 16.7ms between frames. So this virtual button press is guaranteed to be detected in at least one frame, but sometimes that one pulse will be detected over two frames which is not wanted. With this set to 1, the first frame after the button is pressed would be ignored which is what this original PR does, so that works.

The only other question is the button ignored for the entire set number of frames after the button is pressed even if the button is released before the end of that time?

For instance, if this option is set to 10, is the behavior the following: button is pressed at Frame 1, button is held until Frame 5, then released on Frame 6. If pressed again on Frame 7, the computer would view that as continually pressed. So what if the button is pressed on Frame 8? Would that press register and 10 frames would be ignored after that press?

Or would the computer still ignore any button press until after Frame 11?

Let me know what you think.

@happppp
Copy link
Member

happppp commented Jul 31, 2024

I had an idea: Expose the PORT_IMPULSE setting to the per-game cfg, and add support for impulse on digital inc/dec inputs of analog controls. Then users that need it can modify the cfg just like they can currently override the PORT_TOGGLE setting in a cfg.

But at the same time I think PORT_IMPULSE is not future proof, it's kind of a hacky method and will likely be removed from MAME at some point, with a different solution coin ports that need it. Perhaps a coin box device, coin counters and lockout can be moved to there too.

@zorro2055
Copy link
Author

zorro2055 commented Aug 1, 2024

Thanks @happppp for that idea. I wouldn't mind doing that, but if you think it would be removed at some point, that kind of dampens my motivation to make this change if everyone on this thread accepted this idea.

I guess that brings up the point, what would be considered a non-hacky method to support the ultimarc rotary joystick? Since by its Pulse nature, it doesn't fit cleanly with POLL based input. Maybe there is no answer to that, thus this standstill.

I will say there is one way to make it not hacky, but it is not realistic because it would be a huge amount of work, and probably not supported by all platforms. Basically, use EVENT based input drivers rather than POLL based input drivers. So Mame could check the event log of input that has occurred since the last frame and use the last input by default to emulate the original POLLing that I assume is typically used on arcade games. But that behavior could be modified for a game to look at all events since the last frame. For instance, my Ultimarc pulse time can be set as low as 4ms (I think that is the minimum resolution of the event based udev linux driver that I tested it with), A game with a rotary joystick would just search the input events that happened since the last frame, lets say button up events. Then, it could rotate the character the total number of button up events it counted. Or limit to a maximum of one character rotation if one or more button up events are detected since the last frame. Anyway, not realistic to implement, but just throwing it out there to maybe facilitate other ideas....

Hopefully this sparks conversation?

@zorro2055
Copy link
Author

Hey all, not sure if this will help, but I am responding to each of @cuavas comments in detail coming up with some new ideas along the way which maybe will get this discussion to start moving forward to a resolution. I was thinking about this in the back of my mind before, but @happppp inspired me to respond sooner now than later.

I've quoted everything @cuavas said although some of the later comments might supercede the older comments.

I agree that there’s a problem, but I don’t think this is the solution. The issue is that the handling of “sensitivity” for positional controls is completely broken.

Okay, thinking about that more, I see what you are saying. It could be assumed that the positional increment/deincrement is always 1 with sensitivity always 100% (or character always rotates 1 rotation per frame). So perhaps in the analog controls, it is not even listed, and the sensitivity is not listed. They are replaced with Positional Button Down Behavior and Positional Frames Ignored.

Moving “N steps per frame” isn’t useful behaviour for positional controls. You really want to be able to configure it to move “one step every N frames”. If you hold the button/direction, it should move continuously, but at a reasonable speed.

This makes sense, hence my proposing my above comment. I would also think it would be be useful to have behavior where if the button is hit, the character rotates once, but does not rotate again until the button is released. So the player knows he can rotate the character as fast as he can hit the button. This allows the character to rotate faster then one step every N frames, but there is the very slight possibility if the player hits the button super quick and releases between a frame, the button would not register.
To accommodate both options, Positional Button Down Lockout Behavior maybe has the following settings:

  • X Frames (X set in Positional Frames Ignored)
  • X Frames or Button Release (X set in Positional Frames Ignored)
  • Until Button Release

Positional Frames Ignored would have the options of 0-255 (0 being character always rotates 1 rotation if button polls down at a frame, 1 ignores one frame after button initially down (my case with for the Ultimarc rotary joystick ), 2 ignores two frames after initially down, etc.

It may be possible to just apply different scaling for positional inputs when the source is digital.

We already apply scaling to the sensitivity when an absolute control is used for a relative axis input. See the code here – we divide by eight to get more sane scaling:
https://github.com/mamedev/mame/blob/mame0267/src/emu/ioport.cpp#L3832

I think that function really needs to be updated to treat sensitivity for positional inputs differently when an absolute control is used for a wrapping positional input, or a digital control is used for any positional input.

Interesting on scaling, but as @happppp said, at least in the case for the Ultimarc rotary joystick which uses virtual inc/dec buttons, changing scaling would not solve the the issue, but only change how many rotations per "virtual press" the character would have. What this PR does is guarantee detection, but not do a double detect. It does this when the rotary joystick, when twisted one 30 degrees snapping step, triggers the virtual button to be "held" down for greater than 1/60 of a second, but less than 2/60 of a second. By ignoring the frame after button detection, that guarantees detection without double detection if the game is running at least close to its proper frame rate.

Those will never work well because they don’t show as an analog axis from the PC’s point of view. MAME doesn’t respond to button presses and releases, it polls the state of buttons on each emulated frame update.

This works for the Ultimarc case when running close to the framerate as I have personally experienced. True, the con is if running far from the proper framerate, it will not work as well, but the game in general will not be as fun to play at that point, The pro is giving a very close to original arcade experience. Pros and cons, just like just using buttons have a pro of more commonly available, but has a con of not give as authentic and as fun of an experience.

MAME will miss “clicks” in various situations, for example:
Only if not running close to frame rate, see above comment for pros and cons.

Emulated frame rate is slow enough that the simulated press and release happen between frame updates. (Either a system with slow video refresh, or a system that’s too demanding to run at full speed.)

True, that is a con for the Ultimarc joystick, but the game is probably not that fun to play in general at that point when not running close to the native frame rate. The pro is a very close to original arcade experience when the game is running close to the native framerate.

Spinning the control fast enough that there isn’t a frame update during each pressed and released interval.

That is theoretically possible, but I have not experienced it. Rotating the joystick more than 30 times at 30 degree steps in one second ( 30 Hz detection rate when game is running close to 60Hz native frame rate) is pretty difficult. I have not noticed this happening with my joystick.

You’ll also currently get bad results if the emulated system is running fast enough that it does more than one frame update between the simulated press and release.

If the game is running close to native framerate and the "virtual" press time of the Ultimarc rotary joystick is set to greater than the time of one frame, but less than the time of two frames, then the possible double detect on the second frame will always be ignored. Since it "holds" the button down less than two frames, one 30 degree snap rotation of the joystick will never be detect over 3 or more frames. So no issue with a one fixed length time "virtual" button press causing rotation across multiple frames.

MAME’s approach of polling once per frame is fundamentally incompatible with things that are physically relative axis controls but work by simulating button pushes.
This PR does allow button presses to simulate a rotary motion without double detects at the expense of halving the detection framerate (so detects inc/dec "virtual" button press at 30Hz if native framerate is 60Hz), and needed the game to run close to native framerate to work well. The pro is a more fun close to original arcade experience.

Note that this PR only handles the the case where the emulated system is running fast enough that there are multiple frame updates between the simulated press and release. It doesn’t deal with the press and release happening between frame updates, or spinning the control too fast to see every pressed and released interval.

Copying comment responses from above that address this.

If the game is running close to native framerate and the "virtual" press time of the Ultimarc rotary joystick is set to greater than the time of one frame, but less than the time of two frames, then the possible double detect on the second frame will always be ignored. Since it "holds" the button down less than two frames, one 30 degree snap rotation of the joystick will never be detect over 3 or more frames. So no issue with a one fixed length time "virtual" button press causing rotation across multiple frames.

That is theoretically possible, but I have not experienced it. Rotating the joystick more than 30 times at 30 degree steps in one second ( 30 Hz detection rate when game is running close to 60Hz native frame rate) is pretty difficult. I have not noticed this happening with my joystick.

And all that doesn’t change the fact that the current behaviour is basically useless for positional controls. You never want “N positions per frame” when holding down a key/button assigned to one of these inputs – you want it to move one position when you press the key/button, and then continue to more at a reasonable rate if you continue to hold the key/button (and one position per frame is never a reasonable rate).

Yeah, true, implementing the changes removing proposed in the first and second comment responses with replacing Positional Inc/Dec and Positional Sensitivity with Positional Button Down Behavior and Positional Frames Ignored would fix this issue, give another option, and solve the Ultimarc issue that this original PR set out to do.

Hopefully these answers help clarify things. And hopefully the first and second response to comments provides a more complete solution to help fix the broken positional system. If this seems like this could be a good new direction, but with certain tweaks, please let us know what those are. It would take some work to make these changes, and I have not investigated details yet, but I would be up to do it if everyone was on board.

Also, I think it is more convenient to have these just proposed new options in the analog settings menu, but if people think those option should be in the cfg file as suggested by @happppp as another option, I could roll with that too.

@zorro2055
Copy link
Author

zorro2055 commented Aug 4, 2024

Note the new options I proposed would not "fix" these games that use a rotary joystick, but are still the Dial type:
Caliber 50
Touchdown Fever
Touchdown Fever 2

I assume they'll be switched to Positional at some point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants