Author Topic: AI Naval Transport Build Priority / Coastal Calculation  (Read 842 times)

0 Members and 1 Guest are viewing this topic.

Offline scient

AI Naval Transport Build Priority / Coastal Calculation
« on: April 11, 2020, 06:07:25 AM »
I've been working on some map auxiliary functions to help me get a better understanding of the Path class and how it interacts with the Map structure. Primarily related to Continents as well as the significance behind the Map region field. I've finally got my head wrapped around how land masses and bodies of water are identified and grouped via region value. Anyway, the inconsistent behavior comes down to how the game looks at the tile radius around a base. The function in question is base_coast(uint32_t baseID) that takes a particular base identifier and returns a region for an adjacent body of water if the base has any coastline or -1 for landlocked bases. It's essentially an extended version of is_coast(x,y,false). If you look at the values inside each array (xRadiusOffset/yRadiusOffset) used by it, you'll see that it radiates out from the starting point (x+1,y-1) clockwise around a tile. If base_coast() passes all the is_coast() checks, you end up reaching this snippet of code.

Code: [Select]
region = region_at(xRadius, yRadius);
int compare = (region >= 127) ? 1 : Continents[region].unk1;
if (compare >= val) {
   val = compare; // value isn't used?
}

It cycles through until it has checked all the radius tiles and then returns the value of the region variable (initialized as -1 before loop). The compare/val variables are unused in any other calculation or logic. Since I don't know what that value inside Continents structure is yet, I can't really make a determination of the meaning behind it, tell if/why it's broken or whether it impacts the issue I noticed.

Edge case scenario: What if you have a base that straddles two bodies of water? In the screenshots below, you have a small body of water to the north of the base with region 92 (region is displayed to left of coordinates with scenario + debug mode active) and the massive ocean that connects all the land masses to the south with a region of 65. Let's run through the execution logic.

First pass of base_coast(), region var is 92.


Second pass, region var is 65.


Third and final pass before it returns, region var is set to 92 and this is what base_coast() returns:


So why does this matter? Well, base_coast() gets called in three instances: transport_base(), naval_base() and base_build(). However, it really boils down to all hooking into base_build() because transport_base() and naval_base() are only ever called by it. The return value doesn't matter for naval_base() since it's treated as a boolean. However, for transport_base() and the direct call from base_build() it actually passes the returned region value into another function sea_coasts().

Code: [Select]
uint32_t __cdecl sea_coasts(uint32_t regionSrc) {
uint32_t seaCoastCount = 0;
for (int i = 1; i < RegionBounds; i++) {
if (sea_coast(i, regionSrc)) {
seaCoastCount++;
}
}
return seaCoastCount;
}

BOOL __cdecl sea_coast(uint32_t regionDst, uint32_t regionSrc) {
uint32_t offset, mask;
bitmask(regionSrc & RegionBounds, &offset, &mask);
return (Continents[regionDst].unk6[offset] & mask) != 0;
}

Still trying to piece together the significance of unk6 inside Continents struct but from what I've been able to discern based on function names and usage it has to do with pathing between land masses across bodies of water. It might actually just be a straight coastal count with some conditions (ocean vs fresh water).

So, if we were to pass region 92 value to it, sea_coasts() returns a count of 1. If we pass 65 to it (the primary ocean), we get a count of 61. Below, you'll see it checks to see whether sea_coasts() return value is > 1 or >= 2 respectively.




Why should we care? Base_build() is a massive function that drives AI and automated base building directives. It's the 3rd largest code base out of all the functions. So in this situation, you might see a base not prioritizing unit production plans or base facilities that take advantage of the access to the primary ocean. I'm not planning on making any changes right now but definitely something to look at in the future.

Offline bvanevery

  • Emperor of the Tanks
  • Thinker
  • *
  • Posts: 6370
  • €659
  • View Inventory
  • Send /Gift
  • Allows access to AC2's quiz & chess sections for 144 hours from time of use.  You can't do without Leadship  Must. have. caffeine. -Ahhhhh; good.  Premium environmentally-responsible coffee, grown with love and care by Gaian experts.  
  • Planning for the next 20 years of SMACX.
  • AC2 Hall Of Fame AC Text modder Author of at least one AAR
    • View Profile
    • Awards
