Skip to content

Ensure hitobjects are judged in order of start time#633

Open
Rian8337 wants to merge 6 commits into
osudroid:masterfrom
Rian8337:start-time-ordered-judgement
Open

Ensure hitobjects are judged in order of start time#633
Rian8337 wants to merge 6 commits into
osudroid:masterfrom
Rian8337:start-time-ordered-judgement

Conversation

@Rian8337

Copy link
Copy Markdown
Member

Pending discussion, but the fix will likely revolve around something like this.


Consider a timeline with two sliders as follows:

<======>   <======>
   S1         S2

Suppose that the player is currently at n combo. If S2's head hit window extends beyond S1's tail, remove slider lock is enabled, and S2's head was successfully hit before S1 ends, the following will happen chronologically:

  1. The player gains combo from hitting S2's head, so now they are at n + 1 combo
  2. 30 score is added, which is fine since this score is independent of combo
  3. When S1 ends, its tail is judged with the player combo being at n + 1 combo
  4. S1's tail is judged as if the player is at n + 1 combo

This is a problem, because the other scenario is that S1's tail is judged first (at n combo) before S2's head (at n + 1 combo). This means that the score achieved depends on whether S2's head was hit early or late, which is inconsistent. For what it's worth, this is also an issue both in osu!stable and osu!lazer. In all cases, except nested hitobjects, hitobjects must be judged in order of their startTime.

This PR solves that problem by deferring S2's head judgement until S1's tail is judged. The same goes for a slider-circle timeline like this, where the circle appears in the middle of a slider:

<======o======o======o=>
           o
           ^
 C1 in the middle of S1

In this timeline, the judgement order is as follows:
S1 head --> S1 first tick --> S1 second tick --> S1 third tick --> S1 tail --> C1

The drawback of this is that players will not see their score, combo, and accuracy being updated visually since again, C1's judgement would be deferred. However, C1s hit effect would appear at the time it is hit.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enforces that hitobject judgements are processed in start-time order when remove-slider-lock is enabled. Without this, a later object hit during a still-running earlier slider would advance combo before the earlier slider's tail was scored, producing inconsistent scoring. The fix introduces a pooled Judgement queue inside GameScene that defers later objects' judgements (circle hits, slider heads, accuracy registrations) until all preceding active objects have completed, and adds a new isCompleted() method on GameObject/GameplaySlider/GameplaySpinner to distinguish "head hit" from "fully finalized".

Changes:

  • Added Judgement pooled value class describing the three deferrable judgement kinds (CIRCLE_HIT, SLIDER_HEAD, REGISTER_ACCURACY).
  • In GameScene, track currentUpdatingObject, maintain a start-time-ordered pendingJudgements list, defer onCircleHit/slider head/registerAccuracy when a preceding active object is incomplete, and flush after each update pass.
  • Added GameObject.isCompleted() (overridden in GameplaySlider/GameplaySpinner) so the deferral logic can distinguish "judged" from "fully concluded".

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/com/osudroid/game/Judgement.kt New pooled Judgement record carrying all state needed to replay a deferred judgement.
src/ru/nsu/ccfit/zuev/osu/game/GameScene.java Adds the deferred-judgement queue, enqueue/flush logic, and integrates it into onCircleHit, onSliderHit (SLIDER_START), and registerAccuracy.
src/ru/nsu/ccfit/zuev/osu/game/GameObject.java Adds isCompleted() defaulting to isJudged(), plus required imports.
src/ru/nsu/ccfit/zuev/osu/game/GameplaySlider.java Overrides isCompleted() to return isOver, separating it from the early-isJudged semantics.
src/ru/nsu/ccfit/zuev/osu/game/GameplaySpinner.java Overrides isCompleted() to return passedTime >= duration and updates isJudged() accordingly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/ru/nsu/ccfit/zuev/osu/game/GameScene.java
@Rian8337 Rian8337 added the release-stream:main Issues/pull requests that targets the main release stream. label Jun 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-stream:main Issues/pull requests that targets the main release stream. size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants