Page 2 of 2

Re: Let's talk train design

Posted: Thu Sep 25, 2025 9:07 am
by Tertius
waterBear wrote: Thu Sep 25, 2025 2:30 am Yeah, that's the dream. I think you can probably do it with a centralized system, but I am lazy and don't want to build (another) huge combinator brain. Good luck though!

As for the trip planning, I do everything I can to let the game mechanics do that for me. One of my tricks is to attach a single combinator to each stop that basically says "set priority to 50 minus C" where C is the number of trains on the way or at the stop. That automagically prevents the trains from all going to the nearest pickup and ignoring distant providers, for example. It also works for delivery stations. Sometimes you have multiple ore drop offs that all need to be evenly fed, and this trick solves that problem nicely - with one combinator.

It's one of the reasons I don't like a lot of my own solution ideas so far. I want to avoid a system that tells the trains where to go via signal, both because that's going to end up being a mess of combinators and also because then I have to do that work instead of the game engine.
You're describing the system I presented. In detail. It has all the properties you envision. You even did the same priority calculation I do. I literally do the same: an arithmetic combinator with P=50 - C. Everything else is being done by the game engine with all the interrupts I described.

The only thing we differ is "multiple ore drop offs that all need to be evenly fed". The general solution to this is not a circuit but instead overproviding ore. Produce more ore than is being consumed, so full trains will accumulate in the long run, so there will always be a full train available for any drop off station, so they're being filled by a waterfall of trains automatically.
Since your factory will always grow, never shrink, there can never be any "too big" ore supply, since you need to build more ore loading stations anyway.
If you must underprovide, you can use a circuit to disable any unloading station until its buffer chests are below a threshold. Using priority even for unloading stations instead of disabling or just relying on train limits as I do is a neat idea by @Harb42, I guess I will combine this with the buffer chest fill state on my own maps. This will help during initial ramp up. Thanks for that idea!
waterBear wrote: Wed Sep 24, 2025 10:57 pm The basic idea for the most common implementation is this: A requesting stop globally broadcasts a needed number of train loads of an item. Provider stops then come online, and trains go straight to them.

There are lots of "gotchas" with the way trains and interrupts work that make this harder than it sounds in practice. It's do-able if you have one provider station for every item. By playing with the C signal from the stops, you can ensure that global demand gets reduced by 1 for every train on its way to either a provider stop for the item or to the requesting stop.
to help solve this problem, I'll take my work back offline.
By calculating this way, you get unavoidable edge cases. It's not possible to reduce the amount of the global demand and react on it the same tick. There is a minimum delay of 1 tick. In the 0th tick a train reserves a station, so its from C=0 to C=1. This means, global demand is lowered by 1, because the train was just dispatched. However, you cannot react in the same tick, because if there is a circuit reacting to the value of C, its reaction is visible only in the next tick, so for one tick there is a wrong value for the global demand. Depending on how important that value is for whatever, this can be a problem. And it will hit in the long run. Not perhaps during development, but later if you run it for days with a larger amount of trains.

This can happen for everything: If you use circuits, there is always a delay between a status change and the reaction to that status change. The delay is absolutely unavoidable, minimum 1 but usually 2 or more depending on intermediate combinators, however some built in game mechanics don't have this delay but react instantly such as reserving a station that just got empty. So some things just cannot be controlled and managed correctly all time with circuits.

Re: Let's talk train design

Posted: Thu Sep 25, 2025 5:45 pm
by Harb42
computeraddict wrote: Thu Sep 25, 2025 6:45 am
Harb42 wrote: Thu Sep 25, 2025 3:52 am By some time all fluid trains will carry water which become basically useless.
That means you're doing it wrong, not that the mechanics have flaws. All of the solutions described in this thread cannot have this happen.
Yes, it is easily can happen. In addition, it is explicity stated in the FFF-s, that this is kind-of intended: it goes to whatever loader it is, not only what type. Read back.

Re: Let's talk train design

Posted: Thu Sep 25, 2025 6:14 pm
by Harb42
Tertius wrote: Thu Sep 25, 2025 8:45 am
Harb42 wrote: Thu Sep 25, 2025 3:52 am My main problem is the following:
Your build contains a mistake, based on wrong assumption about the wildcard expansion. Use it for unloading, but not for loading.

The idea is to start with an empty train. Completely empty. For an empty train, no green cargo or fluid wildcard can match. So you cannot use the wildcard for the loading process. So instead of using an interrupt for loading, add the loading as the only static schedule entry. For this to work, you need to name all loading stations the same, no matter the material it's supposed to load. Instead of "(fluid type) load", name all of them just "fluid load". Overcome yourself and just equalize all fluid loading station names. And do it accordingly for the cargo item transport trains, name them all "cargo load".

