Performance and the guards
Skills call skills, so a runaway tree is three config lines away. Four guards exist because of that, and all four are load-bearing.
limits:
max-depth: 32
max-mechanics-per-execution: 4000
max-targets: 64
tick-budget-ms: 5.0
max-depthnumberdefault32How deep skill calls may nest. A skill that calls itself is legal and useful; unbounded it is a stack overflow.
max-mechanics-per-executionnumberdefault4000Total mechanics one execution may run. A
repeatinside arepeatover a 64-target targeter is a frozen server.max-targetsnumberdefault64The hard cap on every targeter, applied after its own
limit. A third-party targeter cannot forget it, because the engine applies it rather than the targeter.tick-budget-msnumberdefault5.0Wall clock per tick. A tree that exceeds it is suspended and resumed next tick, not truncated.
Why the budget lives on the execution
The guards have to see the whole tree. A repeat inside a repeat is only a problem in aggregate, so the counter cannot live in a node. It lives on the execution and is charged on the one path every mechanic goes through.
That also means no mechanic has to remember to call it. The guards were written before the executor rather than bolted onto a working one, which is why this holds for third-party mechanics too.
Why suspension is possible at all
Skill trees are walked over an explicit frame stack, not by recursion.
With a recursive walk the wall-clock guard could only ever abort, since there is no way to suspend a Java call stack and resume it next tick. With an explicit stack, suspension is just "stop looping and reschedule", and it works identically at depth 1 and depth 30.
delay and the sync/async hops fall out of the same mechanism for free.
What exceeding one looks like
Exceeding any guard aborts that execution and logs once per skill per minute, naming the skill id and the offending node path:
[warn] skill 'aether:meteor_storm' exceeded max-mechanics-per-execution (4000)
at skills[2] -> repeat -> skills[0]
Polling
A mob runs no task at all unless its definition declares a polled trigger: ~onTimer, ~onTick, ~onPlayerNear or ~onPlayerLeave.
When it does, that task's period is the GCD of the declared intervals, decided at compile time rather than at runtime. A mob with one ~onTimer:160 costs one task every 160 ticks, not a per-tick task checking a counter.
Spawning and particles
performance:
particles-per-player-per-tick: 400
particle-view-distance: 48
spawner-scan-period-ticks: 40
random-spawn-budget-per-world: 40
particles-per-player-per-ticknumberdefault400Particles are emitted per player, so this genuinely applies rather than being a global figure nobody hits.
particle-view-distancenumberdefault48Beyond this, a player is not sent the particles at all.
spawner-scan-period-ticksnumberdefault40Minimum 20.
random-spawn-budget-per-worldnumberdefault40The server-wide ceiling on random spawn rules. A filter written slightly too wide is otherwise indistinguishable from a denial of service.
Anchors have their own scan period and activation range. See Spawning.
The design ceiling
50 concurrent mobs with active skill trees, under 2 ms/tick total.
That is the figure the guard defaults are set around. A server well past it is not necessarily in trouble, but it is past what was measured.
Folia
Nothing uses BukkitRunnable or Bukkit.getScheduler(). Every task goes through the scheduler at the owning entity or region, threat tables and cooldowns are on concurrent maps, and the content registry is an immutable snapshot swapped behind a volatile field.
That is what folia-supported: true rests on, rather than it being a declaration made in hope.
A delayed skill resumes on the correct region thread, and one whose caster died during the pause is dropped rather than resumed against a stale entity.
Measuring
/bestiary debug <mob>
Includes a timing mode: per-skill wall clock, so the expensive thing is identifiable rather than guessed at.
/bestiary platform reports how much content is loaded and how many mobs are live.
Change one limit at a time and reproduce the same traced cast. A larger tick budget permits more synchronous work per tick, while larger mechanic or target caps permit more total work; they solve different guard failures. Keep the first throttled warning and timing trace for comparison.