ppy/osu · error · InvalidOperationException
HitObjectContainer should be set before CheckHittable is cal
Error message
HitObjectContainer should be set before CheckHittable is called.
What it means
StartTimeOrderedHitPolicy.CheckHittable() requires HitObjectContainer to enumerate hit objects up to the target's start time and determine if a blocking object precedes it. The policy must be initialized with HitObjectContainer before hit-checking. This mirrors the LegacyHitPolicy invariant but in a different policy implementation.
Source
Thrown at osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs:29
namespace osu.Game.Rulesets.Osu.UI
{
/// <summary>
/// Ensures that <see cref="HitObject"/>s are hit in-order of their start times. Affectionately known as "note lock".
/// If a <see cref="HitObject"/> is hit out of order:
/// <list type="number">
/// <item><description>The hit is blocked if it occurred earlier than the previous <see cref="HitObject"/>'s start time.</description></item>
/// <item><description>The hit causes all previous <see cref="HitObject"/>s to missed otherwise.</description></item>
/// </list>
/// </summary>
public class StartTimeOrderedHitPolicy : IHitPolicy
{
public IHitObjectContainer? HitObjectContainer { get; set; }
public ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult result)
{
if (HitObjectContainer == null)
throw new InvalidOperationException($"{nameof(HitObjectContainer)} should be set before {nameof(CheckHittable)} is called.");
DrawableHitObject? blockingObject = null;
foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
{
if (hitObjectCanBlockFutureHits(obj))
blockingObject = obj;
}
if (blockingObject != null)
{
// A hit is disallowed if:
// 1. The last blocking hitobject has not yet been judged.
// 2. The current time is before the last hitobject's start time.
//
// Hits at exactly the same time as the blocking hitobject are allowed for maps that contain simultaneous hitobjects (e.g. /b/372245).
if (!blockingObject.Judged && time < blockingObject.HitObject.StartTime)
return ClickAction.Shake;
View on GitHub (pinned to d9c73e12ad)
Solutions
- Set HitObjectContainer on the policy before gameplay starts.
- Ensure the policy initialization happens in the playfield's load step, consistently for all policy implementations.
Example fix
// before var policy = new StartTimeOrderedHitPolicy(); policy.CheckHittable(hitObject, time, result); // HitObjectContainer is null // after var policy = new StartTimeOrderedHitPolicy(); policy.HitObjectContainer = HitObjectContainer; policy.CheckHittable(hitObject, time, result);
Defensive patterns
Strategy: validation
Validate before calling
// Ensure HitObjectContainer is set before gameplay
if (policy.HitObjectContainer == null)
throw new InvalidOperationException("HitObjectContainer must be set before gameplay.");
policy.HitObjectContainer = HitObjectContainer; Prevention
- Initialize all IHitPolicy instances with HitObjectContainer during Playfield.LoadComplete.
- Apply the same initialization pattern across all policy implementations to ensure consistency.
- Consider making HitObjectContainer a constructor parameter to enforce initialization at creation time.
When it happens
Trigger: Calling CheckHittable before setting HitObjectContainer on the StartTimeOrderedHitPolicy instance.
Common situations: Switching from LegacyHitPolicy to StartTimeOrderedHitPolicy without ensuring the same initialization is performed; test setup omission; ruleset configuration that creates a policy lazily but queries it eagerly.
Related errors
- HitObjectContainer should be set before CheckHittable is cal
- HitObjectContainer should be set before HandleHit is called.
- A {hitObject} was hit before it became hittable!
- 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/d4cf4c700a936d4c.
Report an issue: GitHub.