News:

Simutrans Sites
Know our official sites. Find tools and resources for Simutrans.

Script API: the bridge planner is limited to 10 tiles by a hardcoded value

Started by victor_18993, July 28, 2026, 08:45:40 PM

Previous topic - Next topic

0 Members and 2 Guests are viewing this topic.

victor_18993

Script API: bridge planner limited to 10 tiles
While looking into why
Código[Seleccionar]
sqai_railsometimes struggles to cross obstacles, I found a limitation in the Script API rather than in the AI itself.
Código[Seleccionar]
bridge_planner_x.find_end()currently calls the internal bridge planner with two fixed values:
Código[Seleccionar]
max_length = 10
also_flat_ends = false
Because of that, scripts cannot find a bridge endpoint more than 10 tiles away, and they cannot end a bridge on flat ground, even when the bridge descriptor would allow it.
Looking at the history, the 10-tile limit seems to have been introduced when the bridge builder was rewritten in r11608 and the binding was updated with a literal value. It does not look like an intentional API restriction.
This only affects scripts. The normal bridge tool used by players does not go through this function.
I have prepared a small patch that adds two optional arguments:
Código[Seleccionar]
bridge_planner_x.find_end(pl, pos, dir, bridge, min_length)
bridge_planner_x.find_end(pl, pos, dir, bridge, min_length, max_length)
bridge_planner_x.find_end(pl, pos, dir, bridge, min_length, max_length, flat_ends)
The existing call keeps exactly the same behaviour, so current scripts are not affected unless they choose to use the new options.
A
Código[Seleccionar]
max_lengthof 0 means "as far as the bridge descriptor and game settings allow". The value is still safely capped to avoid problems with the current
Código[Seleccionar]
uint8loop counter.
I have kept this first patch deliberately small. It only changes the planner API; it does not modify
Código[Seleccionar]
sqai_railyet and it does not change bridge construction itself.
The new tests cover bridges below, at and above 10 tiles, flat endpoints, descriptor limits, invalid ranges, scenario restrictions and compatibility with the old call.
The full automated test suite passes: 206/206.
The diff is 4 files, +252/-3, with only 53 lines of C++; the rest are tests.
I have stopped here so the API change can be reviewed independently before adapting
Código[Seleccionar]
sqai_rail.
 
En la vida todo son vivencias y cada una de ellas nos hace mas grandes,¿Como de grande eres tu? :)

prissi

Maglev amps have max_distance==2 but span only one tile max. Somehow you comment suggests otherwise, like 2 can span two tiles. (Cannot compile on this machine right now.)

victor_18993

Thanks, you were right that my wording was unclear. I was mixing the distance between the two bridge endpoints with the number of intermediate tiles actually spanned.
I have now traced the values through the planner and bridge validation code.
find_end_pos() iterates over the distance between the start and end positions. The bridge descriptor limit is checked separately against the number of intermediate tiles:
koord_distance(end, start) - 1 > desc->get_max_length()
So under the current trunk behaviour:
endpoint distance 1 = 0 intermediate tiles
endpoint distance 2 = 1 intermediate tile
endpoint distance 3 = 2 intermediate tiles
This means the
+1 conversion in the patch is correct for the current code. The issue was in my comments and test wording, not in the implementation.
I have updated the documentation and tests to distinguish explicitly between:
  • endpoint distance;
  • intermediate tiles spanned;
  • total positions occupied.
I also added a regression test using the shortest limited bridge descriptor available in the test pakset. In pak64 this is
MonorailRamp, with
max_length = 1, which accepts endpoint distance 2 and therefore spans one intermediate tile.
While checking the history, I found that the behaviour changed in r11608. Before that revision, the validation used:
length > desc->get_max_length()
The current code uses:
length - 1 > desc->get_max_length()
Therefore, the behaviour you described matches the implementation before r11608, while current trunk allows an endpoint distance one greater than the descriptor value.
I have not changed that engine behaviour, since deciding whether the current interpretation is intentional is outside the scope of this API patch.
I also checked the pak64, pak128 and pak128.german sources. None of the 59 bridge descriptors I found uses
max_length = 2, so I could not reproduce that exact value with the current paksets.
The revised patch passes the full test suite, including a mutation check where removing the
+1 makes the descriptor-limit test fail.
En la vida todo son vivencias y cada una de ellas nos hace mas grandes,¿Como de grande eres tu? :)

prissi

Incorporated in r12116.

I think, length==1 should only allow for a single ramp though.

victor_18993

