Anuken/Mindustry · warning · ValidateException

Player attempted to control invalid unit.

Error message

Player attempted to control invalid unit.

What it means

Thrown server-side as ValidateException in the else-branch of unitControl: the requested unit was not a valid possession target (not AI, dead, or wrong team). Even though the earlier possession/admin checks passed, the specific unit failed the runtime eligibility test (unit.isAI() && team match && !dead && playerControllable). This catches clients attempting to control units they should not.

Source

Thrown at core/src/mindustry/input/InputHandler.java:814

                if(before.spawnedByCore){
                    unit.dockedType = before.type;
                }else if(before.dockedType != null && before.dockedType.coreUnitDock){
                    //direct dock transfer???
                    unit.dockedType = before.dockedType;
                }

                if(before.spawnedByCore && !before.isPlayer()){
                    Call.unitDespawn(before);
                }
            }

            Time.run(Fx.unitSpirit.lifetime, () -> Fx.unitControl.at(unit.x, unit.y, 0f, unit));
            if(!player.dead()){
                Fx.unitSpirit.at(player.x, player.y, 0f, unit);
            }
        }else if(net.server()){
            //reject forwarding the packet if the unit was dead, AI or team
            throw new ValidateException(player, "Player attempted to control invalid unit.");
        }

        Events.fire(new UnitControlEvent(player, unit));
    }

    @Remote(targets = Loc.both, called = Loc.server, forward = true)
    public static void unitClear(Player player){
        if(player == null) return;

        //make sure player is allowed to control the building
        if(net.server() && !netServer.admins.allowAction(player, ActionType.respawn, action -> {})){
            throw new ValidateException(player, "Player cannot respawn.");
        }

        if(!player.dead() && !player.unit().spawnedByCore){
            var docked = player.unit().dockedType;

            //get best core unit type as approximation

View on GitHub (pinned to f695ad7e60)

Solutions

  1. Server: treat ValidateException here as expected anti-cheat rejection; log and continue, do not crash.
  2. Ensure modded units correctly implement isAI()/playerControllable()/team semantics.
  3. Clients should only attempt control of valid AI allied units.
  4. If race conditions cause false positives, re-validate unit liveness client-side before requesting.

Example fix

// before: client requests any unit
Call.unitControl(player, unit);

// after: client validates target
if(unit.isAI() && !unit.dead && unit.team == player.team() && unit.playerControllable()){
    Call.unitControl(player, unit);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// client validates the target unit before requesting control
if(unit == null || !unit.isAI() || unit.dead || unit.team != player.team() || !unit.playerControllable()){
    return; // server would reject this
}

Try / catch

try{ Call.unitControl(player, unit); }catch(mindustry.net.ValidateException e){ Log.debug("rejected invalid unit control from @", e.player); }

Prevention

When it happens

Trigger: Client sends unitControl for a unit that is dead, not AI-controlled, on a different team, or not player-controllable. The server reaches the final else and throws. Only on net.server().

Common situations: Malicious client forging a control packet for an enemy/dead/non-AI unit; race where the unit died or changed team between request and validation; modded units that incorrectly report playerControllable.

Related errors


AI-assisted analysis of Anuken/Mindustry@f695ad7e60 (2026-08-14). Data as JSON: /api/errors/064dd1883c064bcf. Report an issue: GitHub.