question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

`HitResults` are not calculated properly at the hit-window edges compared to osu!stable.

See original GitHub issue

Describe the bug:

For the OsuRuleset (did not research/test for other rulesets) I have found a bug as described in the title.

Example:

Given OD=10, which means the hit-window for Meh (50) is supposed to be ±59.5ms:

In Stable: +59ms => Ok, +60ms => Meh In Lazer: +59ms => Ok, +60ms => Ok which is incorrect.

This is demonstrated in one score of mine:

Side by side comparison: https://www.youtube.com/watch?v=IQGsfzOEyJY

  1. If you skip to the end you can already see that there is a big accuracy difference.
  2. At timestamp 1:57 in the last stream: There is supposed to be three consecutive 50’s which is calculated correctly in Stable but not in Lazer.

I used gosumemory to calculate the hit offsets of this replay and at the suspicious note (=6th last note of the map) I hit the note with exactly +60ms offset. (https://gist.github.com/abstrakt-osu/46997fa9d203564ed1f1b1eee09a18ba#file-gosu-json-L894).

Screenshots or videos showing encountered issue:

Side by side comparison: https://www.youtube.com/watch?v=IQGsfzOEyJY

Beatmap: https://osu.ppy.sh/beatmapsets/1010865#osu/2117132 Score (+Replay): https://osu.ppy.sh/scores/osu/3321687146

Stable replay: https://youtu.be/FDhGuZh3P_U Lazer replay: https://youtu.be/Ositc3vu_bg

osu!lazer version: 2020.1225.0

Logs:

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:2
  • Comments:11 (9 by maintainers)

github_iconTop GitHub Comments

1reaction
Walavoucheycommented, Jan 30, 2023

In the above wiki pr I’ve done some investigation about how stable does hit window comparisons. In summary:

Ruleset Comparison (stable) Comparison (lazer) Comparison (lazer replay)
osu! abs(round(hit error)) < floor(hit window) abs(hit error) <= hit window abs(round(hit error)) <= hit window
osu!taiko abs(round(hit error)) < floor(hit window), except for the miss window which uses <= abs(hit error) <= hit window abs(round(hit error)) <= hit window
osu!mania abs(round(hit error)) <= floor(hit window) abs(hit error) <= hit window abs(round(hit error)) <= hit window
  • Hit windows are truncated (cast to integers, i.e. floored) after accounting for OD, while the hit error uses rounded time derived from the audio engine.
  • Lazer sticks with doubles everywhere and doesn’t round anything, except for replay storage.
  • DT and HT make the clock tick faster and slower respectively, which scales the hit error and hit windows used in calculations. This may affect how much the hit windows can vary, but I haven’t thought it through thoroughly.
A few examples
Case Max error (formula) Max error (lazer) Error Max error (lazer replay) Error Max error (stable) Error Delta (stable - lazer replay)
OK window osu!, OD 10 60 <= 60.0 0 < 60.5 +0.5 < 59.5 -0.5 -1
OK window osu!, OD 5.4 96.8 <= 96.8 0 < 96.5 -0.3 < 95.5 -1.3 -1
OK window osu!, OD 5.6 95.2 <= 95.2 0 < 95.5 +0.3 < 94.5 -0.7 -1
OK window osu!mania, OD 5.4 110.8 <= 110.8 0 < 110.5 -0.3 < 110.5 -0.3 0
OK window osu!mania, OD 5.6 110.2 <= 110.2 0 < 110.5 +0.3 < 110.5 +0.3 0
Some theoreticals
Case Max error (formula) Max error (lazer replay if ceiled&floored) Max replay frame error Max error (lazer replay if floored&ceiled) Max replay frame error Max replay frame error (stable)
OK window osu!, OD 10 60 < 61 60 <= 60 60 59
OK window osu!, OD 5.4 96.8 < 97 96 <= 96 96 95
OK window osu!, OD 5.6 95.2 < 96 95 <= 95 95 94
OK window osu!mania, OD 5.4 110.8 < 111 110 <= 110 110 110
OK window osu!mania, OD 5.6 110.2 < 111 110 <= 110 110 110
  • “ceiled&floored” is smoogi’s suggestion. It effectively means floor(abs(hit error)).
  • “floored&ceiled” is the reverse of smoogi’s suggestion. It effectively means ceil(abs(hit error)).

Neither of these two give correct effective hit windows (max error) for any case. The comparisons simply aren’t the same.

Of course, true hit timing aside, if we only compare replays between stable and lazer where we only see the ints, “ceiled&floored” would match stable in the case of osu!mania, but not for osu! (as in, you can be off by a whole millisecond in lazer but not in stable). Same for “floored&ceiled”.

In short:

  • In stable with <, the max hit error can be up to 1.5 ms smaller
  • In stable with <=, the max hit error can be up to 0.5 ms smaller or larger
  • In lazer, hit windows are accurate during gameplay because everything uses doubles (however, due to rounding in replays the judgement may change)
  • For replays in lazer (where frame times are rounded integers), max hit errors are accurate to stable wherever <= is used, but larger by 1 ms compared to stable wherever < is used. (Assuming the hit windows have the same value in stable and lazer, of course.) The max hit error can be up to 0.5 ms smaller or larger

I haven’t been able to verify this in-game though as I don’t have the tooling or time to do so. Maybe there’s a relevant test scene in lazer, or if not maybe one of those could be cooked up (comparing against stable rounding/truncation)?

LegacyBeatmapEncoder applies a rounding operation to replay frames:

Rounding the hit time for replays should be correct, but I’m not entirely sure what that implies. Are these rounded hit times used for replay playback of plays set on lazer/stable or only for .osr exports?

I believe osu!taiko also uses <= for the normal hit (strongs are still <).

I haven’t been able to find anything of the sort in the stable reference code.

In stable, taiko misses are given here, the comparison for which (<=) can be seen here and here (variable name for the “miss window” is a bit misleading). Greats and Ok’s are compared with <. There’s no difference between regular and large taiko notes from what I can tell.

You’d want to ceil early hits.

Both stable and lazer use absolute hit error, so it’s always comparing two positive values. There’s no special case to consider here, especially since hit times are rounded in stable anyway.

https://github.com/ppy/osu/blob/d38316bf4f552629cf86dc5ccfe78c3f2fd95f27/osu.Game/Rulesets/Scoring/HitWindows.cs#L127-L138

Where timeOffset is the hit error (positive or negative, but made positive above). This is the same in stable (example).

0reactions
Walavoucheycommented, Jan 30, 2023

Suppose you have a hitobject at 500ms. The user hits a circle at -59.6ms delta lazer-time (i.e. 440.4ms). Should this value be rounded up or down for storage in the replay. Conversely, suppose the user hits at +59.6ms, should this value be rounded up or down?

Rounding the value emulates the rounded-integer temporal granularity of stable. With that, the only difference for replays in lazer (with integer time values) is that they’re still compared against doubles, non-floored hit windows (as well as the < vs <= thing). Using ceil and floor (instead of round) would only cause yet another difference between stable and lazer.

This affects the lazer part of the examples I presented I suppose. I’ve updated them with lazer replay timings and also made it all into a table.

even though there’s still up to 1ms discrepancy it will always be the correct judgement.

Well I thought through the implications of this… (see updated examples section) but I can’t convince myself that this would solve anything.

  • Are you supposed to decide whether to floor or ceil a replay frame depending on proximity to (early-side or late-side of) a hit object?
  • You’re essentially making hit windows more lenient, but according to the example in OP, it’s already too lenient (expected Meh, got Ok). On top of that, a same exact (sub-millisecond) input would be able to give different replay files on lazer compared to stable.
  • Even if you you do this, it doesn’t make stable-set replays show correct judgements in lazer.

    In Stable: +59ms => Ok, +60ms => Meh In Lazer: +59ms => Ok, +60ms => Ok which is incorrect.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Gameplay differences from osu!stable
Hit window edge calculations don't match stable. Stable uses a mix of comparators for hitwindows, whereas lazer uses one.
Read more >
Novinky – Steam Community Announcements
Fixed very old legacy beatmaps (< v8) sometimes generating mismatched slider ticks (compared to stable) because of different tickDistance algorithm
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found