Infinite Loop BTS 1.01

Fuyu's Better BUG AI, aka BBAI 1.02b, is a newer/better version of BBAI than the one available here, but alas it still does not include Affy's kludge/hack/fix from this thread. I added this fix to MongooseMod's dll not long after it was posted, and I do believe it prevents any more infinite loops (as Affy said it did in his mod, A New Dawn, as well).

But yes, I'm sure there are probably side effects since it's obviously not a perfect solution. I have not looked into this situation yet myself since I was happy just to get a working solution, but I'll be getting back into the SDK code again at some point here, so I'll take a look if I have time.

Sure was nice when JDog, Fuyu and Afforess were still active... now I have to do everything myself. Sigh. ;)
 
Thank you, LunarMongoose, for your pointer (as Affy said it did in his mod, A New Dawn, as well) ... well, I read the thread "[BTS] Rise of Mankind - A New Dawn" since October last year ... still found no mentioning about infinite loops ... so sadly no answer to my initial question that way.

Afforess, would you please be so friendly and write something about your hack? As I understand it, the code loops through the list of units, and if the current one happens to be the suspicious one (which is the current one of the time before) it counts up and finally forces finishMoves ... this surely is right if there is just 1 unit in the list (the last), or the same unit is taken all the time ... is this the case?
If the hacked passage is executed with 2 alternating units it seems to reset the counter forever ... No?
(unit A joins unit X, and unit B joins because of that too unit X, then because of that unit A leaves unit X, and unit B leaves because of that too unit X ...)

