TL;DR
After a train finishes a delivery, let it chain directly to its next delivery instead of returning to a depot to reset — unless its fuel is low, in which case it goes back to a depot (which refuels) exactly as today.
I can link you to a private repo if you want to see it. I have not distributed this at all, I wanted to prove the concept but I will not publish it per your license.
What ?
The depot round-trip between every delivery is pure in-flight overhead. Removing it raises per-train utilization, so you need fewer trains for the same throughput — a real UPS win on a train-heavy base — and it drops the per-delivery depot unload/reset/refuel-pathfind work.
It's fully optional and off by default (new settings; with it disabled, behavior is byte-for-byte stock LTN), and it reuses your existing train pool and dispatcher rather than adding a parallel one. It has to live inside LTN, not a companion mod: a second mod can't touch the dispatcher tables or create a delivery, so it would desync accounting and cause double deliveries.
How it works ("hold-and-chain")
Since the dispatcher only considers trains parked at a stop, the finished train is held at its requester and registered into the available pool there, so your own dispatcher assigns its next delivery straight from that spot — requester → next provider, no depot.
The timing, step by step:
On requester arrival, if chaining is on and fuel is OK, the train gets a short grace wait (~2s) so it lingers after unloading instead of departing, and is marked as a held candidate.
Once it's finished unloading (empty), the original delivery is finalized normally (completion event + removal, so accounting stays correct) and the train is registered into the pool as a "field" train.
The dispatcher picks it up on its next update tick and it leaves directly for the next provider.
If nothing matches within the hold window (default ~30s), the train falls back to a depot.
Field trains are preferred in dispatch, but only as a tiebreaker after cargo-fit — a better-fitting depot train still wins, so throughput quality doesn't regress.
Eviction (so a held train never blocks a dock)
A held train occupies its requester stop, so it's released to a depot the moment it would get in the way:
A new delivery is created inbound to that same requester → the held train is evicted immediately so the incoming train can dock. Deliveries to other stops don't evict it.
Fuel is low on arrival → never held; takes the normal depot path (and refuels there).
Hold window expires (~30s) with no reassignment → released to a depot.
Chaining turned off, or the train leaves/becomes invalid → dropped.
The scan that manages held trains only iterates the small held set on a throttled interval — no global per-tick polling.
Settings (all default to stock behavior)
Master on/off switch (default off).
Minimum fuel % to allow chaining (default 33).
Hold window in ticks before depot fallback (default ~30s).
Prefer field trains toggle (default on, only active when chaining is on).
Lastly, there is a 2nd change that would modify route calculations. However this code is not in my private repo yet, but here is the concept.
It adds a server setting with three choices:
- Exact (unchanged, stays the default): ask the route calculator about every train, always pick the true closest.
- Batched: instead of ~300 separate "how far is train #N from the pickup?" questions, ask one question: "from the provider, how far is every parking spot?" The route calculator can answer a multi-destination query in a single pass — LTN
already uses this exact trick when a train picks which depot to go home to; I just applied it to dispatch too. The catch: it measures routes from the provider to the depots — backwards relative to how the train will actually drive, and
on one-way rail networks the reverse route isn't identical. So the ranking is approximately right: in testing it always picked from the near end of the pack, but typically a train ~40–60% farther than the true closest — think 1.5km
instead of 1km, maybe 30 extra seconds of driving. With 157 spares, throughput doesn't notice.
- Off: don't consider distance at all; rotate among suitable idle trains. Dispatching becomes essentially free; trains drive farther on average.
Why ?
We have a py mega base, and with over 1000 train stops the ticks add up.
TL;DR
After a train finishes a delivery, let it chain directly to its next delivery instead of returning to a depot to reset — unless its fuel is low, in which case it goes back to a depot (which refuels) exactly as today.
I can link you to a private repo if you want to see it. I have not distributed this at all, I wanted to prove the concept but I will not publish it per your license.
What ?
The depot round-trip between every delivery is pure in-flight overhead. Removing it raises per-train utilization, so you need fewer trains for the same throughput — a real UPS win on a train-heavy base — and it drops the per-delivery depot unload/reset/refuel-pathfind work.
It's fully optional and off by default (new settings; with it disabled, behavior is byte-for-byte stock LTN), and it reuses your existing train pool and dispatcher rather than adding a parallel one. It has to live inside LTN, not a companion mod: a second mod can't touch the dispatcher tables or create a delivery, so it would desync accounting and cause double deliveries.
How it works ("hold-and-chain")
Since the dispatcher only considers trains parked at a stop, the finished train is held at its requester and registered into the available pool there, so your own dispatcher assigns its next delivery straight from that spot — requester → next provider, no depot.
The timing, step by step:
On requester arrival, if chaining is on and fuel is OK, the train gets a short grace wait (~2s) so it lingers after unloading instead of departing, and is marked as a held candidate.
Once it's finished unloading (empty), the original delivery is finalized normally (completion event + removal, so accounting stays correct) and the train is registered into the pool as a "field" train.
The dispatcher picks it up on its next update tick and it leaves directly for the next provider.
If nothing matches within the hold window (default ~30s), the train falls back to a depot.
Field trains are preferred in dispatch, but only as a tiebreaker after cargo-fit — a better-fitting depot train still wins, so throughput quality doesn't regress.
Eviction (so a held train never blocks a dock)
A held train occupies its requester stop, so it's released to a depot the moment it would get in the way:
A new delivery is created inbound to that same requester → the held train is evicted immediately so the incoming train can dock. Deliveries to other stops don't evict it.
Fuel is low on arrival → never held; takes the normal depot path (and refuels there).
Hold window expires (~30s) with no reassignment → released to a depot.
Chaining turned off, or the train leaves/becomes invalid → dropped.
The scan that manages held trains only iterates the small held set on a throttled interval — no global per-tick polling.
Settings (all default to stock behavior)
Master on/off switch (default off).
Minimum fuel % to allow chaining (default 33).
Hold window in ticks before depot fallback (default ~30s).
Prefer field trains toggle (default on, only active when chaining is on).
Lastly, there is a 2nd change that would modify route calculations. However this code is not in my private repo yet, but here is the concept.
It adds a server setting with three choices:
already uses this exact trick when a train picks which depot to go home to; I just applied it to dispatch too. The catch: it measures routes from the provider to the depots — backwards relative to how the train will actually drive, and
on one-way rail networks the reverse route isn't identical. So the ranking is approximately right: in testing it always picked from the near end of the pack, but typically a train ~40–60% farther than the true closest — think 1.5km
instead of 1km, maybe 30 extra seconds of driving. With 157 spares, throughput doesn't notice.
Why ?
We have a py mega base, and with over 1000 train stops the ticks add up.