Friday Facts #374 - Smarter robots

Posted by kovarex, Oxyd, Klonan on 2023-09-01

Hello,
today we are going to talk about some flying robot behavior improvements. Some of the problems we fixed were reported countless times, so I hope that some people will appreciate it :).


Quality of life versus flashy featureskovarex

I know that you are mainly looking forward to the new content, and that just quality of life improvements aren't the kind of things that make people buy the game and get excited for.

But I strongly believe, that if you want to add content, mechanics, and systems to a game, which already isn't simple, there is always a risk of just it being too much. By doing QOL improvements, we reduce the small hassles and annoyances, which effectively creates an extra mental space to enjoy more in the game. Its like cleaning your room before getting a new toy.

So to make things clear, the reason that we make and present these kind of changes is not because we don't want to make new flashy features, we just want the new stuff to be enjoyable without a burden of having too much to deal with.
But don't worry, we will show something new next week!


Smarter robot tasksOxyd

Logistic and construction robots have been in the game for a while, and at their core they are fairly simple entities. A logistic robot, for example, may be told to go pick up an item from a chest, and deliver it to another chest. So far so good, but in Factorio there usually is more than one item to be transferred β€” so what does the game do when tasked with transferring, say, 500 items from one box to another?

The Old Way

In Factorio 1.1, the game keeps a list of all idle robots, including those hiding away in roboports, and when a task needs to be taken care of, it selects the nearest idle robot from that list. So, to transfer 500 items from a provider chest to requester, the game would find the first idle robot closest to the provider chest and task it with the transfer. This robot now has a task, so it is removed from the list of idle robots.

But we still have 499 other items to transfer (ignoring robot capacity bonus for simplicity). The logistic network simply repeats the previous steps for all the other items β€” find the next closest idle robot, task it with transferring the next item and so on until there is a robot for each of the 500 items in the box.

The problem with this strategy is that while the first few robots may be located in roboports reasonably close to the provider chest, by the time we get to the last items the network may have to reach for robots at the other end of the factory because there is no closer idle robot for the task. The end result is that the first batch of robots will arrive at the chest, take the item and deliver it to the destination, and then recharge and go have a nap in a roboport β€” transferring a single item was all these robots were tasked with and the remaining items have already been assigned to other robots. After that, a slow trickle of robots from far away parts of the factory will come in and transfer the remaining items.

The situation is similar for construction robots, and this can be especially apparent when using one's portable roboport. Say the player wants to remove a bunch of trees in their factory. A single personal roboport can have up to 25 construction robots, so that's up to 25 trees that will be deconstructed using the player's personal robots. All remaining trees will be assigned to robots from the main logistic network, and the player will have to wait for them to arrive for the trees to be gone β€” even though the player's personal robots are right there with nothing better to do!

The New Way

Choosing a robot for a task only from the list of idle robots clearly isn't the best strategy. Assigning a task to a robot that is currently busy but nearby may be a better choice, even if the new task may have to wait until the robot finishes its other tasks. This required two main changes to the logistic network code.

The first change was to allow robots to have multiple tasks assigned to them. Much of the code has been written with the assumption that a robot has exactly one job, but after some code refactor, robots now have a queue of tasks.

Second, we need a way to select a robot for a task. Each task has a starting position, for example a deconstruction order starts at the entity to be deconstructed. Until now, the selection process was simple β€” just select the idle robot closest to that point.
But now, when we can also select a robot with one or more queued tasks, the process becomes a bit more complex.

The new metric for robot selection is the arrival time. For an idle robot, its arrival time is simply the distance to the starting position divided by its speed. For a busy robot, we can look through its queue of tasks, figure out where and when the robot is going to end up when its done, and then add the flight time from there to the starting position of the new job.
When selecting a robot for a task, we select the one that is going to arrive first, even if it has to take care of other things before that.

It is worth pointing out that the arrival time calculation is only an estimate. For example, we simplify the effects of the recharging times. A keen-eyed player will no doubt notice situations where choosing a different robot could have been a little bit more efficient. But in the big picture, it doesn't seem to be very noticeable.

Let's see how the new system handles our previous two examples. The initial batch of robots is tasked with transferring all the items which results in them shuffling back and forth between the requester and the provider.