So any empty train will go to the nearest free "fluid load" station and starts loading until it is full.
Now the interrupt condition (fluid wildcard) > 0 can trigger and will match the fluid just loaded. The waiting condition it creates should be (fluid wildcard) = 0 so it is ensured the train is completely empty after it leaves the unloading station, so the cycle will start again with an empty train on the static schedule entry. The train must be completely empty, not almost empty as you did.

Only if you completely empty a train this train system is universal. One cycle it is being loaded with water, the next cycle it might be filled with oil. This cannot happen if you keep some leftover. The train has to lose its identity to be available for everything the next cycle.

In case you see just the nearest fluid load station being visited and just the same fluid is available, you need to distribute more trains. If you have enough trains, the nearest station will always a train, either at the station or driving there, and the next train will reserve the next near station, and so on, until all loading stations are occupied.
The amount of trains with the same fluid will be implicitly limited by the sum of the train limits of the loading station of that fluid and the train limits of the unloading stations of that fluid. So if you have train limit 1 and 3 stations loading water and 1 water unloading station, you will see there will never be more than 4 trains loaded with water. So you add 4 trains for water.
So the 5th train cannot be loaded with water, it is available for loading with the next fluid. If you have a similar setup for the next fluid, you need to add 4 trains for the next fluid as well.
The trains themselves don't have an identity, they change fluids dynamically.
Hmm. You are right. It is not straightforward that it does not work for loading. -> I think it can be made that way, that it does not have to empty, so it "remember" what to load and unload all the time, because the cargo is not empty. "I got oil, okay I start to unload it. I am almost empty, time to load. What to load? Okay, I got oil, I go for loading oil." I think this 'attached' (and still independent) operation can be really useful to guarantee specific resource. For example the stone is really far away, and I always want at least 1 stone cargo, but don't want to create a new train group for it.
Besides this, you are right, in the beginning the train is empty, so it does not know what to load. This would be a manual step, and that's it.

The other think, the logical error in my setup: What you stated only works if the loader the train never goes back to park. Because in that case the "slot" will be freed up, and can consume trains indefinitely. That was my design's flaw. And for the unloaders it is advised to go back to the dock (if all the loaders are full), because it would create a deadlock otherwise. Loaders wait the Unloader stations to be empty, Unloaders wait the Loader station to be empty.

So, thanks for the insights, I go and try it out :)

Re: Let's talk train design

