A recent Auto update made it work MUCH better with such beatmaps than what it used to be, and I'm very grateful for that because now we can watch epic replays of epic maps with concurrent objects. However I believe it's not as good as it could be (especially regarding sliders with objects over them), getting a lot of 100s and 50s, and even breaks where SS is possible. In this post I'll attempt to describe every possible situation with concurrent objects and explain the solution that I came up with for each of those.
Circles
A circle is a hit object that occurs at a specific time and can be clicked in a wide interval around that time without being recorded as a 100, a wider interval without being recorded as 50, and even wider interval without being recorded as a miss. The possible situation that could occur is multiple circles placed on the same timestamp, like that:
Let's say that there are n circles, we'll call them 1, 2, ..., n on the same timestamp t. Then each circle p (1 <= p <= n) can be clicked on a timestamp t + p - (n / 2) (for consistency, let's think that (n / 2) is rounded towards +infinity), effectively making this pattern into a really fast stream.
So if we have circles 1, 2, 3, 4, 5 on the same timestamp 10, then the circle #1 will be clicked on the timestamp 10 + 1 - 3 = 8, circle #2 on 10 + 2 - 3 = 9, circle #3 on 10, #4 on 11 and #5 on 12.
And if we have circles 1, 2, 3, 4 on the timestamp 10, then the circle #1 will be clicked on 10 + 1 - 2 = 9, circle #2 on 10 + 2 - 2 = 10, #3 on 11 and #4 on 12.
A minimal timestamp in osu! is 1 millisecond, so this way you can click a lot of circles situated in one timestamp without going out of the 300 interval.
Sliders
A slider is an object which consists of a number of parts, a start circle, (maybe, perhaps many) slider ticks and (maybe) a repeat arrow. When you follow the slider, another important part appears, a slider ball:
In order to 300 a slider, one needs to click on the start circle (as on a normal circle, refer to the Circles part), then have the cursor inside the slider ball and any of the buttons pressed on the timestamp of each slider tick, as well as on the timestamp of the repeat arrow and the end circle. Since the repeat arrow and the end circle do not differ from the slider ticks in this regard, I'll refer to both slider ticks, repeat arrow(-s) and the end circle as slider ticks. I'll also introduce the term slider segment: it is the slider part between each slider tick, and between the start circle and the first slider tick. The slider you saw on the screenshot has 4 slider segments this way, along with an end circle (which can be thought of as a short slider segment on its own):
The time of a slider segment is a time starting from timestamp of the beginning slider tick and ending on timestamp of the ending slider tick - 1 of that segment. For the first segment, from the start circle to the first slider tick, that time would start from timestamp of the start circle click. The timestamp of a slider segment is the first timestamp in the time of the slider segment. The starting position of a slider segment is the position of its first slider tick (or start circle).
This allows to define the way of 300-ing a slider in the following way: click the start circle, and then for each next slider segment the cursor needs to be at most the ball radius away from the starting position of that segment (clicking position), and at least one of the buttons needs to be pressed, on the timestamp of that slider segment.
Let's say that the object is concurrent with a slider segment if the object's timestamp is located inside the time of that slider segment. Then, for each slider segment of the given slider:
If there are no concurrent objects, traverse the segment like Auto normally would.
If there is one or more concurrent object, then omit the normal segment traversal:
1. The object's timestamp is the same as the timestamp of this segment.
- It's a circle (normal, or another slider's start circle): handle it as if it was one timestamp later, so first handle this segment's starting position and on the next timestamp handle the circle. However, if the clicking area of the circle intersects with the clicking area of the segment's slider tick, then the circle may be clicked along with the slider tick condition being satisfied.
- It's a slider tick of another slider: if the clicking area of the current slider tick intersects with the clicking area of another slider's tick, then place the cursor between them so that it satisfies both ticks, which would be right in the middle between the two positions. If the clicking areas do not intersect, choose the slider that still satisfies its 300 condition. If there are multiple sliders, then try to keep as many sliders' 300 condition as possible.
2. The object's timestamp is after the timestamp of this segment.
- It's a circle: just handle it normally.
- It's another slider's segment start: handle according to this scheme.
This means that if we have one long slider without any intermediate ticks and a couple of small fast sliders starting and ending within this slider's segment time, then we click the long slider's start circle, then traverse the small sliders normally and then make sure to have the cursor in the appropriate position on the long slider's ending tick and a button pressed.
Spinners
A spinner is an object that has a starting time and an ending time (last timestamp when the cursor position matters), and requires the cursor to go around its center with as little detours as possible for the optimal completion.
On any timestamp of the spinner:
- If there's a concurrent circle, and its clicking time contains some time before the spinner starts, or after the spinner ends, then click it in that time (when the spinner is not active).
- If there's a concurrent slider segment, then handle its condition (if necessary) and treat the spinner as a concurrent object constantly over the segment time.
Hopefully, I haven't forgotten anything, please point at any errors shall you find them. I'd love to try to implement everything I said here myself, but at the current time I don't have any means of doing that :/ (I heard that osu! might be going open-source? If that happens and this is not implemented by then, this is the first thing I'll do with it).
Circles
A circle is a hit object that occurs at a specific time and can be clicked in a wide interval around that time without being recorded as a 100, a wider interval without being recorded as 50, and even wider interval without being recorded as a miss. The possible situation that could occur is multiple circles placed on the same timestamp, like that:
Let's say that there are n circles, we'll call them 1, 2, ..., n on the same timestamp t. Then each circle p (1 <= p <= n) can be clicked on a timestamp t + p - (n / 2) (for consistency, let's think that (n / 2) is rounded towards +infinity), effectively making this pattern into a really fast stream.
So if we have circles 1, 2, 3, 4, 5 on the same timestamp 10, then the circle #1 will be clicked on the timestamp 10 + 1 - 3 = 8, circle #2 on 10 + 2 - 3 = 9, circle #3 on 10, #4 on 11 and #5 on 12.
And if we have circles 1, 2, 3, 4 on the timestamp 10, then the circle #1 will be clicked on 10 + 1 - 2 = 9, circle #2 on 10 + 2 - 2 = 10, #3 on 11 and #4 on 12.
A minimal timestamp in osu! is 1 millisecond, so this way you can click a lot of circles situated in one timestamp without going out of the 300 interval.
Sliders
A slider is an object which consists of a number of parts, a start circle, (maybe, perhaps many) slider ticks and (maybe) a repeat arrow. When you follow the slider, another important part appears, a slider ball:
In order to 300 a slider, one needs to click on the start circle (as on a normal circle, refer to the Circles part), then have the cursor inside the slider ball and any of the buttons pressed on the timestamp of each slider tick, as well as on the timestamp of the repeat arrow and the end circle. Since the repeat arrow and the end circle do not differ from the slider ticks in this regard, I'll refer to both slider ticks, repeat arrow(-s) and the end circle as slider ticks. I'll also introduce the term slider segment: it is the slider part between each slider tick, and between the start circle and the first slider tick. The slider you saw on the screenshot has 4 slider segments this way, along with an end circle (which can be thought of as a short slider segment on its own):
The time of a slider segment is a time starting from timestamp of the beginning slider tick and ending on timestamp of the ending slider tick - 1 of that segment. For the first segment, from the start circle to the first slider tick, that time would start from timestamp of the start circle click. The timestamp of a slider segment is the first timestamp in the time of the slider segment. The starting position of a slider segment is the position of its first slider tick (or start circle).
This allows to define the way of 300-ing a slider in the following way: click the start circle, and then for each next slider segment the cursor needs to be at most the ball radius away from the starting position of that segment (clicking position), and at least one of the buttons needs to be pressed, on the timestamp of that slider segment.
Let's say that the object is concurrent with a slider segment if the object's timestamp is located inside the time of that slider segment. Then, for each slider segment of the given slider:
If there are no concurrent objects, traverse the segment like Auto normally would.
If there is one or more concurrent object, then omit the normal segment traversal:
1. The object's timestamp is the same as the timestamp of this segment.
- It's a circle (normal, or another slider's start circle): handle it as if it was one timestamp later, so first handle this segment's starting position and on the next timestamp handle the circle. However, if the clicking area of the circle intersects with the clicking area of the segment's slider tick, then the circle may be clicked along with the slider tick condition being satisfied.
- It's a slider tick of another slider: if the clicking area of the current slider tick intersects with the clicking area of another slider's tick, then place the cursor between them so that it satisfies both ticks, which would be right in the middle between the two positions. If the clicking areas do not intersect, choose the slider that still satisfies its 300 condition. If there are multiple sliders, then try to keep as many sliders' 300 condition as possible.
2. The object's timestamp is after the timestamp of this segment.
- It's a circle: just handle it normally.
- It's another slider's segment start: handle according to this scheme.
This means that if we have one long slider without any intermediate ticks and a couple of small fast sliders starting and ending within this slider's segment time, then we click the long slider's start circle, then traverse the small sliders normally and then make sure to have the cursor in the appropriate position on the long slider's ending tick and a button pressed.
Spinners
A spinner is an object that has a starting time and an ending time (last timestamp when the cursor position matters), and requires the cursor to go around its center with as little detours as possible for the optimal completion.
On any timestamp of the spinner:
- If there's a concurrent circle, and its clicking time contains some time before the spinner starts, or after the spinner ends, then click it in that time (when the spinner is not active).
- If there's a concurrent slider segment, then handle its condition (if necessary) and treat the spinner as a concurrent object constantly over the segment time.
Hopefully, I haven't forgotten anything, please point at any errors shall you find them. I'd love to try to implement everything I said here myself, but at the current time I don't have any means of doing that :/ (I heard that osu! might be going open-source? If that happens and this is not implemented by then, this is the first thing I'll do with it).