For deconstructing trees, each of the player's personal robots does more than just a single tree. Robots from the main network still come to help out, but this time it's because they can help get the whole job done faster.

Performance

Having a simple list of all busy robots and going through it each time a new task comes in may work fine for small factories, but with several thousand robots flying everywhere it can quickly become a UPS drain. To alleviate that, we implemented a different representation.

Whenever a robot's queue of tasks is updated, it calculates its final position estimate β€” that is, its final position and the time at which it will finish. Each map chunk now stores a list of all busy robots that are estimated to finish on that chunk. So when a robot updates its final position estimate, it registers itself in that chunk's list of robots. When searching for a robot for a particular task, the game now starts its search at the chunk where the job's starting position is located, and continues its search in an outward spiral.

Storing busy robots on chunks and searching in a spiral improved the performance by a lot, and even factories with thousands of busy robots run well.


Smarter robot other thingsKlonan

While playing and developing the expansion for the last 2 years, we have also accumulated a few more features and changes to make the robots feel smarter and work better.

Roboport robot requests

There are some situations when you need to make sure some roboports always have robots to service tasks. For example if you have a little resupply point with buffer chests, you want logistic robots around to quickly empty your trash and refill your supplies. It is quite annoying when all the materials are right there, but you are stuck waiting around, because due to natural robot migratory patterns, the roboports in the area are quite empty.

It could be solved with requester chests, inserters, circuit network, and such... but we decided a smoother approach. You can now set logistic requests for robots directly inside the roboport GUI. Robots will be dispatched from other roboports to fulfill the request (important to note, robots will not be taken from storage or provider chests).

With this new feature, we can set all the roboports near our resupply point to always have 100 logistic robots available. Once we arrive, our needs are serviced in record time, and afterwards you can see robots arriving to get the roboport back up to 100.

Another nice use of this is that we can use the roboport requests to remove certain robots from the network. Perchance we had some mod with higher quality worker robots, we can request the low quality ones and remove them with a filter inserter, over time removing the worse robots from circulation.

Better robot charging heuristic

When a worker robots runs low on energy, they need to find a nearby roboport to charge at, but they need to be a little bit smart about it. For instance if they always chose the closest roboport, then you would get queues and traffic jams at some roboports while others are completely empty.

For these 'smarts' we use a quite simple heuristic, factoring in not just how close the roboport is, but also how many other robots are currently charging there. Typically this worked 'well enough' but every so often you could encounter a little inconvenient situation where they are still bunching up on some roboports while ignoring others that are just a little bit further.

In some extreme cases this can actually be quite a problem, as the robots waiting to charge still use energy for hovering, so lose more energy, in a positive feedback loop where all robots who join the queue will fall to 0 energy while they are waiting their turn. Generally though the main problem here is that the player looks at the robots and thinks "These robots are so dumb...". Not the end of the world, but we can do better...

Surprisingly in this case the issue can be helped a lot by just a small improvement in the heuristic logic. When deciding which roboport to charge at, the robots crucially did not consider the current robots on the way to that roboport, only those already in the queue, and did not factor in how many free charging spots the roboport has open. By adding these two extra parameters into the equations, the distribution of the robots improves quite nicely.

Mitigation for robot pathing over lakes

It has been a problem for a long time, the robots are pretty dumb when it comes to pathfinding, and try to fly over areas that have no logistic network coverage. This is because for performance reasons, we don't do any pathfinding at all. The robots just fly in a straight line to the job position.

One major problem of this is the possibility of the robots to get stuck in an infinite loop. This is because they fly out over uncovered territory, run out of battery, and return back to recharge and try again. Obviously they will never make it.

This issue was notable enough and annoying enough for us that we really did want to come up with a fix. The solution we came to was a smart trick in the roboport selection when they run out of energy. Usually the robot just chooses one of the nearest roboports, which is fine for normal circumstances, but in this case leads to the robot turning completely around in failure.

So we changed the selection logic, so that the robot tries to always charge at a roboport that is closer to the destination than the robot is. This means that even if the robot will potentially fly with low energy for longer, it will always be able to make some progress towards the target, and eventually complete their mission.

It is not a perfect solution, but should hopefully help prevent the most egregious issue of this technical constraint.


As always, let us know what you think at the usual places.