MOVEMENT · PATHFINDER SEARCH

How to Use PathFinder.search() Without Accepting Partial Paths

CURATED PATH

These links are maintained for a concrete prerequisite, failure mode, debugging step, or next lesson instead of generic topic similarity.

Movement

Recommended next steps

Customize terrain and structure costsAdvanced searchDiagnose failed or incomplete searchesFailure modeChoose the correct goal rangeGoal definition
Verification statusArticle origin: Original English guide — no translated source article · Official API docs: Checked — PathFinder.search() goal, range, options, and result contract · Completion gate: Checked — incomplete=true is treated as failure even when path is non-empty

VERIFICATION

Evidence and test status

Article origin
Original English guide — no translated source article
Official API docs
Checked — PathFinder.search() goal, range, options, and result contract
Completion gate
Checked — incomplete=true is treated as failure even when path is non-empty
Result semantics
Checked — path, ops, and cost are kept distinct
Code review
Passed — examples guard missing objects, complete searches, and empty completed paths
Screeps Console test
Pending — no Console execution is claimed
Live shard movement test
Pending — no live path execution is claimed
Last verified
August 29, 2026

Quick answer

PathFinder.search() is a search API, not a movement command. Give it an origin and a goal whose range matches the position your next action actually needs. Then treat result.incomplete as the completion gate. A non-empty result.path is not proof of success because an incomplete search may still return the closest partial path it found.

The safe order is: define the goal contract, run the search, reject incomplete, handle the valid empty-path case, and only then hand a complete non-empty path to movement.

Use the smallest safe search pattern

This example searches from one Creep to one visible Source. The Source tile is not walkable, so the goal stops at range 1:

const creep = Object.values(Game.creeps)[0];
const source = creep?.room.find(FIND_SOURCES)[0];

if (!creep || !source) {
  console.log('missing creep or source');
  return;
}

const result = PathFinder.search(
  creep.pos,
  { pos: source.pos, range: 1 }
);

console.log(JSON.stringify({
  incomplete: result.incomplete,
  steps: result.path.length,
  ops: result.ops,
  cost: result.cost
}));

if (result.incomplete) {
  console.log('search stopped before reaching the goal range');
  return;
}

if (result.path.length === 0) {
  console.log('goal range already satisfied; no movement path needed');
  return;
}

const moveResult = creep.moveByPath(result.path);
console.log({ moveResult });

There are two separate decisions here. incomplete answers whether the search reached a valid goal state. path.length answers how many positions the returned route contains. Do not reverse those responsibilities.

Make goal range match the task

A PathFinder goal can be a RoomPosition or an object containing pos and range. The documented default range is 0, which means the search tries to reach the target position itself.

That default is wrong for targets that cannot be occupied. The official documentation uses a Source as the example: a Creep cannot stand on the Source tile, so the goal should stop at least one square away.

const sourceGoal = {
  pos: source.pos,
  range: 1
};

const result = PathFinder.search(creep.pos, sourceGoal);

Use the downstream task to choose the range. If the next operation requires adjacency, range 1 is the natural search contract. If another action can operate farther away, use the range that action actually permits instead of automatically forcing every search to range 1.

Important: the goal range changes where the search is allowed to finish. It does not change the range rules of harvest(), transfer(), upgradeController(), or any other action.

Read path, ops, cost, and incomplete separately

PathFinder.search() returns one object with four fields that answer different questions:

FieldWhat it meansDo not treat it as
pathAn array of RoomPosition objects for the returned routeA success flag
opsSearch operations performed before the result was calculatedPath length or travel ticks
costAccumulated search cost from terrain costs and any CostMatrix valuesGeometric distance
incompleteWhether the pathfinder failed to find a complete pathA hint you can ignore when path is non-empty

The separation matters when you compare candidates. A route can contain fewer positions but have a higher configured cost. A search can perform many operations and still return a short route. And an incomplete search can return positions that look plausible without ever reaching the required goal range.

Why a non-empty path can still be failure

The easiest PathFinder bug is this:

const result = PathFinder.search(origin, goal);

if (result.path.length > 0) {
  assignTask(result.path); // unsafe success test
}

The official API explicitly says that when the pathfinder fails to find a complete path, incomplete becomes true and path can still contain the closest partial path found under the search parameters.

If that partial route is accepted as a valid assignment, the failure moves downstream. A Creep may walk partway toward a target and stop at a room boundary, a blocked area, or a search-limit frontier. The movement layer then looks broken even though the original error was accepting an incomplete search.

Gate the assignment where the evidence exists:

function findCompletePath(origin, goal, opts = {}) {
  const result = PathFinder.search(origin, goal, opts);

  if (result.incomplete) {
    return {
      ok: false,
      reason: 'incomplete-search',
      path: [],
      ops: result.ops,
      cost: result.cost
    };
  }

  return {
    ok: true,
    path: result.path,
    ops: result.ops,
    cost: result.cost
  };
}

