Ensure hitobjects are judged in order of start time#633
Conversation
There was a problem hiding this comment.
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
Judgementpooled value class describing the three deferrable judgement kinds (CIRCLE_HIT,SLIDER_HEAD,REGISTER_ACCURACY). - In
GameScene, trackcurrentUpdatingObject, maintain a start-time-orderedpendingJudgementslist, deferonCircleHit/slider head/registerAccuracywhen a preceding active object is incomplete, and flush after each update pass. - Added
GameObject.isCompleted()(overridden inGameplaySlider/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.
Pending discussion, but the fix will likely revolve around something like this.
Consider a timeline with two sliders as follows:
Suppose that the player is currently at
ncombo. IfS2's head hit window extends beyondS1's tail, remove slider lock is enabled, andS2's head was successfully hit beforeS1ends, the following will happen chronologically:S2's head, so now they are atn + 1comboS1ends, its tail is judged with the player combo being atn + 1comboS1's tail is judged as if the player is atn + 1comboThis is a problem, because the other scenario is that
S1's tail is judged first (atncombo) beforeS2's head (atn + 1combo). This means that the score achieved depends on whetherS2'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 theirstartTime.This PR solves that problem by deferring
S2's head judgement untilS1's tail is judged. The same goes for a slider-circle timeline like this, where the circle appears in the middle of a slider:In this timeline, the judgement order is as follows:
S1 head --> S1 first tick --> S1 second tick --> S1 third tick --> S1 tail --> C1The 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.