ppy/osu · error · InvalidOperationException

HitObjectContainer should be set before HandleHit is called.

Error message

HitObjectContainer should be set before HandleHit is called.

What it means

StartTimeOrderedHitPolicy.HandleHit() requires HitObjectContainer to enumerate and miss all hit objects prior to the hit one. The policy must be initialized with HitObjectContainer before any hit is handled. The null check guards the enumeration inside HandleHit.

Source

Thrown at osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs:60

                // 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;
            }

            // 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();
            }
        }

View on GitHub (pinned to d9c73e12ad)

Solutions

  1. Set HitObjectContainer on the policy before any hits can be processed.
  2. Ensure the playfield assigns HitObjectContainer to the policy during LoadComplete, before hit objects become interactive.

Example fix

// before
var policy = new StartTimeOrderedHitPolicy();
policy.HandleHit(hitObject); // HitObjectContainer is null

// after
var policy = new StartTimeOrderedHitPolicy();
policy.HitObjectContainer = HitObjectContainer;
policy.HandleHit(hitObject);
Defensive patterns

Strategy: validation

Validate before calling

// Ensure HitObjectContainer is set before any hit can be handled
if (policy.HitObjectContainer == null)
    throw new InvalidOperationException("HitObjectContainer must be set before gameplay.");
policy.HitObjectContainer = HitObjectContainer;

Prevention

When it happens

Trigger: Calling HandleHit(hitObject) before setting HitObjectContainer on the StartTimeOrderedHitPolicy instance.

Common situations: Same as [13] — policy created without wiring the container; a custom DrawableHitObject that calls HandleHit during load before the playfield finishes setup.

Related errors


AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13). Data as JSON: /api/errors/13d95b3572338757. Report an issue: GitHub.