Keeping the partial path out of the success payload prevents a later caller from accidentally treating it as movement-ready.

Why an empty complete path can be success

The opposite shortcut is also unsafe: path.length === 0 does not automatically mean failure. If the origin already satisfies the goal range, there may be no movement step to return.

const goal = { pos: source.pos, range: 1 };
const result = PathFinder.search(creep.pos, goal);

if (!result.incomplete && result.path.length === 0) {
  const alreadyReady = creep.pos.inRangeTo(source.pos, 1);
  console.log({ alreadyReady });
}

This gives you a useful invariant: first ask whether the search completed, then ask whether movement is necessary. Do not force both questions into one path-length check.

Know which limits can stop the search

The default search has bounded work. Current official documentation lists maxOps with a default of 2000, maxRooms with a default of 16 and maximum of 64, and maxCost with a default of Infinity. A roomCallback can also return false to exclude a room from the search.

Those controls are useful, but they can also make an otherwise reachable destination incomplete under your chosen search contract. If you deliberately override them, log the exact values rather than treating the following diagnostic example as recommended defaults:

const opts = {
  maxOps: 4000,
  maxRooms: 8,
  maxCost: 250
};

const result = PathFinder.search(creep.pos, goal, opts);

console.log(JSON.stringify({
  goalRoom: goal.pos.roomName,
  goalRange: goal.range,
  maxOps: opts.maxOps,
  maxRooms: opts.maxRooms,
  maxCost: opts.maxCost,
  incomplete: result.incomplete,
  steps: result.path.length,
  ops: result.ops,
  cost: result.cost
}));

Do not respond to every incomplete result by blindly increasing limits. First confirm that the goal range is achievable and that your room or CostMatrix policy did not deliberately block the route.

If you need to build walkability and terrain costs, use the dedicated CostMatrix guide. This article only owns the search-result contract.

Use multiple goals without confusing selection with distance

PathFinder.search() can accept an array of goals. The API returns the cheapest path it finds among them under the configured search costs.

const goals = creep.room.find(FIND_SOURCES).map((source) => ({
  pos: source.pos,
  range: 1
}));

if (goals.length === 0) {
  console.log('no visible sources');
  return;
}

const result = PathFinder.search(creep.pos, goals);

if (result.incomplete) {
  console.log('no complete goal reached under this search contract');
  return;
}

“Cheapest” here follows PathFinder costs; it does not necessarily mean the fewest RoomPositions. If your task also needs load balancing, stable identity, assignment counts, or resource availability, path search is only one input to that policy. The Source selection guide owns that higher-level assignment problem.

Hand a complete path to movement

Path search and movement execution should stay separate. A complete search result says the route reached the requested goal contract. It does not say the Creep has already moved.

const result = PathFinder.search(creep.pos, goal);

if (result.incomplete) {
  return;
}

if (result.path.length > 0) {
  const moveResult = creep.moveByPath(result.path);
  console.log(JSON.stringify({
    moveResult,
    firstStep: result.path[0],
    remainingSteps: result.path.length
  }));
}

moveByPath() is a movement call with its own return value. Keep that return value separate from result.incomplete. If movement is accepted but later position does not change, continue with the movement progress diagnostic rather than rewriting the search result.

Debug an incomplete search in the right order

  1. Confirm origin and goal identity. Log room names and coordinates so you know what was actually searched.
  2. Confirm goal range. Do not search for an occupied or non-walkable target tile with the default range 0.
  3. Check incomplete directly. Never infer completion from a populated path.
  4. Inspect search limits. Record maxOps, maxRooms, and maxCost when you override them.
  5. Inspect room exclusion. A roomCallback returning false makes that room unavailable to the search.
  6. Inspect custom costs. A CostMatrix can make tiles effectively unwalkable or make a route exceed your cost budget.
  7. Only then debug movement. A complete search and an accepted movement command are different stages.

If the failure is specifically ERR_NO_PATH from a movement or routing API, the ERR_NO_PATH guide compares the relevant failure surfaces.

Know what this guide does not own

This article owns one question: how to form a correct PathFinder.search() request and interpret its result before downstream code accepts it.

This boundary keeps search correctness from turning into a second CostMatrix guide, a second movement diagnostic, or a second target-selection system.

FAQ

Does path.length > 0 mean PathFinder.search() succeeded?

No. An incomplete result may still include a partial path. Check incomplete.

What range should I use for a Source?

At least 1. A Source tile is not walkable, and the official documentation uses range 1 in its example.

Is cost the number of steps?

No. It is the accumulated configured search cost.

Is ops the number of movement ticks?

No. It is the number of search operations performed before the path was calculated.

Can a completed search return an empty path?

Yes when no movement is needed to satisfy the goal contract. Confirm the origin already meets the requested range before deciding what to do next.

Official documentation

PathFinder.use() is deprecated in the current API documentation and is not part of this guide.

SOURCE AND SCOPE

This is an original English guide. Its verification record separates documentation-reviewed claims from Console or live-shard observations that have not been performed.