yairm210 / Unciv

Open-source Android/Desktop remake of Civ V
Mozilla Public License 2.0
8.49k stars 1.57k forks source link

Possibly Errorenous Civ AI Policy Selection Logic #10857

Closed chris03-dev closed 8 months ago

chris03-dev commented 10 months ago

NOTICE: The initial description is outdated, read this link instead. Still on Windows though.

Is there an existing issue for this?

Game Version

4.9.15

Describe the bug

Civ AIs seems to select the same policies if a mod is detected. Quite easy to trigger, and also possibly game-breaking if you have been relying on strategies that depend on Civ AI policy selection behavior. This mostly started as an attempt to find a cause for another issue in the mod I was using.

Steps to Reproduce

  1. Open the file manager of your choice
  2. Go to the root Unciv directory, where you may find the executable binary and/or the JAR file
  3. Go to the mods/ directory if it already exists. If it didn't exist, create the mods/ directory yourself.
  4. Create a directory in the mods/ directory.
  5. Open the Unciv application.
  6. Select "Start new game", and for best results:
    • Select your Civ as Spectator
    • Select your AI Civs as Egypt, Denmark, France (preference for Tradition policies), Rome, Russia, and Sweden (preference for Liberty policies)
    • Set the Game Speed to Quick
    • Set the map size to the smallest possible
  7. Enable AutoPlay and watch the bug unravel in the Politics tab.

Note: If you have at least one mod installed, you might be already experiencing this issue. To fix this, put all your mods in a backup directory, and then remove all of the files and folders within the mods/ directory (it is not necessary to remove the mods/ directory itself).

Screenshots

My configuration: image


Running three tests of 50 turns each without a folder within the mod/ folder: image

Test 1: image

Test 2: image

Test 3: image


Now with a folder within the mods/ folder: image

Test 1: image

Test 2: image

Test 3: image

Link to save file

No response

Operating System

Windows

Additional Information

No response

SomeTroglodyte commented 10 months ago

Slightly unclear: Did you select the empty bogus mod for that second set of tests or not?

Invalid or not, any folder is interpreted as mod

Of course. We own the definition what the mods folder means and is meant for, thus we can declare that to be so. E.g.: Should you create a folder with characters not allowed in mod names (like '-'), you're on your own, if Unciv breaks it's your fault not ours - Unciv itself would not have created such a folder.

How did this even manage to slip by

I for one don't care one whit what the AI does as long as it lets me win in peace. 🤪 It requires someone very ~obsessive~ observant to notice such intricacies - thank you.

So... Since this is hard to believe from a causal look at the code, a little collection of tidbits...

Running any minimal game without mods folder to the point any AI starts choosing a policy, then display the priority matrix gives: image The same with an empty mods/Abracadabra folder: image The priority matrix is identical.

So, let's look at "Egypt, Denmark, France (preference for Tradition policies)"... all "preferredVictoryType": "Cultural", their priorities for the two branches are both 10. Random happenstance? Getting one of those screenshots would be 12.5%, but three in a row? 0.2%... Ah, but branch choice has no random component in code! "Choose the branch with the LEAST REMAINING policies, not the MOST ADOPTED ones" would mean if Liberty had more member policies then that would already explain that it must always be chosen... But no, both have 6.

That leaves minBy - says "Returns the first entry yielding the smallest value of the given function" - meaning it's stable and with all selectors the same value it will deterministically return the first one. candidateCompletionMap is ordered under the hood - but by what? It's a filtered unordered HashMap. Order is not necessarily random but depends on JVM state and thus it is nondeterministic from a coder perspective...

To convert HashMap-based order to true random choice when all other factors are equal, one could code candidateCompletionMap.filter { it.value == candidateCompletionMap.values.min() }.keys.random() in line 300 instead of the minBy - but that doesn't feel quite right, these AI decisions should be much fuzzier than they are. Keep all candidates, make (modded) priority, the completionist preference, and other potential AI factors fuzzy contributions to a weight, then randomWeighted - would feel nicer. To me at least.

chris03-dev commented 10 months ago

Slightly unclear: Did you select the empty bogus mod for that second set of tests or not?

In my tests, yes. But I do have to note that earlier observations involving this bug has happened with the mods I already installed.

Side note, I haven't really delved into the Kotlin programming language very much, but I'll try to understand what the code meant.

chris03-dev commented 10 months ago

Update: The issue is harder to pin down than I thought. I found out that the conditions I set up to reproduce this issue (i.e. the bogus folder) sometimes works, sometimes not. Same goes for any valid installed mod. Unfortunately, I'll have to call it a day for now since it's getting late, but I'll try to look further into this soon.

I apologize and retract my previous harsh words if I have caused you any inconvenience in the meantime, or if this randomness is actually intended behavior.

SomeTroglodyte commented 10 months ago

apologize

No need. Opportunity for tongue-in-cheek yes, harsh no. And a puzzler. And after looking into it a valuable discovery - though at first of course I thought 'this gal/guy is three months minus two days off with their calendar'[^*]... :upside_down_face: :zany_face: :smile_cat:

Translation of that tech mumbo jumbo up there: hard to pin down - exactly, because the result depends on the internal working of a lookup mechanism. One more string on the stack can tip the balance. Which can be your mod name. Could even be OS state dependent.

[^*]: meaning april fool's day

chris03-dev commented 10 months ago

Alright, I tried to run a few more tests without a mod... I think I might have overstated the role of a mod folder leading to this issue, since the so-called conditions I set are not actually necessary for the game to bug out like this. I tried a few tests without any mod, and it occurs all the same.

The only concrete information I could get out of this whole thing is that the Civ AI policy-selection behavior is consistent (and by that, I mean that Denmark, Egypt and France chose Liberty or Tradition together every single time) whenever I start a new game, that is, until I close the game itself. Whether the first 3 nations mentioned will have different policy selection behavior is now anyone's guess when I boot the game up again.

Now I only have one question... is this policy selection behavior supposed to be a bit random? Because from what I can gather from your earlier message, the selection process should be deterministic.

If so, it should be random on every new game, instead of being random on every Unciv startup. Otherwise, I think there is a bigger problem than I could try debugging on my own.

SomeTroglodyte commented 10 months ago

supposed to be a bit random

No idea - there's no random call in the code (for branch selection, for policy selection once a branch is started there is). But having the result, when all other determining factors are equal, to be dependent on java library internals and/or system state is entirely unsatisfactory. I say - personal opinion - that should not be the case, and unless we move to something more fuzzy over a larger scope (for more variety), a randomized choice in that last step is the only alternative. Or distribute priorities in such a manner that there never is ambiguity in branch choice - which modders would of course be able to break.

chris03-dev commented 10 months ago

Alright, I found out that one of the mods did break policy selection. I just made a fix by adding weights to certain victory types and now it works as expected, granted that the looming issue regarding random policy selection per game session still pops up sometimes.

Speaking of which, is policy selection already dictated for the entire session by the time the game is booted up, or whenever it enters the world screen for the first time? Or is it supposed to be calculated every time "New game" is selected?