Re: AI Naval Transport Build Priority / Coastal Calculation
« Reply #1 on: April 11, 2020, 01:46:16 PM »
Bloody fascinating, if inexplicable.  I'll be sure to never design an AI this way.  And try not to organically evolve one this way either.

Offline scient

Re: AI Naval Transport Build Priority / Coastal Calculation
« Reply #2 on: April 12, 2020, 05:09:40 PM »
Yeah, this is an issue that is completely transparent to the player. Other than the AI performing "poorly".

Offline scient

Re: AI Naval Transport Build Priority / Coastal Calculation
« Reply #3 on: April 15, 2020, 12:07:47 AM »
Another issue I noticed with base_coast() is that if it passes all the initial is_coast() checks, it increments the iterator as follows: i += (2 - (i & 1)). It then does i++ before starting the loop over. So what happens is, if there is an ocean tile it jumps ahead 2 (i is odd) or 3 (i is even) tiles. This could easily skip over other ocean tiles. It all depends on the water tile layout in relation to base radius starting point.

From the OP screenshots, the start of loop i is 0 with first tile being 86,126. Since this is an ocean square, i becomes 3. So the next check will be directly south of the base at 85,129. However, because this is a land tile it fails the is_coast check and i is only incremented by 1. So now with i equal to 4, it moves on to 84,128 an ocean tile. Now i becomes 7, and you're at 85,125 the final tile.

I think the original purpose of this was to speed up the performance so it kind of does a quick check of East, South, West and then North quadrants of base. There is a similar type performance check in port_to_port() that skips checking the same region over and over. No issues there.

Now, removing this doesn't really fix the underlying issue in base_coast() since it returns the last iteration region variable regardless. I'm going to leave the code as is with some notes with a TODO to revisit it later.

However, the same iterator behavior affects port_to_coast(int baseID, int region). In this case, the return value is a boolean and there is a specific check after the is_coast() checks that could end loop. I'll have to do some testing, but I may remove the iteration behavior and just let the loop check all the tiles unless it finds a match returning early. I think in this case it would generate more consistent results.

port_to_coast snippet:
Code: [Select]
if (sea_coast(region, region_at(xRadius, yRadius))) {
return true;
}
i += (2 - (i & 1));
« Last Edit: April 16, 2020, 08:48:03 PM by scient »

Offline scient

Re: AI Naval Transport Build Priority / Coastal Calculation
« Reply #4 on: April 16, 2020, 08:52:35 PM »
So after my last post, I got my head around what it's trying to do and there isn't an issue with the behavior. Basically, it tries to skip adjacent tiles to speed up performance. So if a tile contains water and the iterator is even, it skips the next two adjacent tiles. If the iterator is odd, it skips one adjacent tile. The idea is that adjacent water tiles will have the same region so why check them multiple times. I left my original post up with strike through it. The issue outlined in OP is still inconsistent behavior tho that will need to be addressed in future.

 

* User

Welcome, Guest. Please login or register.
Did you miss your activation email?


Login with username, password and session length

Select language:

* Community poll

SMAC v.4 SMAX v.2 (or previous versions)
-=-
24 (7%)
XP Compatibility patch
-=-
9 (2%)
Gog version for Windows
-=-
103 (32%)
Scient (unofficial) patch
-=-
40 (12%)
Kyrub's latest patch
-=-
14 (4%)
Yitzi's latest patch
-=-
89 (28%)
AC for Mac
-=-
3 (0%)
AC for Linux
-=-
6 (1%)
Gog version for Mac
-=-
10 (3%)
No patch
-=-
16 (5%)
Total Members Voted: 314
AC2 Wiki Logo
-click pic for wik-

* Random quote

Until quite recently, spider silk had the highest tensile strength of any substance known to man, and the name silksteel pays homage to the arachnid for good reason.
~Commissioner Pravin Lal 'U.N. Scientific Survey'

* Select your theme

*
Templates: 5: index (default), PortaMx/Mainindex (default), PortaMx/Frames (default), Display (default), GenericControls (default).
Sub templates: 8: init, html_above, body_above, portamx_above, main, portamx_below, body_below, html_below.
Language files: 4: index+Modifications.english (default), TopicRating/.english (default), PortaMx/PortaMx.english (default), OharaYTEmbed.english (default).
Style sheets: 0: .
Files included: 45 - 1228KB. (show)
Queries used: 37.

[Show Queries]