[edit: I use jdog5000 latest revision 597 DLL, which he gave no version name, but could be named BB(tS)AI 1.02c ... (a few sources were changed by jdog5000 _after_ Fuyu's BetterBUGAI 1.02b)]
 
I found an infinite loop bug the other day related to a group of 2 units where one was wounded and one was not.

The group would hit the AI_heal code, and the wounded unit would be split off to heal. But then, the unwounded unit would look for someone to join up with, find the wounded unit right there and form a new group, which would then split off the wounded guy... repeat ad infinitum...

My solution was to have the group heal if there were only 2 units, instead of trying to split out the one wounded guy.

This issue seemed to manifest itself during the attackCity move, but it may have also caused problems elsewhere.
 
My solution was to have the group heal if there were only 2 units, instead of trying to split out the one wounded guy.
Congratulations, Tholal, that you are able to isolate such a problem and have a nice individual solution, probably you don't mind to squash another infinite loop bug now and then ... :lol:

Do you still have a savegame and if so, can you please post it here?

[edit: what if same happens with 2 wounded or unwounded units and 1 unwounded / wounded? ...]
 
This was in my modmod, so a savegame wouldn't be useful. I'll try and dig out the code later to show where the issue was and where I fixed it.

Good questions about the other potential scenarios. I don't really know. I just happened across this one issue while debugging and managed to fix it. That was just a few days ago, so I can't say for sure that it solved everything, but I haven't had any infinite hangups since then.
 
I'll try and dig out the code later to show where the issue was and where I fixed it.
That would be great! Thanks.
Such individual solutions are fitting far better and are preferable anytime, because they provide less "collaterally damage" then a general sledgehammer ... I'd like to incorporate your solution for this part of the problem.

so I can't say for sure that it solved everything, but I haven't had any infinite hangups since then.
My tip: it solved the problem you described.

I fear nevertheless a general parachute will be necessary. If the possible constellations for group / split off cycles would be infinite, we would need infinite individual infinite loop patches. :p

[edit: Was Afforess' hack part of your source?]
 
Really excited to see some posts on this thread. Of course, my savegame is still attached earlier in the thread. I would be thrilled if a fix happened! I haven't played Civ IV since these problems happened, it was just so demoralizing to have a really good game spinlock like that.
 
OK. Here's a more detailed report of the issue I found.

In the ::AI_heal() function, wounded units in a stack are booted from the stack when the stack is in a city.

In ::AI_groupMergeRange(), units look for a group of equal size or larger of the same AI type to group with.

So, in the scenario with a group of two units flagged with the same AI where one is wounded, if the second unit tries to regroup with the first unit you end up in an infinite loop.

Though it's probably not the proper long-term solution, I got around this issue by altering the following code in ::AI_heal()

Code:
			CvUnit* pUnitToHeal = aeDamagedUnits[iI];
			pUnitToHeal->joinGroup(NULL);
			pUnitToHeal->getGroup()->pushMission(MISSION_HEAL);

and wrapped the joinGroup(NULL) statement like so.

Code:
			CvUnit* pUnitToHeal = aeDamagedUnits[iI];
			if (pGroup->getNumUnits() > 2)
			{
				pUnitToHeal->joinGroup(NULL);
			}
			pUnitToHeal->getGroup()->pushMission(MISSION_HEAL);

This makes the group of two heal together rather than splitting and rejoining.
 
Interestingly, this might be exactly my same issue. It's been a while, but I think that I had damaged some of his ships, and he dropped troops on my mainland and then was (presumably) going to head back to safe territory.
 
Afforess, would you please be so friendly and write something about your hack? As I understand it, the code loops through the list of units, and if the current one happens to be the suspicious one (which is the current one of the time before) it counts up and finally forces finishMoves ... this surely is right if there is just 1 unit in the list (the last), or the same unit is taken all the time ... is this the case?
If the hacked passage is executed with 2 alternating units it seems to reset the counter forever ... No?
(unit A joins unit X, and unit B joins because of that too unit X, then because of that unit A leaves unit X, and unit B leaves because of that too unit X ...)

Not Quite.

The Game Loops through of all of the units several times each turn, trying to determine if they should move (land units move before air units...etc, something like that), but if it get's an infinite loop it keeps looping through units, finding a unit(s) who have NOT used up their moves, and tries to make them move. But the unit fails to move for whatever reason, and it loops again. I just decided to keep track of the last unit that was looped over and updated, and count the number times it passed. If it looped over the unit too many times (my number was arbitrary), it reset the units moves and moved on.

Seemed to fix all the infinite loop issues. Honestly, when I came up with the fix:

A. Everything I knew about programming had been self-taught. (Not the case anymore)
B. I was going to quit modding in a few weeks.
C. I wasn't inclined to learn exactly how SelectionGroups worked, the whole thing seemed a convoluted mess.
D. And my solution (miraculously) worked, and consequences be damned.

If you find a better solution that covers all the cases, let me know.
 
@Tholal
Though it's probably not the proper long-term solution, I got around this issue by altering the following code in ::AI_heal()
Thanks for sharing what you have found. :goodjob: I suspect with 3 units of same AI and 1 or 2 wounded you first loop forever and then have 3 heal together :D ... until the whole army heals if one is wounded. No.
>>> Ideally we could make the AI aware of conflicting rules for a given object and have it weight this rules ... ideally.
In this individual case my first intuition for a patch is: in groupMergeRange() to _avoid to group with wounded_ units when in a city. Would mean let both standalone rather than splitting and rejoining, but I have to think more about it.
"Don't block a unit ready to fight, let the wounded safely heal in city."

@rramstad
this might be exactly my same issue ... I think that I had damaged some of his ships
Yes, but the point where the animation stops, is the last move WITHOUT problems ... the move which "hangs", is not shown, because the AI can't find that move ...
The problem in your save lies in LAND units, being not wounded & being no cargo, no Navy problem at all ...
Don't hold the breath, but you'll play it again.

@Afforess
Thanks you very much for your explanation ...
The Game Loops through of all of the units several times each turn
this was the key for me, I asked me again and again, why it would loop with the same unit ...
finding a unit(s) who have NOT used up their moves, and tries to make them move. But the unit fails to move for whatever reason, and it loops again
... and this can be stopped with pLoopUnit->finishMoves()! Great finding!!
A. Everything I knew about programming had been self-taught.
Isn't that 'state of the art' aka known as "training on the job"?? ;)
C. I wasn't inclined to learn exactly how SelectionGroups worked, the whole thing seemed a convoluted mess.
Me too! Thanks again for telling the most important about its structure.
If you find a better solution that covers all the cases, let me know.
At least in theory there can be more complex problems, which would eventually need a counter in every unit ...

I have a solution (working miraculously too) that covers all the cases, but I still try to tone down a bit the collaterally damage :lol:

Can you tell, what your hack reports for rramstad's save (post #3 in this thread)? Just 1 unit or several? (There is a suspicious group of 6 units, but only 1 cannon has left over moves)

@everybody
I am very interested in "infinite loop" save files using just jdog5000's BBAI.
 
@Afforess
Now as I have installed the C++ IDE and inserted your code fragment, I recognize that your hack was probably not an attempt for a general solution. My fault.

@rramstad (aka Bob2.sav in post #3)
Good news: the 'infinite loop' is finite, this uncovers the bad news: Los Angeles is seriously threatened, you could use some heavily entrenched infantry ready for rugged defense ...

I had not really expected that a general solution for this kind of issues could be handled entirely with Python ... but it can. Look for the Tester.py file in your Better BTS AI Installation, default would be:
C:\Program Files\Firaxis Games\Sid Meier's Civilization 4\Beyond the Sword\Mods\Better BTS AI\Assets\Python\Development
Make a save copy and then overwrite it with the new downloaded Tester.py version.

Usage:
Shift + 0: force all units of all players to end turn (shift zero, not ohh)

During my test, the next couple of turns (3) the procedure had to be repeated (caused by something unresolved in the Roman army on tile [40,30]),

Spoiler :
3 Roman Grenadiers in Warwick. With ctrl-MouseoverFlag can in debug mode be seen a group of 2 Cannon, Knight, Musketman & Grenadier as well as a standalone Longbowman & Cannon. 2 Grenadiers seem to group & split off. No unit wounded. No unit has cargospace or is cargo. (On the city tile is also an English Spy.)

--------------- Roman Empire (Player 3) 208 units
38~ [40,30] Grenadier (True)24297619-2891814: 0 ----
43~ [40,30] Grenadier (True)24199390-2900011: 0 x
45~ [40,30] Longbowman (True)22208741-5013549: 0 x
76~ [40,30] Grenadier (False)18317536-3358796: 0 g ----
86~ [40,30] Knight (False)18317536-1843286: 0 g ----
111~ [40,30] Cannon (False)18317536-4817007: 0 g ----
175~ [40,30] Cannon (True)18309346-4858031: 60 x
179~ [40,30] Musketman (False)18317536-4120755: 0 g ----
181~ [40,30] Cannon (True)18317536-4784309: 0 G ----
--------------- English Empire (Player 6) 60 units
46~ [40,30] Spy (True)4620357-1097784: 0

but then the problematic constellation disappeared ... and so Los Angeles got lost by the way :D (maybe the offending 'infinite loop' unit simply Changed its Agenda from ATTACK_CITY to DEFEND_CITY) :D:D:D


BetterBtSAI: """We cannot reload like you, but we can loop forever ... give us Los Angeles ... resistance is futile!"""
.
 

Attachments

  • Tester.zip
    1.3 KB · Views: 270
rom my experiences it seems that these infinite loop issues are related to grouping. Specifically, one particular set of units that keep grouping and ungrouping with each other ad infinitum.
 
@OneFootInThe...
I knew one part of my hack _IS_ good ... :king:

@Tholal
yes, good description!
Somewhere I read about a "transporter" issue, jdog5000 described: "the ones in the past required very specific unit combinations and circumstances, like Galleys joining Galleon groups" ... so I first thought its about naval units and cargo ...
The case you found is about wounded units. Afforess' hack indicates an issue about units trying to perform a "groupMove". rramstad' case is different again.

So what? Several, (many?) issues, all with a very low probability, all should be ideally solved by analyzing & recoding. Ie. another individual solution ... (btw. makes the code more complex, which is at least part of the problem) ...

Potentially those 'combinations and circumstances' are infinite. Kind of a rabbit and hedgehog story. So I tried to cure it in a general way. First I thought about an counter in every unit (eg. every unit can group just, say, 5 times. The 6th time it may not, what breaks the circle) ... but that costs a bit performance all the time. And one has to find the right central spot in the C++ code ...

So I settled with this Python solution - it is simple, just costs performance when "ne rien va plus" anyway ... And it solves the problem which the PLAYER has.

Drawback are the collaterally damages ... to see what I mean just fire "shift0" when you have moved half of your units and the other half waiting for orders.

Of course this can be stripped down to 1 unit (of several, forming the group), which probably has already used up all its moves, but this needs time & analysis ... Do you want to invest these?
 
I have to admit, compared to the other hacks, I really like your python solution. What I have to ask though: why would you finishMoves for all units of all players, instead of targeting only the currently active player?
That said, does finishMoves hurt a player that isn't currently active? I'm not sure.
Code:
int [COLOR="Green"]/*PlayerTypes*/[/COLOR] CyGame::getActivePlayer() 
{
	return m_pGame ? (int)m_pGame->getActivePlayer() : -1;
}
In Python it's probably game.getActivePlayer(), so this could work:

snip
No, that doesn't work.
 
That said, does finishMoves hurt a player that isn't currently active? I'm not sure.

Yep. He probably did not notice, since the human is player 0 in SP, and goes first, but it's definitely hurting other AI's.
 
@Fuyu
compared to the other hacks, I really like your python solution
because it is targeting a general solution or because it is in Python?

I'd prefer an automatic!! general solution in C++ (1 group/ungroup counter in every unit @right central spot), but now thats clearly beyond my abilities - all in all I may have looked only 4-5 hours at the CIV4 C++ code :(
why would you finishMoves for all units of all players
1. This is the simple, 'surely work without explanation' solution for upload
2. it is just for a first quick test: if THAT doesn't work, I have to rethink something
3. it is mostly a copy of shift1, which I did first in order to analyze attributes of suspicious units ...
btw, complete list of Tester keys [
Ctrl+Shift+S "show Stranded units by player", Stranded units sind zB Landunits auf kleiner insel ohne aufgabe
### Shift + Alt + [0-5]: force all units of Domain # of all players to end turn. # is € of {0...5}
### Shift + Alt + 8: shift iCivRegister to lower: Player 0-9
### Shift + Alt + 9: shift iCivRegister to upper: Player 10-19
### Alt + [0-9]: force all units of Player # to end turn. # is € of {0...19}. 0 is human
### Shift + 0: force all units of all players to end turn
### Shift + 1: show all units of all players in PythonDbg.log
### Shift + 2: reset iPlayerIndex, iUnitIndex
### Shift + 3: increment iPlayerIndex
### Shift + 4: increment iUnitIndex
### Shift + 5: increment iUnitIndex by 10
### Shift + 6: show current unit
### Shift + 7: force current unit to end turn
### Shift + 8: decrement iUnitIndex
### Shift + 9: force current unit to end turn & increment iUnitIndex
]
Your proposal: game.getActivePlayer()
I have to look into it again - AFAIR: months ago I tried to establish something depending on the currently active player ... but it didn't work because it always returned the HUMAN player ... despite what one could think seeing the function name ... AFAIR!! I'll look into it.

@Afforess
Yep. He probably did not notice, [...] but it's definitely hurting other AI's.
C'mon, what do you expect from a function commented "force all units of all players to end turn"?

Would you please describe in a few words the issue which led to your hack?
 
I like the solution because it is only active when it's needed. The grouping counter might work but costs performance all the time, and it's still a hack and no solution.
If activePlayer returns the human then maybe there's a different command to determine whose turn it is? I would hope so, that would make this solution almost elegant.
 
Top Bottom