Thanks for incorporating it.
I agree about
length == 1. With the current
length - 1 check, a descriptor with
max_length = 1 can span one intermediate tile, while "one" should probably mean a single ramp with no bridge deck between two ramps.
That behaviour comes from the r11608 change rather than this API patch, so I left the patch matching current trunk. I will trace the old and current bridge-length semantics and prepare a separate, small test and fix if the intended meaning is confirmed.
En la vida todo son vivencias y cada una de ellas nos hace mas grandes,¿Como de grande eres tu? :)

victor_18993

I traced it. You are right, and the change was not deliberate: it came in with the bridge builder
rework, r11608, the same commit as the inverted test I sent earlier today.

Before r11608

The search counted steps from the start tile and compared that directly:

uint16 length = 0;
do {
    length ++;
    pos = pos + zv;
    ...
    if(  desc->get_max_length() > 0  &&  length > desc->get_max_length()  ) {
        error_msg = "Bridge is too long for this type!\n";
        return koord3d::invalid;
    }

So max_length was the distance between the two ramps. A descriptor with max_length 1 permitted a
distance of 1, which is your single ramp with nothing spanned.

After r11608

can_span_bridge() compares length-1 instead, with length = koord_distance(end_pos, start_pos):

if (desc->get_max_length() > 0  &&  length-1 > desc->get_max_length()) {

which permits a distance of max_length+1. Every bridge with a limit became one tile longer than its
dat file asks for, in every pakset, and nothing announced it.

How much this actually moves

pak64 has 13 bridge descriptors and 10 of them set a limit, so 10 of 13 have been reaching one tile
too far since January 2025:

MonorailRamp          max_length=1    reaches 2, should reach 1
Schiffhebewerk        max_length=4    reaches 5, should reach 4
MaglevBridge          max_length=5    reaches 6, should reach 5
PowerBridge          max_length=5    reaches 6, should reach 5
ClassicRoad          max_length=7    reaches 8, should reach 7
tb_classic_road      max_length=7    reaches 8, should reach 7
ClassicRail          max_length=7    reaches 8, should reach 7
SteelRail            max_length=10    reaches 11, should reach 10
WoodenRoad            max_length=12    reaches 13, should reach 12
mlm_concrete_bridge  max_length=12    reaches 13, should reach 12

MonorailRamp is your example exactly: today it spans a one tile gap, and with the old rule it only
joins two adjacent ramps.

What convinced me the old meaning is the intended one

Not only your sentence, which on its own could be read either way:

- bridge_desc.h documents get_max_length() as "maximum bridge span";
- the value goes straight from the dat parameter to the descriptor, with no conversion anywhere;
- the tool tip shows it to the player as a length in km, in simtool.cc;
- and every pakset was authored long before January 2025, so they were all written against the old
  rule and none of them were re-tuned when it changed.

The patch

Three places, because the current convention is applied consistently and so the correction has to
be:

- can_span_bridge(): back to "length > max_length".
- renovate(): it counts part_list, the tiles between the two ends, so the ramp distance is one more
  than that. This check did not exist before r11608 - it was written against the new convention.
- the script binding I sent this morning, where I wrote "+1" precisely to match current trunk. With
  the semantics restored that "+1" is wrong, so it goes.

One test changed with it. test_way_bridge_planner_desc_limits asserted the current behaviour, that
a limited bridge reaches get_max_length()+1; it now asserts get_max_length(). That test was mine
and it was pinning the bug, which is a fair reminder that a test written from observed behaviour is
only as good as the behaviour.

One consequence worth stating

Bridges already standing are unaffected: the limit is only checked when building. But renovate()
will now refuse to upgrade an existing bridge that is one tile over its type's limit, and anyone
who built at the limit since January 2025 has some of those. The alternative is to leave the
stricter rule out of renovate(), which would keep the two checks disagreeing. I went for
consistency, but say the word and I will split it.

State

Reference suite 206/206, builds with no new warnings, patch applies to a clean r12116 and builds
from scratch there. MinGW/g++ only, so no claim about MSVC.
En la vida todo son vivencias y cada una de ellas nos hace mas grandes,¿Como de grande eres tu? :)

Andarix

QuoteRevision: 12116
Autor: prissi
Datum: Samstag, 1. August 2026 17:37:20
Meldung:
FIX; (victor_18993) proper script bridge buiding for length > 10 and flat ending tiles
----
Verändert : /simutrans/trunk/src/simutrans/gui/fabrik_info.h
Verändert : /simutrans/trunk/src/simutrans/script/api/api_pathfinding.cc
Verändert : /simutrans/trunk/src/simutrans/script/api/changelog.h
Verändert : /simutrans/trunk/tests/all_tests.nut
Verändert : /simutrans/trunk/tests/tests/test_way_bridge.nut


I'm not sure if bridges should be possible yet where riverbanks are flat.

Screenshot 2026-08-01 203424.jpg