PATHFINDING · COSTMATRIX DIAGNOSTICS

What CostMatrix 255 Means in Screeps PathFinder

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

Use the matrix inside PathFinder.search()Primary API pathUse CostMatrix to diagnose ERR_NO_PATHFailure modeApply path costs to Source selectionPractical useVisualize paths and targetsDebugging tool
Verification statusChinese source article: Reviewed in full · Official documentation: Checked against the linked Screeps API and game-loop documentation · Technical correction: Static structure costs, per-search traffic overlays, invisible-room fallback, and incomplete-path handling are separated

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official documentation
Checked against the linked Screeps API and game-loop documentation
Technical correction
Static structure costs, per-search traffic overlays, invisible-room fallback, and incomplete-path handling are separated
JavaScript syntax
Passed by the editorial batch gate
Evidence level
Official documentation review, repository review, syntax checks, and static analysis only
Screeps Console test
Pending
Live multi-tick verification
Pending
Live CostMatrix traffic and cross-room test
Pending
Last editorial review
July 31, 2026

Use this guide when

Use this page when a custom PathFinder.CostMatrix is part of the problem: a route avoids roads, walks through structures, rejects an invisible room, or returns an incomplete path after you added custom costs. Start with one visible room and one goal range. Traffic policy and cross-room routing come later.

Use the ERR_NO_PATH diagnostic when the immediate question is why a movement search failed. This page owns the matrix design itself.

A matrix overrides terrain; it does not describe the whole room

ValueMeaningUse
0Keep the configured plain or swamp costNo custom override
1–254Use this custom movement costRoad preference, traffic, or risk
255UnwalkableSolid structures or an explicit hard block

A new matrix contains zeros. Zero is not free movement. Terrain still applies through plainCost and swampCost.

Build the static structure layer first

function isWalkableStructure(structure) {
  if (
    structure.structureType === STRUCTURE_ROAD
    || structure.structureType === STRUCTURE_CONTAINER
    || structure.structureType === STRUCTURE_PORTAL
  ) {
    return true;
  }

  return structure.structureType === STRUCTURE_RAMPART
    && (structure.my === true || structure.isPublic === true);
}

function buildStaticMatrix(room) {
  const costs = new PathFinder.CostMatrix();

  for (const structure of room.find(FIND_STRUCTURES)) {
    if (structure.structureType === STRUCTURE_ROAD) {
      costs.set(structure.pos.x, structure.pos.y, 1);
      continue;
    }

    if (!isWalkableStructure(structure)) {
      costs.set(structure.pos.x, structure.pos.y, 255);
    }
  }

  return costs;
}

This policy keeps Roads cheap, allows Containers, Portals, and owned or public Ramparts, and blocks other structures. It is deliberately narrower than a full colony policy. Add special cases only when you can explain why a Creep should cross that tile.

Clone the static layer before adding current traffic

function addTrafficCosts(
  staticCosts,
  room,
  movingCreepId
) {
  const costs = staticCosts.clone();

  for (const other of room.find(FIND_CREEPS)) {
    if (other.id === movingCreepId) {
      continue;
    }

    const current = costs.get(
      other.pos.x,
      other.pos.y
    );

    if (current < 255) {
      costs.set(
        other.pos.x,
        other.pos.y,
        Math.max(current, 10)
      );
    }
  }

  return costs;
}

A soft cost keeps occupied tiles possible when traffic must unwind. Marking every other Creep as 255 can turn temporary congestion into an artificial no-path result. The value 10 is an example policy and must be read relative to your Road, plain, and swamp costs.

Search, inspect, then submit movement

function moveWithMatrix(creep, target, range) {
  if (!creep || !target) {
    return { status: 'input-missing' };
  }

  if (creep.pos.inRangeTo(target, range)) {
    return { status: 'already-in-range' };
  }

  const search = PathFinder.search(
    creep.pos,
    { pos: target.pos, range },
    {
      plainCost: 2,
      swampCost: 10,
      maxOps: 4000,
      maxRooms: 8,
      roomCallback(roomName) {
        const room = Game.rooms[roomName];

        if (!room) {
          return undefined;
        }

        return addTrafficCosts(
          buildStaticMatrix(room),
          room,
          creep.id
        );
      }
    }
  );

  if (search.incomplete) {
    return {
      status: 'search-incomplete',
      pathLength: search.path.length,
      ops: search.ops,
      cost: search.cost
    };
  }

  if (search.path.length === 0) {
    return {
      status: 'no-steps-returned',
      pathLength: 0,
      ops: search.ops,
      cost: search.cost
    };
  }

  return {
    status: 'movement-submitted',
    moveResult: creep.moveByPath(search.path),
    pathLength: search.path.length,
    ops: search.ops,
    cost: search.cost
  };
}

Returning undefined from roomCallback leaves the room on terrain-only costs when it is not visible. Return false only for an explicit room ban. A completed search and an accepted movement intent still do not prove that the Creep changed position during the same script execution.

Do not collapse every empty path into one error

An empty path has different meanings depending on the starting state. The Creep may already satisfy the requested range, or the search may have produced no step. Check range before the search and check search.incomplete afterward. A non-empty partial path can still be incomplete.

Low maxOps, low maxRooms, blocked callbacks, an impossible goal range, and an over-restrictive matrix are separate causes. Record the actual goal, range, ops, cost, path length, and incomplete flag before changing several settings at once.

Cache only the static layer

Structures change less often than Creep positions. A production cache may keep a versioned static matrix in global, but it must rebuild after a global reset and clone before adding current traffic. Do not cache a matrix that already contains yesterday's Creep positions.

Use the global-cache guide for the reset and invalidation contract. Use the current page for the matrix contents.

Current tick and later ticks

PathFinder.search() describes the search performed now. moveByPath() submits one movement intent for the current tick. To verify progress, record roomName:x:y and compare a later tick. This revision contains official-document review, syntax checks, and static analysis only; live traffic and multi-room observations remain pending.

Choose another guide when

Use the path-search diagnostic for ERR_NO_PATH, callback rejection, or an incomplete search. Use the accepted-movement diagnostic when movement returns OK but position does not change across later ticks.

Official documentation

SOURCE AND SCOPE

Review the source, evidence, or next system

This English guide keeps the technical scope and verification boundaries of its Chinese source. The verification record shows which checks were completed and which runtime observations are still pending.