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

LegacyHitPolicy.CheckHittable() requires HitObjectContainer to enumerate alive objects and determine click ordering for stacked hit objects. The policy must be initialized with HitObjectContainer before any gameplay hit-checking occurs. This is a setup invariant enforced at call time.

Source

Thrown at osu.Game.Rulesets.Osu/UI/LegacyHitPolicy.cs:39

    public class LegacyHitPolicy : IHitPolicy
    {
        public IHitObjectContainer? HitObjectContainer { get; set; }

        private readonly double hittableRange;

        public LegacyHitPolicy(double hittableRange = OsuHitWindows.MISS_WINDOW)
        {
            this.hittableRange = hittableRange;
        }

        public void HandleHit(DrawableHitObject hitObject)
        {
        }

        public virtual ClickAction CheckHittable(DrawableHitObject hitObject, double time, HitResult result)
        {
            if (HitObjectContainer == null)
                throw new InvalidOperationException($"{nameof(HitObjectContainer)} should be set before {nameof(CheckHittable)} is called.");

            var aliveObjects = HitObjectContainer.AliveObjects.ToList();
            int index = aliveObjects.IndexOf(hitObject);

            if (index > 0)
            {
                var previousHitObject = (DrawableOsuHitObject)aliveObjects[index - 1];
                if (previousHitObject.HitObject.StackHeight > 0 && !previousHitObject.AllJudged)
                    return ClickAction.Ignore;
            }

            if (result == HitResult.None)
                return ClickAction.Shake;

            foreach (DrawableHitObject testObject in aliveObjects)
            {
                if (testObject.AllJudged)
                    continue;

View on GitHub (pinned to d9c73e12ad)

Solutions

  1. Set HitObjectContainer on the policy before gameplay starts: policy.HitObjectContainer = HitObjectContainer;.
  2. Ensure the policy is assigned and initialized during the Playfield's LoadComplete or load step, before any hit objects are processed.

Example fix

// before
var policy = new LegacyHitPolicy();
policy.CheckHittable(hitObject, time, result); // HitObjectContainer is null

// after
var policy = new LegacyHitPolicy();
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

When it happens

Trigger: Calling CheckHittable(hitObject, time, result) before setting policy.HitObjectContainer = container. The null check throws immediately.

Common situations: Custom ruleset integration that creates a LegacyHitPolicy but forgets to wire HitObjectContainer during playfield load; test setup that constructs a policy without initializing the container; lifecycle ordering where the policy is queried before the playfield finishes loading.

Related errors


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