Travel
Send the player across terrain and let TeaKit choose how to get there.
player.travelTo moves the player to a destination the way a player would: walking, sprinting, jumping, crouching, crawling, climbing, swimming, diving, riding boats, horses, camels, and minecarts, gliding with an elytra, dropping onto a water bucket, and stepping through portals. It picks a route through the loaded terrain and switches between those modes as the route needs.
Use it when your mod changes how players get around, or when a test needs the player somewhere that is awkward to reach. For a short walk to a block, walkTo on the players page is enough.
Travel needs Minecraft 1.21.11 or newer and a client session. The player.driver capability covers it.
Travel to a destination
This test builds a pool across the player's path and checks that they swam across it:
import { Capability, Readiness, describe, expect, pos, test } from "@teakit/test";
describe("travel", () => {
describe.configure({
target: { minecraft: ">=1.21.11" },
readiness: [Readiness.World, Readiness.Player],
capabilities: [Capability.PlayerDriver, Capability.ClientRenderProbes],
timeout: "2m",
});
test("swims across a pool to reach the far side", async ({ player, world, render }) => {
const start = pos(0, 100, 0);
const destination = start.offset(0, 0, 20);
// A stone floor with a pool across its full width.
await world.fill(start.offset(-3, -4, -1), start.offset(3, -1, 21), "minecraft:stone");
await world.clear(start.offset(-3, 0, -1), start.offset(3, 4, 21));
await world.fill(start.offset(-3, -3, 6), start.offset(3, -1, 12), "minecraft:water");
// Routes are planned from the client's copy of the world.
await expect(async () => (await render.block(start.offset(3, -1, 12)).inspect()).id)
.toEventuallyEqual("minecraft:water", { timeout: "5s" });
await player.reset({ gameMode: "survival", health: 20, food: 20, effects: "clear", inventory: "clear" });
await player.teleport({ x: 0.5, y: 100, z: 0.5 });
const trip = player.travelTo(destination, { wait: false });
const result = await trip.wait({ timeout: "60s" });
expect(result.status).toBe("succeeded");
expect(await player.position()).toBeNear(destination, { distance: 1 });
expect((await trip.summary())?.distanceByMode.swim ?? 0).toBeGreaterThan(0);
});
});The destination is the block where the player's feet should end up. Somebody has to be able to stand there.
Wait for the client to see your fixture before you start the trip. Blocks placed with world exist on the server first, and a route planned before they reach the client goes around terrain that isn't there yet.
Awaiting travelTo directly, without wait: false, resolves once the trip succeeds. It rejects if the trip fails, is cancelled, or is still going after timeout, which defaults to 30 seconds. Give long trips a longer timeout, or use wait: false and wait on the handle as above.
Decide what the trip may do
Without options, a trip may use every travel mode and put down boats and minecarts from the hotbar, picking them back up afterwards. It won't place or break blocks, craft, eat, use rockets or water buckets, or tame anything unless you allow it:
| Option | What it allows |
|---|---|
allowedModes | Only these travel modes, such as ["walk", "jump", "swim"]. The trip fails instead of using a mode you left out. |
placing | Bridges, pillars, and trapdoor crawl entrances, using listed full blocks from the hotbar, up to maxBlocks. |
breaking | Clearing obstacles, carving stairs, and digging down through listed blocks with listed tools, up to maxBlocks. |
nonDestructive | Putting changed blocks back and removing temporary bridges once the player has passed. |
crafting | Crafting listed recipes from carried ingredients, with an optional detour to a crafting table. |
gathering | Mining listed blocks off the route when the trip needs more building blocks or a tool. Gathered blocks aren't restored. |
resupply | Moving approved supplies from the main inventory into the hotbar. |
eating | Eating listed foods once hunger reaches minFoodForSprint, up to maxItems meals. |
maxRockets, equipElytra | Using firework rockets while gliding, and putting on a carried elytra for a flight. |
waterBucket | Planned drops onto a water bucket placed just before landing. |
fallRecovery | Checked drops into water, riding a boat off a cliff, and steering to safety after losing footing. |
mounts | Taming and saddling nearby horses, and whether to ride or collect vehicles the player doesn't own. |
deployVehicles | Putting down carried boats and minecarts. On by default. |
Block and tool lists accept IDs or tags such as #minecraft:logs. Every budget counts for the whole trip, including after a retarget or policy change.
In a walled corridor with a two-block gap in the floor, this trip bridges the gap with cobblestone from the hotbar:
await player.give("minecraft:cobblestone", 8);
const trip = player.travelTo(destination, {
allowedModes: ["walk"],
placing: { blocks: ["minecraft:cobblestone"], maxBlocks: 2 },
wait: false,
});
await trip.wait({ timeout: "60s" });
const summary = await trip.summary();
expect(summary?.itemsConsumed["minecraft:cobblestone"]).toBe(2);summary.terrainEdits lists every block the trip placed, broke, or restored, with its position and the block before and after.
Weigh one route against another
preferences changes which of the allowed routes the trip prefers. None of them grant a new action:
| Preference | Effect |
|---|---|
modeCosts | Makes a mode cheaper or dearer, from 0.1 to 10 times its usual cost. { boat: 10 } prefers walking around a lake to crossing it by boat. |
hazardCost | Extra cost for each step beside lava, fire, or another hazard, from 0 to 64. Defaults to 8. |
maxDrop | The largest plain drop, from 1 to 3 blocks, without fall recovery. Defaults to 1. |
maxDetour | Keeps the route within this many blocks of the straight line to the destination. 0 means no limit. |
entityCost | Extra cost for cells other entities were standing in when the route was planned. |
wholeJourney | Plans the whole route before moving, instead of planning ahead in sections while walking. |
acceptNearest | Travels to the closest reachable point when the destination can't be reached, and succeeds with travel.reachedNearest. |
maxNodes | The search budget for each route. Defaults to 24,000. |
Choose where the trip ends
arrivalRadius lets the trip finish anywhere within up to 16 blocks of the destination. That helps when the destination itself is a block, a bed, or a spot nobody can stand in.
Leave out y to travel to a column whose height you don't know. The trip stands on the surface once that chunk has loaded:
const result = await player.travelTo({ x: 30, z: 30 }, { timeout: "60s" });
// The goal now holds the height the column resolved to.
expect(result.feet).toEqual(result.goal);To reach another dimension, pass dimension. The player walks into the nearest loaded portal that leads there, waits for the transfer, and continues on the other side. It uses nether portals between the overworld and the nether, and end portals to and from the end. A trip never builds a portal or crosses one it doesn't need.
// Assumes a lit nether portal near the player.
await player.travelTo(pos(688, 100, 666), { dimension: "minecraft:the_nether", timeout: "2m" });
expect((await player.pose()).dimension).toBe("minecraft:the_nether");Follow and steer a running trip
With wait: false, travelTo returns a handle you can read and control while the player moves:
| Method | Use |
|---|---|
status() | The current snapshot: status, activity, feet, goal, and live travel state such as the mode, sprinting, food, and blocks placed. |
wait({ timeout }) | Waits for the trip to end, the same way awaiting travelTo does. |
retarget(pos, { arrivalRadius, dimension }) | Sends the same trip somewhere else without stopping. |
updatePolicy(options) | Replaces the trip's options. Budgets already spent still count. |
pause() and resume() | Hands control back to the player, then continues from wherever they now stand. |
cancel() | Ends the trip and returns control. |
route() | The steps planned so far, with their modes, placements, breaks, and landings. |
summary() | The finished trip: distance by mode, duration, terrain edits, items used, and retargets. |
events() | The status changes, terrain edits, item uses, and world clicks recorded for the trip. |
A retarget keeps the camera, vehicles, and budgets. If the player is partway through something they can't stop, such as a flight or a portal transfer, that finishes first.
const trip = player.travelTo(pos(5, 100, 36), { allowedModes: ["walk"], wait: false });
await trip.started();
await trip.retarget(pos(30, 100, 20), { arrivalRadius: 1 });
expect((await trip.wait({ timeout: "60s" })).status).toBe("succeeded");
expect((await trip.summary())?.retargets).toBe(1);Starting another trip replaces the running one. The replaced trip's summary stays available with cancelReason: "replaced".
updatePolicy refuses options the trip can't use, such as a horse-only policy with no horse nearby. The trip keeps going with its old options.
Find out why a trip failed
A failed trip reports a failure kind, so a test can check the reason without matching message text:
// Nobody can stand inside solid stone.
await world.fill(pos(20, 100, 20), pos(22, 102, 22), "minecraft:stone");
const trip = player.travelTo(pos(21, 101, 21), { allowedModes: ["walk"], wait: false });
await expect(async () => (await trip.status()).status)
.toEventuallyEqual("failed", { timeout: "30s" });
expect((await trip.status()).failure).toBe("destination");| Failure | Meaning |
|---|---|
unreachable | No route through the loaded terrain reaches the destination with the allowed modes and supplies. |
destination | Nobody can stand at the destination with the allowed modes. |
search_limit | The route search spent maxNodes without finding a usable route. |
terrain_unloaded | Terrain the route needs never loaded. |
no_progress, stuck | The player stopped getting closer after repeated replanning. |
supplies, resources | The trip couldn't get the blocks, items, vehicle, or equipment the route needs. |
landing, fall | A flight or drop found no safe landing, or a fall recovery failed. |
air | No breathing spot remained within the player's air. |
vehicle | A boat, minecart, or mount couldn't be ridden, left, or collected safely. |
no_portal | No usable portal led toward the destination's dimension. |
error | Something unexpected happened. The message has details. |
A cancelled trip reports cancelReason instead: requested, escape, movement_takeover, player_changed, or replaced. A paused trip reports pause: requested, pause_menu, or movement_keys.
Some requests are refused before a trip starts, such as rideHorseTo with no tame, saddled horse nearby, or a dimension with no loaded portal. The call rejects, its message includes a code such as resources or no_portal, and any running trip keeps going.
Plan a route without moving
previewTravel plans the whole way through loaded terrain in the current dimension, without moving the player or interrupting a running trip:
const preview = await player.previewTravel(destination, { allowedModes: ["walk", "boat"] });
expect(preview.state).toBe("planned");
expect(preview.plan?.modes).toContain("boat");A preview that can't find a route ends with state: "failed" and a failure kind, rather than rejecting.
Keep menus usable while travelling
Pass managed: true to travel with an orbit camera instead of the player's own view. The camera starts above and behind the player with a free cursor. Drag with the left mouse button to orbit and scroll to zoom. The inventory, chat, and other screens keep working while the player moves.
| Option | Effect |
|---|---|
escape | "cancel", the default, ends the trip and opens the pause menu. "pause-menu" holds the trip while the pause menu is open. |
movementKeys | "cancel", the default, ends the trip when a movement key is pressed. "pause-while-held" lets the player walk while the key is held, then replans. |
cameraCollision | false keeps the orbit distance in enclosed spaces and shows the player through walls. |
cameraPitch, cameraDistance | Where the orbit starts: 10 to 85 degrees, and 2 to 16 blocks. The player can change both. |
The field of view stays fixed while sprinting. Arrival and cancellation restore the normal camera and controls.
Without managed, opening a screen pauses the trip until it closes.
A click on the world during a managed trip, without dragging, is recorded in events().clicks with the camera ray at that moment. player.pickRay({ x, y }) gives the same ray for any GUI position, which lets a test turn a click into a destination.
Restrict a test to one way of moving
Each mode also has its own call. These use the same planner and handle as travelTo, but fail rather than switch to another mode:
| Call | Moves by |
|---|---|
walkTo | Walking, without sprinting or crossing deep water. |
sprintJumpTo | Running and jumping. |
swimTo | Swimming on the surface. |
diveTo | Swimming underwater between breathing spots. |
crawlTo | Crawling, entering through a trapdoor or an existing low pose. |
climbTo | Ladders. |
flyTo | Elytra flight from a launch spot to a landing. |
rideBoatTo, rideHorseTo, rideMinecartTo | That vehicle, including getting on and off. |
allowedModes does the same thing through travelTo when you want to allow a few modes together.
Know the limits
- Routes come from the terrain the client has loaded. The trip waits for chunks to stream in, but it doesn't generate or force-load them.
- The trip finds a good route, not always the best one.
- It waits briefly for an entity standing in its way, then routes around it. It doesn't predict where moving entities will go.
- It reaches the overworld, the nether, and the end, through existing portals only.
- Crafting uses the integrated server's recipes. On a remote server it uses the player's unlocked recipe book, and gathering can't predict drops.
- A trip is best effort about restoring terrain. Check
travel.unrestoredTerrainwhen a test depends on it.