Build one complete Upgrader round trip
This lesson has one observable goal: make Upgrader1 harvest Energy, travel to the owned Room Controller, spend that Energy with upgradeController(), and return to harvesting when empty.
Empty → harvest. Full → upgrade. Partly full → continue the current trip.
The previous lesson explained that Upgrader is a player-defined responsibility. This lesson supplies the repeated behavior that makes the label useful.
Lesson boundary: the example uses one fixed Creep name and the first currently active Source. It does not optimize Source assignment, Controller throughput, Containers, Links, or automatic replacement.
Confirm the Creep and room can run the lesson
Upgrader1exists and has finished spawning.- The Creep has active
WORK,CARRY, andMOVEparts. - The current room has a Controller that belongs to you.
- At least one Source is active when the Creep needs Energy.
Create the Creep with the previous spawnCreep() lesson when it does not exist. Keep the name in that spawn request and this main loop identical.
The learning body [WORK, CARRY, MOVE] is enough for this first cycle: WORK harvests and upgrades, CARRY provides the Energy Store, and MOVE allows travel.
Understand what upgradeController() actually does
creep.room.controller returns the Controller in the Creep's current room. Calling creep.upgradeController(controller) spends carried Energy through active WORK parts and adds upgrade progress when the target is valid.
The action works within range 3. That is different from harvest(), which requires the Creep to be adjacent to the Source.
| Action | Required range | Resource effect |
|---|---|---|
harvest(source) | 1 | Adds Energy to available CARRY capacity. |
upgradeController(controller) | 3 | Spends carried Energy on Controller progress. |
This is why the movement code below requests range 1 for the Source but range 3 for the Controller.
Keep one two-state rule in Creep Memory
creep.memory.upgrading is a small persistent flag. It prevents the Creep from reversing direction after every individual unit of Energy changes.
| Store condition | Saved state | Next responsibility |
|---|---|---|
| No carried Energy | false | Find an active Source and harvest. |
| No free Energy capacity | true | Move within range 3 and upgrade. |
| Partly full | Keep the existing value | Finish the current harvest or upgrade trip. |
An undefined flag behaves like false in the branch below, so a new empty Upgrader begins by harvesting. Review Screeps Memory basics when you want to inspect how the value persists.
Run the complete Upgrader1 loop
State impact: this main loop writes creep.memory.upgrading, moves the Creep, harvests Energy, and submits Controller upgrade actions.
const CREEP_NAME = 'Upgrader1';
function reportEveryTwentyTicks(message) {
if (Game.time % 20 === 0) {
console.log(message);
}
}
function moveWithinRange(creep, target, range, label) {
const moveResult = creep.moveTo(target, {
range: range,
reusePath: 5
});
if (moveResult !== OK && moveResult !== ERR_TIRED) {
reportEveryTwentyTicks(
creep.name + ' moveTo(' + label + ') returned ' + moveResult + '.'
);
}
}
module.exports.loop = function () {
const creep = Game.creeps[CREEP_NAME];
if (!creep) {
reportEveryTwentyTicks(
CREEP_NAME + ' was not found. Create it or check the exact name.'
);
return;
}
if (creep.spawning) {
return;
}
if (creep.getActiveBodyparts(WORK) === 0 ||
creep.getActiveBodyparts(CARRY) === 0 ||
creep.getActiveBodyparts(MOVE) === 0) {
reportEveryTwentyTicks(
CREEP_NAME + ' needs active WORK, CARRY, and MOVE parts.'
);
return;
}
const controller = creep.room.controller;
if (!controller || !controller.my) {
reportEveryTwentyTicks(
CREEP_NAME + ' cannot find an owned Controller in ' +
creep.room.name + '.'
);
return;
}
const usedEnergy =
creep.store.getUsedCapacity(RESOURCE_ENERGY);
const freeEnergy =
creep.store.getFreeCapacity(RESOURCE_ENERGY);
if (usedEnergy === 0) {
creep.memory.upgrading = false;
} else if (freeEnergy === 0) {
creep.memory.upgrading = true;
}
if (creep.memory.upgrading) {
const upgradeResult =
creep.upgradeController(controller);
if (upgradeResult === ERR_NOT_IN_RANGE) {
moveWithinRange(creep, controller, 3, 'Controller');
} else if (upgradeResult === ERR_NOT_ENOUGH_RESOURCES) {
creep.memory.upgrading = false;
} else if (upgradeResult !== OK) {
reportEveryTwentyTicks(
CREEP_NAME + ' upgradeController() returned ' +
upgradeResult + '.'
);
}
return;
}
const activeSources =
creep.room.find(FIND_SOURCES_ACTIVE);
const source = activeSources[0];
if (!source) {
reportEveryTwentyTicks(
CREEP_NAME + ' is waiting for an active Source.'
);
return;
}
const harvestResult = creep.harvest(source);
if (harvestResult === ERR_NOT_IN_RANGE) {
moveWithinRange(creep, source, 1, 'Source');
} else if (harvestResult !== OK &&
harvestResult !== ERR_NOT_ENOUGH_RESOURCES) {
reportEveryTwentyTicks(
CREEP_NAME + ' harvest() returned ' +
harvestResult + '.'
);
}
};
Follow the loop across repeated ticks
- The main loop finds
Upgrader1and validates its body and owned Controller. - An empty Store sets
upgradingto false. - The Creep selects a currently active Source.
harvest()returnsERR_NOT_IN_RANGEuntil the Creep reaches range 1, somoveTo()advances it across later ticks.- A full Store sets
upgradingto true. upgradeController()returnsERR_NOT_IN_RANGEuntil the Creep is within range 3.- Successful upgrade actions consume Energy across later ticks.
- When the Store reaches zero, the next tick switches back to harvesting.
The code does not attempt to harvest and upgrade in the same branch. Each tick reads current state and submits the action appropriate for that state.
Read the important action results
| Result | Meaning in this lesson | Response |
|---|---|---|
OK | The action was scheduled. | Inspect the Creep and Controller on later ticks. |
ERR_NOT_IN_RANGE | The target is outside the action range. | Move to range 1 for Source or range 3 for Controller. |
ERR_NOT_ENOUGH_RESOURCES | The Creep has no Energy for upgrading, or the Source has no available Energy for harvesting. | Change state or wait for an active Source. |
ERR_NO_BODYPART | No active WORK part can perform the action. | Inspect body damage or the original body design. |
ERR_INVALID_TARGET | The target cannot currently accept that action. | Inspect the target and Controller state. |
ERR_NOT_OWNER | The Controller is not owned by you. | Stop this beginner loop in that room. |
Use the ERR_NOT_IN_RANGE guide when movement succeeds but the action never becomes valid.
Verify one full Source-to-Controller cycle
Do not treat a single OK return value as proof of the whole loop. Watch several later ticks and confirm this sequence:
Upgrader1approaches an active Source while empty.- Its Energy Store increases.
memory.upgradingbecomes true only when the Store is full.- The Creep stops within range 3 of the owned Controller.
- Its carried Energy decreases while Controller progress increases.
- The flag becomes false after the Store reaches zero.
- The Creep returns to an active Source.
Completing this full round trip is the acceptance test for the lesson. The page does not claim that this sequence has been run in a live Screeps room.
Diagnose the first failures without adding more systems
| Symptom | First evidence to inspect |
|---|---|
| The Creep never leaves the Source. | Check Store capacity and whether memory.upgrading becomes true when full. |
| The Creep reaches the Controller but does not upgrade. | Check carried Energy, active WORK, ownership, range, and upgradeResult. |
| The Creep changes direction too early. | Confirm the partially full state preserves the previous flag. |
| The Creep waits beside an empty Source. | Confirm the code uses FIND_SOURCES_ACTIVE, not a permanently fixed depleted Source. |
| The Console repeats the same message every tick. | Keep throttled diagnostics while fixing the underlying missing object or body part. |
Do not add Builder, repair, population control, or Link logic until this one cycle is observable.
Finish the lesson, then build the first Extension
You have completed Lesson 9 when you can explain and observe all of the following:
upgradeController()spends carried Energy and works within range 3;harvest()requires range 1;creep.memory.upgradingpreserves one responsibility across ticks;- empty and full Store boundaries switch the state;
- the code validates the Creep, active body parts, active Source, and owned Controller;
- one full harvest-upgrade-harvest round trip is visible across later ticks.
Continue to build the first Extension after reaching RCL 2 →
Return to the English beginner roadmap to review all twelve lessons.