Understanding the Tick and Render Budgets
Minecraft Bedrock runs its game simulation at 20 ticks per second, giving the server thread a strict budget of 50 milliseconds per tick (50 MSPT). Everything inside your Behavior Pack—entity AI goals, block ticks, loot table evaluations, functions, and Script API callbacks—must finish inside that 50ms window alongside vanilla game logic. Once total MSPT crosses 50, the game slows down noticeably: mobs move in bursts, chests open late, and eating food takes longer than normal.
The Resource Pack runs on the client thread and the GPU, where the target is 60 frames per second (about 16.6 milliseconds per frame) or higher, while staying inside strict system memory ceilings. Because Bedrock runs on everything from desktop PCs to older phones and consoles, a pack that feels smooth on a developer workstation can easily freeze a mobile device or Nintendo Switch if texture atlases or entity geometries are bloated.
When profiling an add-on, always separate simulation lag from rendering lag. If looking away from a group of custom mobs restores your frame rate immediately, your bottleneck is in the Resource Pack (geometry, animations, Molang, or particles). If looking away does nothing and blocks still take two seconds to drop items after being broken, your bottleneck is in the Behavior Pack.
| System | Pack Type | Primary Metric | Typical Culprits |
|---|---|---|---|
| Server Thread | Behavior Pack | MSPT (Target < 50ms) | Pathfinding, nearest-attackable-target scans, unthrottled scripts, command loops |
| Client CPU | Resource Pack | Frame time (Target < 16.6ms) | Complex client Molang queries, bone animations on hundreds of entities, high particle counts |
| Client RAM / VRAM | Resource Pack | Memory footprint | Oversized PNG textures, duplicate textures across models, high-resolution block atlases |
Entity Behavior and AI Costs
Entities are almost always the heaviest part of a Behavior Pack. Even a stationary custom entity costs tick time if it carries collision boxes, physics calculations, and active AI goals. Of all standard entity components, pathfinding (`minecraft:navigation.walk` and its siblings) and target acquisition (`minecraft:behavior.nearest_attackable_target`) are the most expensive because they inspect surrounding blocks and entities through spatial queries.
When configuring target selection on a common mob, keep `within_radius` as small as your design allows and raise `scan_interval` so the entity is not scanning every single tick. If an entity has `must_see` enabled, the engine must perform raycasts against world geometry during scans; disabling `must_see` or shrinking the radius cuts the cost substantially when dozens of instances are loaded.
For decorative entities, projectile markers, or furniture that never need to walk or push other mobs, strip out navigation and movement components entirely and configure `minecraft:pushable` to disable piston and entity pushing. Reducing the frequency of collision checks on non-combat entities is one of the fastest ways to reclaim server tick time.
{
"format_version": "1.20.80",
"minecraft:entity": {
"description": {
"identifier": "wiki:custom_guard",
"is_spawnable": true,
"is_summonable": true
},
"components": {
"minecraft:physics": {
"has_gravity": true,
"has_collision": true
},
"minecraft:pushable": {
"is_pushable": false,
"is_pushable_by_piston": true
},
"minecraft:navigation.walk": {
"can_path_over_water": false,
"avoid_damage_blocks": true
},
"minecraft:behavior.nearest_attackable_target": {
"priority": 2,
"must_see": true,
"reselect_targets": true,
"within_radius": 12.0,
"scan_interval": 20,
"entity_types": [
{
"filters": {
"test": "is_family",
"subject": "other",
"value": "monster"
},
"max_dist": 12
}
]
}
}
}
}Script API Over Tick Functions
Historically, creators used `tick.json` to run `.mcfunction` files 20 times per second. That pattern is very expensive: every command line is parsed and executed every tick, and target selectors like `@e` with distance or tag arguments walk the entity list repeatedly. Moving periodic logic into `@minecraft/server` scripts and shifting from polling to event listeners eliminates most of that overhead.
Whenever the Script API offers an `*AfterEvent` or `*BeforeEvent` (such as `world.afterEvents.entityHurt` or `world.afterEvents.playerBreakBlock`), subscribe to that event instead of running a loop that inspects players every tick. Furthermore, many world events accept an event options object that filters which entities or blocks trigger the callback inside the native C++ engine before JavaScript is even invoked.
When you genuinely need a continuous loop, pass a tick interval greater than 1 to `system.runInterval()`, and always pass an `EntityQueryOptions` object to `dimension.getEntities()` so the engine narrows the search by `type`, `families`, or `maxDistance` natively rather than returning an array of every loaded entity in the dimension for your script to filter in JavaScript.
import { world, system } from "@minecraft/server";
// Run once every 2 seconds (40 ticks) instead of every tick
system.runInterval(() => {
const overworld = world.getDimension("overworld");
for (const player of world.getAllPlayers()) {
// Filter natively in C++ via EntityQueryOptions before JS array allocation
const nearbyGuards = overworld.getEntities({
type: "wiki:custom_guard",
location: player.location,
maxDistance: 24
});
for (const guard of nearbyGuards) {
guard.addEffect("regeneration", 60, { amplifier: 0, showParticles: false });
}
}
}, 40);
// Use native event filtering so the callback only fires for our custom entity
world.afterEvents.entityDie.subscribe(
(event) => {
const deadEntity = event.deadEntity;
deadEntity.dimension.spawnParticle("minecraft:huge_explosion_emitter", deadEntity.location);
},
{
entityTypes: ["wiki:custom_guard"]
}
);Custom Blocks, Geometry, and Tick Frequency
Custom blocks affect both the server and the client. On the server side, attaching `minecraft:tick` to a widespread block—such as a custom grass or ore generated across chunks—can quickly overwhelm the tick budget if the interval range is tight. Reserve `minecraft:tick` for blocks players place in small numbers (like machines or crops), or push the `interval_range` to higher values when rapid updates are not essential.
On the client side, custom block geometry (`minecraft:geometry`) bypasses some of the optimizations used by standard unit cubes (`minecraft:unit_cube`). A block with complex custom geometry and non-opaque `minecraft:material_instances` (`render_method` set to `alpha_test` or `blend`) does not cull the faces of adjacent blocks, forcing the GPU to render hidden block faces underneath and behind it.
If a player builds a floor or wall out of hundreds of custom-geometry blocks with `blend` or `alpha_test`, frame rates drop sharply on mobile devices. Use standard cube geometry whenever a block is a full cube, use `opaque` as the `render_method` whenever transparency is not needed, and keep element counts in custom block models as low as possible.
{
"format_version": "1.20.80",
"minecraft:block": {
"description": {
"identifier": "wiki:slow_machine"
},
"components": {
"minecraft:destructible_by_mining": {
"seconds_to_destroy": 2.0
},
"minecraft:geometry": "minecraft:geometry.full_block",
"minecraft:material_instances": {
"*": {
"texture": "wiki_slow_machine",
"render_method": "opaque"
}
},
"minecraft:tick": {
"interval_range": [80, 120],
"looping": true
}
}
}
}Client Rendering: Molang, Animations, and Textures
Client entity files (`RP/entity/*.json`), animation controllers, and animations run every rendered frame for every visible entity. To prevent off-screen entities from burning CPU time on bone transforms and Molang math, always include `scripts.should_update_bones_and_effects_offscreen` and `scripts.should_update_effects_offscreen` only when strictly required—leaving them at their default (not updating offscreen) lets the engine skip animation evaluation when the entity is behind the camera.
Inside `scripts.pre_animation`, compute expensive Molang expressions once per frame and store the result in a `variable.*` or `temp.*` rather than repeating the same `query.*` and trigonometric math across twenty individual bone channels in your animation file. Use `temp.*` variables whenever the value does not need to persist to the next frame, as temporary variables avoid storage overhead on the entity instance.
Finally, watch your texture dimensions carefully. Bedrock stitches block and item textures into unified texture atlases in video memory. Mixing a few 512x512 or 1024x1024 block textures into a pack of 16x16 textures forces the entire atlas to scale up, multiplying memory usage across the whole sheet and causing low-memory devices to drop textures or crash to the home screen.
{
"format_version": "1.10.0",
"minecraft:client_entity": {
"description": {
"identifier": "wiki:custom_guard",
"materials": {
"default": "entity_alphatest"
},
"textures": {
"default": "textures/entity/custom_guard"
},
"geometry": {
"default": "geometry.custom_guard"
},
"scripts": {
"pre_animation": [
"variable.move_speed = query.ground_speed * 0.5;"
],
"animate": [
"walk_controller"
]
},
"animations": {
"walk_controller": "controller.animation.custom_guard.walk",
"walk": "animation.custom_guard.walk"
},
"render_controllers": [
"controller.render.default"
]
}
}
}Which versions this applies to
Applies to stable Minecraft Bedrock 1.20.80 and 1.21.x. None of the components, Molang features, or Script API methods shown here require experimental toggles.
When it does not work
| What you see | Why | The fix |
|---|---|---|
| Console shows: [Scripting][error]-Watchdog: Hang detected in pack ... or scripts terminate unexpectedly during heavy loops. | Bedrock enforces a scripting watchdog timer. If a single synchronous script execution (such as spawning thousands of blocks or iterating over huge arrays in one tick) takes too long, the engine halts the script pack to protect the server from freezing. | Break large synchronous workloads across multiple ticks using system.runJob() with a generator function (function*), yielding periodically so the watchdog budget is never exceeded in a single tick. |
| Blocks render with pink-and-black missing textures or the game closes on mobile and consoles when loading the world. | One or more block or entity textures use oversized resolutions (such as 1024x1024 or 2048x2048), causing the stitched terrain texture atlas to exceed the device's available VRAM or RAM. | Downscale block and item textures to 16x16 or 32x32, and keep entity textures to powers of two (such as 64x64 or 128x128) appropriate to the pixel density of the model. |
| Server simulation slows down severely over time, even when players stand still in a base. | Projectile markers, invisible helper entities, or custom ambient mobs are being spawned without a despawn component, accumulating in loaded chunks and running collision and physics checks every tick. | Add minecraft:despawn (or trigger an instant despawn event via minecraft:timer) to temporary and ambient entities so they clean themselves up when players leave or after their task completes. |
Reference, straight from the game files
Read out of the game itself, so the ids match the version you are building for.
