Introduction
Recently I have implemented a basic timing processor for mania and standard gamemodes, and want to detail what goes into it and some things I learned. I don't believe the processor is 1:1 with osu!, but it does its job in allowing to analyze replays. A google doc version of this post can be found here.
The code for the processors can be found here:
About
In a rhythm game, players time to hit notes. The difference between the timing of the note and when the player hits the note is called the offset. An offset processor determines how to handle each hit, and is required to ensure hits are related to the correct notes. This does not calculate judgments, but that would be the step that follows this process.
The offset space
The offset is calculated via
This makes the left side have a negative offset if the player hits early, and the right side have a positive offset if the player hits late. The hit space is centered around 0 ms offset and consists of 4 regions, or windows: early miss window, early valid window, late valid window, and late miss window. Hits that fall in a window are marked appropriately. Hits that are outside the hit space boundary are not processed; that space is called the nop (no operation) space.
The 4 regions can be of any size; this gives us the first 4 controllable parameters of the offset processor. t_early_miss controls the offset at which the early miss boundary ends and early nop space begins. t_early_valid controls the offset at which the early valid boundary ends and early miss window begins. The early valid window and late valid window are separated by the 0 ms center. t_late_valid controls the offset at which the late valid boundary ends and late miss window begins. Finally, t_late_miss controls the offset at which the late miss boundary ends and late nop space begins.
The miss windows play an important role in penalizing the spamming of hits. The consequence of removing the miss windows, that is making t_early_miss = t_early_valid and t_late_valid = t_late_miss is that hits will then only either not be processed or be counted as valid hits. Effectively, it will only be possible to miss by not processing a key when required. On the other side, having large miss windows would cause empty spaces far from notes to be processed as misses.
A valid window too large is also not desirable as that may cause desynchronization in note correlation; when hitting twice or more before or after a timing, the processor may start consistently processing hits one or more notes ahead or behind, depending on how many notes fit in the valid window. To demonstrate, the following case has both miss boundaries at +/- 1000ms and both valid boundaries at +/- 500 ms:
Compare that to when the miss boundary is at +/- 200 ms and valid boundary is at +/- 100 ms:
The processor needs to handle single notes and hold notes. Single note timing is sensitive to press timing, but not release timing. Hold notes are sensitive to both press and release timing. Here we double the controllable parameters of the processor: we have a hit space and a release space, each having 4 controllable parameters. This allows release timings and hit timings have different behavior. Generally it’s fine for the release offset space not to have miss windows, and let the late nop space count release missed if not released within the valid window.
The processor
The replay and map can be represented with 4 actions that correspond to a change in keys state, k:
The processor needs to correlate the replay with the map. Two pointers keep track of the current location in the map and replay from which the offset is calculated. The offset and additional conditions are then used to determine the operation; how to advance the map pointer. There are 3 operations: advance to next note, advance to next timing, and nop.
When the offset is in early nop space, the operation is nop. When the offset is in the miss window, then operation is to advance to the next note. When the offset is in valid window, the operation is advance to next timing. When the offset is in late nop space, the operation is to advance to the next note, and is counted as a miss.
We must consider a case where there are no hits made all throughout the replay. After advancing to the next timing in the map a check needs to be made to see if it occurs too late after current action in replay, if any. If there are no actions in replay, the operation is to advance to the next note and is counted as a miss.
Finally, there can be various modification to the processing:
Recently I have implemented a basic timing processor for mania and standard gamemodes, and want to detail what goes into it and some things I learned. I don't believe the processor is 1:1 with osu!, but it does its job in allowing to analyze replays. A google doc version of this post can be found here.
The code for the processors can be found here:
Mania processor: https://github.com/abraker95/ultimate_osu_analyzer/blob/dev/analysis/osu/mania/score_data.py
Std processor: https://github.com/abraker95/ultimate_osu_analyzer/blob/dev/analysis/osu/std/score_data.py
About
In a rhythm game, players time to hit notes. The difference between the timing of the note and when the player hits the note is called the offset. An offset processor determines how to handle each hit, and is required to ensure hits are related to the correct notes. This does not calculate judgments, but that would be the step that follows this process.
The offset space
The offset is calculated via
This makes the left side have a negative offset if the player hits early, and the right side have a positive offset if the player hits late. The hit space is centered around 0 ms offset and consists of 4 regions, or windows: early miss window, early valid window, late valid window, and late miss window. Hits that fall in a window are marked appropriately. Hits that are outside the hit space boundary are not processed; that space is called the nop (no operation) space.
The 4 regions can be of any size; this gives us the first 4 controllable parameters of the offset processor. t_early_miss controls the offset at which the early miss boundary ends and early nop space begins. t_early_valid controls the offset at which the early valid boundary ends and early miss window begins. The early valid window and late valid window are separated by the 0 ms center. t_late_valid controls the offset at which the late valid boundary ends and late miss window begins. Finally, t_late_miss controls the offset at which the late miss boundary ends and late nop space begins.
The miss windows play an important role in penalizing the spamming of hits. The consequence of removing the miss windows, that is making t_early_miss = t_early_valid and t_late_valid = t_late_miss is that hits will then only either not be processed or be counted as valid hits. Effectively, it will only be possible to miss by not processing a key when required. On the other side, having large miss windows would cause empty spaces far from notes to be processed as misses.
A valid window too large is also not desirable as that may cause desynchronization in note correlation; when hitting twice or more before or after a timing, the processor may start consistently processing hits one or more notes ahead or behind, depending on how many notes fit in the valid window. To demonstrate, the following case has both miss boundaries at +/- 1000ms and both valid boundaries at +/- 500 ms:
Compare that to when the miss boundary is at +/- 200 ms and valid boundary is at +/- 100 ms:
The processor needs to handle single notes and hold notes. Single note timing is sensitive to press timing, but not release timing. Hold notes are sensitive to both press and release timing. Here we double the controllable parameters of the processor: we have a hit space and a release space, each having 4 controllable parameters. This allows release timings and hit timings have different behavior. Generally it’s fine for the release offset space not to have miss windows, and let the late nop space count release missed if not released within the valid window.
The processor
The replay and map can be represented with 4 actions that correspond to a change in keys state, k:
- Press: Change of key state from lifted to held; k: 0 → 1
Release: Change of key state from held to lifted; k: 1 → 0
Hold: Change of key state from held to held; k: 1 → 1
Free: Change of key state from lifted to lifted; k: 0 → 0
The processor needs to correlate the replay with the map. Two pointers keep track of the current location in the map and replay from which the offset is calculated. The offset and additional conditions are then used to determine the operation; how to advance the map pointer. There are 3 operations: advance to next note, advance to next timing, and nop.
When the offset is in early nop space, the operation is nop. When the offset is in the miss window, then operation is to advance to the next note. When the offset is in valid window, the operation is advance to next timing. When the offset is in late nop space, the operation is to advance to the next note, and is counted as a miss.
We must consider a case where there are no hits made all throughout the replay. After advancing to the next timing in the map a check needs to be made to see if it occurs too late after current action in replay, if any. If there are no actions in replay, the operation is to advance to the next note and is counted as a miss.
Finally, there can be various modification to the processing:
- Blank miss: Operation for hit nop space is changed to be to advance to next note, and is recorded as a special kind of miss. This is desirable if the goal is for the player to be penalized for hitting empty spaces no matter how far from the next note that space is.
- Lazy sliders: Operation for a valid hit is to always to advance to the next note. The entire release offset space is nop. This ignores release timings in hold notes.
- Overlap miss handling: Modifies processing to keep advancing to the next note without advancing the replay pointer so long as the offset ends up in the miss window. This can result in multiple misses due to one hit, and can be used as a anti-spam mechanic.
- Overlap valid handling: Modifies processing to keep advancing to the next note without advancing the replay pointer so long as the offset ends up in the valid window. This can result in the completion of multiple notes with one press or release. This has the effect of making it far easier to spam vibro or other dense patterns.
- Dynamic offset space: The size of the offset space is changed to be proportional to the spacing between each note. This effectively removes nop space, and sets t_early_miss and t_late_miss to be located at the midpoint of two notes. This has the effect of creating tighter timing windows the more dense the patterns are and can be used as an anti-spam mechanic.