ppy/osu · critical · InvalidOperationException
A {hitObject} was hit before it became hittable!
Error message
A {hitObject} was hit before it became hittable! What it means
After a hit is registered, StartTimeOrderedHitPolicy.HandleHit() re-checks via CheckHittable to confirm the object was actually hittable at the hit time (startTime + TimeOffset). If CheckHittable returns anything other than ClickAction.Hit, the game's internal invariant is violated: an object was processed as a hit when the policy says it should not have been. This indicates a logic error in the hit pipeline, not a user mistake.
Source
Thrown at osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs:67
// Generally when the user has hit way too early.
if (result == HitResult.None)
return ClickAction.Shake;
return ClickAction.Hit;
}
public void HandleHit(DrawableHitObject hitObject)
{
if (HitObjectContainer == null)
throw new InvalidOperationException($"{nameof(HitObjectContainer)} should be set before {nameof(HandleHit)} is called.");
// Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks, spinners).
if (!hitObjectCanBlockFutureHits(hitObject))
return;
if (CheckHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset, hitObject.Result.Type) != ClickAction.Hit)
throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!");
// Miss all hitobjects prior to the hit one.
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
{
if (obj.Judged)
continue;
if (hitObjectCanBlockFutureHits(obj))
((DrawableOsuHitObject)obj).MissForcefully();
}
}
/// <summary>
/// Whether a <see cref="HitObject"/> blocks hits on future <see cref="HitObject"/>s until its start time is reached.
/// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> to test.</param>
private static bool hitObjectCanBlockFutureHits(DrawableHitObject hitObject)
=> hitObject is DrawableHitCircle;
View on GitHub (pinned to d9c73e12ad)
Solutions
- Ensure the DrawableHitObject's input handling calls CheckHittable before processing the hit — if it returns non-Hit, do not register the hit.
- Verify that no mod or custom DrawableHitObject bypasses the hit policy check before calling HandleHit.
- Ensure HitObjectContainer ordering matches the start-time ordering expected by StartTimeOrderedHitPolicy.
- If this fires in a custom ruleset, audit the entire hit-registration path from input event to HandleHit.
Example fix
// before — HandleHit called without prior CheckHittable check
if (result.IsHit)
policy.HandleHit(this);
// after — verify hittable before handling
if (result.IsHit && policy.CheckHittable(this, Time.Current, result.Type) == ClickAction.Hit)
policy.HandleHit(this); Defensive patterns
Strategy: validation
Validate before calling
// Verify the object is hittable before registering the hit
var clickAction = policy.CheckHittable(hitObject, hitObject.HitObject.StartTime + result.TimeOffset, result.Type);
if (clickAction != ClickAction.Hit)
return; // do not process the hit
policy.HandleHit(hitObject); Try / catch
// Wrap HandleHit to catch the invariant violation during development
try
{
policy.HandleHit(hitObject);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("hit before it became hittable"))
{
// Log the full hit pipeline state for debugging
Logger.Log($"Hit invariant violated for {hitObject}. Check hittability before registering hits.", LoggingTarget.Runtime, LogLevel.Error);
throw;
} Prevention
- Always call CheckHittable before registering a hit in custom DrawableHitObject subclasses.
- Ensure HitObjectContainer ordering is consistent with StartTime ordering when using StartTimeOrderedHitPolicy.
- Audit the full hit-registration path from input event to HandleHit when adding mods or custom hit objects.
- Add integration tests that simulate early hits and verify the policy correctly blocks them at the input level.
When it happens
Trigger: A DrawableHitObject registers a hit judgement and calls HandleHit, but CheckHittable determines the object was not in a hittable state — e.g., a blocking object precedes it, or the hit time is before the object becomes hittable. This happens when the input pipeline processes a hit without first consulting the policy.
Common situations: Custom ruleset modifications to the hit pipeline that bypass the hit-policy check; mods that alter timing windows or hit ordering in ways inconsistent with the policy; multiplayer race conditions where hit times differ between clients; custom DrawableHitObject subclasses that call OnHit directly.
Related errors
- HitObjectContainer should be set before CheckHittable is cal
- HitObjectContainer should be set before CheckHittable is cal
- HitObjectContainer should be set before HandleHit is called.
- Can't have zero or fewer stages.
- Unsupported grid type.
AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13).
Data as JSON: /api/errors/3bcd034ced6badc7.
Report an issue: GitHub.