Posted: Thu Sep 25, 2025 9:42 pm
by jaxmed2
Harb42 wrote: Thu Sep 25, 2025 6:14 pm Hmm. You are right. It is not straightforward that it does not work for loading. -> I think it can be made that way, that it does not have to empty, so it "remember" what to load and unload all the time, because the cargo is not empty. "I got oil, okay I start to unload it. I am almost empty, time to load. What to load? Okay, I got oil, I go for loading oil." I think this 'attached' (and still independent) operation can be really useful to guarantee specific resource. For example the stone is really far away, and I always want at least 1 stone cargo, but don't want to create a new train group for it.
Besides this, you are right, in the beginning the train is empty, so it does not know what to load. This would be a manual step, and that's it.
I have to ask (maybe I'm still misunderstanding something?) but if having a train "remember" what it's loading is so important, why even fuss around with groups and interrupts? In fact the very point of the groups and interrupts is not having to do things like make a whole new group for every new resource. The pre-2.0 way of doing things using dedicated trains for each resource type might be more what you're after?

Just for completeness and at the risk of stating the obvious, there are two main ways of having "logistical" (not quite LTN but still allowing for a generic-ish requester/provider setup) way of doing trains:

Pre-2.0: Each resource type (e.g. oil) gets one or more dedicated trains. Each train has a very similar schedule: go to "oil pickup" until full, go to "oil dropoff" until empty, repeat. Add dedicated trains for each resource type with an appropriate schedule as you scale up. But this setup means you have to think more about how to keep your trains from running out of fuel.

Post-2.0: You can mix & match resource types but you should still have a separate train group for each train configuration (e.g. "liquid 1-1-1" vs "cargo 2-6" and so on.) Each train group has the following interrupts:
1. When cargo is empty, go to, e.g. "liquid 1-1-1 pickup" (whatever matches this train group) until full <-- important NOT to use wildcard here, provider station names are HARDCODED for each possible train group!
2. When cargo is full, go to "<wildcard> 1-1-1 dropoff" until empty <-- here is where the wildcard comes in, but also still good to use slightly different names for each train group in case you want to have multiple train sizes on the same network (e.g. support both "1-4-1 coal requesters" and "2-8 coal requesters" in the same network)
3. If fuel is low AND cargo is empty, go to the appropriate refuel station for this train size & shape until all locomotives are fueled <-- this interrupt can interrupt other interrupts
4. If destination is full/no destination AND cargo is empty, go to a depot <-- this interrupt can interrupt other interrupts
Add generic trains to each corresponding train group as you scale up.

Both setups of course assume that each station has a circuit condition to enable/disable itself (or set a dynamic train limit if you want a train buffer at a given station) based on their supply buffers.

So long as you have enough trains (which really is the key), either solution basically gives you a requester/provider setup. The main caveat is that you basically must add a new train for each additional provider station "slot" (including buffers) that you build, otherwise you do run the risk of having all of your trains congregate at one resource while other resources get starved. But again that's easily solved by simply having enough trains.

I can only see needing anything more complex than this if there are some niche optimizations you want to make: like the "50-C" dynamic priority if all your stations support dynamic limits and you want to load balance your train buffers. But otherwise you'll get the expected behavior of "trains go to providers, fill up, wait until a requester station opens up, make the delivery, repeat while keeping myself fueled up and never block requester stations" pretty easily using built-in mechanics. Any further optimizations like "I don't want my trains to even go the provider station unless that resource actually needs a delivery" to me just sound like a symptom of "I don't have enough trains"

Re: Let's talk train design

Posted: Thu Sep 25, 2025 10:22 pm
by waterBear
jaxmed2 wrote: Thu Sep 25, 2025 9:42 pm
Harb42 wrote: Thu Sep 25, 2025 6:14 pm Hmm. You are right. It is not straightforward that it does not work for loading. -> I think it can be made that way, that it does not have to empty, so it "remember" what to load and unload all the time, because the cargo is not empty. "I got oil, okay I start to unload it. I am almost empty, time to load. What to load? Okay, I got oil, I go for loading oil." I think this 'attached' (and still independent) operation can be really useful to guarantee specific resource. For example the stone is really far away, and I always want at least 1 stone cargo, but don't want to create a new train group for it.
Besides this, you are right, in the beginning the train is empty, so it does not know what to load. This would be a manual step, and that's it.
I have to ask (maybe I'm still misunderstanding something?) but if having a train "remember" what it's loading is so important, why even fuss around with groups and interrupts? In fact the very point of the groups and interrupts is not having to do things like make a whole new group for every new resource. The pre-2.0 way of doing things using dedicated trains for each resource type might be more what you're after?

Just for completeness and at the risk of stating the obvious, there are two main ways of having "logistical" (not quite LTN but still allowing for a generic-ish requester/provider setup) way of doing trains:

Pre-2.0: Each resource type (e.g. oil) gets one or more dedicated trains. Each train has a very similar schedule: go to "oil pickup" until full, go to "oil dropoff" until empty, repeat. Add dedicated trains for each resource type with an appropriate schedule as you scale up. But this setup means you have to think more about how to keep your trains from running out of fuel.

Post-2.0: You can mix & match resource types but you should still have a separate train group for each train configuration (e.g. "liquid 1-1-1" vs "cargo 2-6" and so on.) Each train group has the following interrupts:
1. When cargo is empty, go to, e.g. "liquid 1-1-1 pickup" (whatever matches this train group) until full <-- important NOT to use wildcard here, provider station names are HARDCODED for each possible train group!
2. When cargo is full, go to "<wildcard> 1-1-1 dropoff" until empty <-- here is where the wildcard comes in, but also still good to use slightly different names for each train group in case you want to have multiple train sizes on the same network (e.g. support both "1-4-1 coal requesters" and "2-8 coal requesters" in the same network)
3. If fuel is low AND cargo is empty, go to the appropriate refuel station for this train size & shape until all locomotives are fueled <-- this interrupt can interrupt other interrupts
4. If destination is full/no destination AND cargo is empty, go to a depot <-- this interrupt can interrupt other interrupts
Add generic trains to each corresponding train group as you scale up.

Both setups of course assume that each station has a circuit condition to enable/disable itself (or set a dynamic train limit if you want a train buffer at a given station) based on their supply buffers.

So long as you have enough trains (which really is the key), either solution basically gives you a requester/provider setup. The main caveat is that you basically must add a new train for each additional provider station "slot" (including buffers) that you build, otherwise you do run the risk of having all of your trains congregate at one resource while other resources get starved. But again that's easily solved by simply having enough trains.

I can only see needing anything more complex than this if there are some niche optimizations you want to make: like the "50-C" dynamic priority if all your stations support dynamic limits and you want to load balance your train buffers. But otherwise you'll get the expected behavior of "trains go to providers, fill up, wait until a requester station opens up, make the delivery, repeat while keeping myself fueled up and never block requester stations" pretty easily using built-in mechanics. Any further optimizations like "I don't want my trains to even go the provider station unless that resource actually needs a delivery" to me just sound like a symptom of "I don't have enough trains"
This is basically what I do on Nauvis. Habit from before Space Age, adapted to a few of the new tools. I have a group for each resource (there are really only a handful of resources you have to make a group for). Schedule is: go to pickup until full, go to drop off until empty, and a refuel interrupt.

There are exceptions, like the wall resupply trains which have some logic to decide when to show up and what to unload, but those are all unsurprising I think.

There are other special case trains, but the time I spend setting up trains this way is such an infinitesimally tiny percentage of the time I spend building stuff that I never bothered to change my ways.