Single Player bugs and crashes v43 download - After January 2023

Crashes (at least for me) repeatedly when I hit enter on this save. I can go back to earlier save,crash still happens at this exact turn.

Used v43 + patch + no latitude limits

Can someone confirm if this is a bug, or just a weird "me" problem.
 

Attachments

  • AutoSave_BC-88252.CivBeyondSwordSave
    1.6 MB · Views: 13
Last edited:
Crashes (at least for me) repeatedly when I hit enter on this save. I can go back to earlier save,crash still happens at this exact turn.

Used v43 + patch + no latitude limits

Can someone confirm if this is a bug, or just a weird "me" problem.
It worked for me, I've attached a save with one turn later.
 

Attachments

  • _Test_CTD_Silver_wizard_BC-88099.CivBeyondSwordSave
    1.6 MB · Views: 11
Crashes (at least for me) repeatedly when I hit enter on this save. I can go back to earlier save,crash still happens at this exact turn.

Used v43 + patch + no latitude limits

Can someone confirm if this is a bug, or just a weird "me" problem.
Use Windows vista SP2 compatibility if using Windows 7.
 
Last edited:
I'm experiencing some weird issues with loading a WorldBuilder save regarding culture, it appears to be a buffer overflow (negative culture resulting in huge actual culture)?
1679236772880.png
1679236781675.png
1679236799205.png


Also tile ownership for Fixed Borders doesn't seem to carry over, but that may just be a general limitation.


// Edit
On closer inspection, this only seems to appear if you load the save from the main menu with "Play a Scenario". If you instead load the save from within the World Builder, the culture and ownership appears to be applied correctly. Except for the Fixed Border tiles.
 

Attachments

  • Australia-original.CivBeyondSwordWBSave
    14.7 MB · Views: 13
It worked for me, I've attached a save with one turn later.
Thanks! Made it a couple more turns from that, hope it stays that way. Looks like a "me" problem all along then.

Use Windows vista SP2 cmpability if using Windows 7.
Using Linux, with Steam+proton. Never had issues with repeatable crashes on v42.
 
Using Linux, with Steam+proton.
You are using the Beta Steam version I hope. It is the Unaltered version that this Mod is compatible with. Steam's main version has been altered by Steam. Can and will cause weird problems.
 
Bummer. Is this something that could be fixed in the code, or does the graphics engine not support allocating more than a specific amount of video memory and therefore these units are then just dropped and therefore turn invisible?
Fixed on git, since we are breaking saves on git we do not plan to update SVN in the immediate future unless people call for it and are ok with SVN breaking saves in two or more consecutive versions.
 
"SVN-11548
v43.1.BETA.7571 - 2023-02-19

### All Changes
- Fixed air strike mission unable to identify valid targets. (Toffer)"

Is there a non-SVN way to fix this by any chance? Air units don't identify targets for air strikes at all, so they're unusable for that purpose. Thanks!
 
You are using the Beta Steam version I hope. It is the Unaltered version that this Mod is compatible with. Steam's main version has been altered by Steam. Can and will cause weird problems.
I am now, thanks!

On earlier versions the Beta Steam version would CTD on starting/loading a game, so I didn't use it. Tried again now and seems to run fine. Thanks!
 
"SVN-11548
v43.1.BETA.7571 - 2023-02-19

### All Changes
- Fixed air strike mission unable to identify valid targets. (Toffer)"

Is there a non-SVN way to fix this by any chance? Air units don't identify targets for air strikes at all, so they're unusable for that purpose. Thanks!
You are on v43?

I'll add that fix to the 43.1 patch.
 
I'm seeing some weird behavior with plot culture flipping.
Tiles that have 2+ million of my culture in the next turn suddenly have only less than 100k and therefore switch to the old owner, which has around 100-300k on the same tile. It almost seems like a buffer overflow, i.e. the culture reaching such a high amount that it reverts to zero and begins building up from there again.
In the World Builder I can easily go above 5 million culture though, so not sure what's going on there.

It may also be caused by some memory allocation errors. I have manually edited my savegame/World Builder save to include space tiles, which it before didn't, so now it's 132x200 tiles large, and crashes from time to time when opening the World Builder with a MAF.
The issue here may also be connected to the culture issue mentioned in the linked post above (or not).

I've attached a save (beware, large), in which I also had to disable Revolutions in the World Builder, because it was causing crashes (a separate issue).
On the next turn the Fort on tile 52, 32 will switch to Celtia (Boudica), these are the before and after culture values:

Before:
Me: 2,168,454
Celtia: 162,773

After:
Me: 22,843
Celtia: 163,134
 

Attachments

  • Robert July 7, AD-1931-before-culture-swap-52,39-disabled-revolutions.CivBeyondSwordSave
    15.9 MB · Views: 13
I've found some overflow protection code in CvPlot.cpp, but I couldn't find a way to test or debug this.
Is there a way to do this without having to recompile the dll file?

It shouldn't really trigger in my case, the plot culture is only a little above 2 million when the switches happen.

C:
void CvPlot::setCulture(PlayerTypes eIndex, int iNewValue, bool bUpdate, bool bUpdatePlotGroups)
{
    PROFILE_FUNC();

    FASSERT_BOUNDS(0, MAX_PLAYERS, eIndex);

    if (iNewValue == 0 && getPlotCity() && getOwner() == eIndex)
    {
        FErrorMsg("Trace: Attempting to set plot culture to zero for player that owns the city on the plot");
        iNewValue = 1;
    }

    if (getCulture(eIndex) != iNewValue)
    {
        std::vector<std::pair<PlayerTypes, int> >::iterator itr;

        // Blaze - 24.8.22 - This overflow protection shouldn't be needed with change to make culture
        // in equilibrium states, rather than rising infinitely.
        // Toffer - 08.08.20
        // 4 byte integer overflow protection
        if (iNewValue > 1000000000) // trigger reduction at a billion
        {
            // This player may not yet be in the vector if it goes from 0 to a billion in one go through worldbuilder or something.
            iNewValue /= 10; // So we do this outside the vector loop.
            // Reduce culture by same factor for all players
            for (itr = m_aiCulture.begin(); itr != m_aiCulture.end(); ++itr)
            {
                if ((*itr).first != eIndex)
                {
                    (*itr).second /= 10;
                }
            }
            // This overflow protection will start to break down if a plot is getting 900 million+ culture from a specific player per turn.
            // it reduce 1 billion+ down to 100 million+, this should be an adequate overflow protection in this case.
        }
        // ! Toffer
[...]
 
I've found some overflow protection code in CvPlot.cpp
That bit you found is probably not responsible (divides by 10 not 100, and should impact all), but I believe there is another setCulture100 style code used elsewhere that could be responsible. My comment is outdated too lol because equilibrium is now an option.

Another option could be culture decay if you're playing with RCS on svn (not right thread tho?); should the tile suddenly become "further" from a city (forests grow, lose intermediate improvements), you'll lose culture pressure very quickly.
If you are on base 43, there's been enough change wrt culture on svn that I'm not sure the issue will persist...

If you want to edit the dll, you can v easily recompile it by using the automatic tools BillW setup: https://github.com/caveman2cosmos/Caveman2Cosmos/wiki/Developer-Guide
 
That bit you found is probably not responsible (divides by 10 not 100, and should impact all), but I believe there is another setCulture100 style code used elsewhere that could be responsible. My comment is outdated too lol because equilibrium is now an option.

Another option could be culture decay if you're playing with RCS on svn (not right thread tho?); should the tile suddenly become "further" from a city (forests grow, lose intermediate improvements), you'll lose culture pressure very quickly.
If you are on base 43, there's been enough change wrt culture on svn that I'm not sure the issue will persist...

If you want to edit the dll, you can v easily recompile it by using the automatic tools BillW setup: https://github.com/caveman2cosmos/Caveman2Cosmos/wiki/Developer-Guide
Thanks for pointing me at the developer guide.
I think I was able to pinpoint the problem to the decayCulture() method.

C:
void CvPlot::decayCulture()
    [...]
            setCulture(
                ePlayerX,
                std::max(
                    getPlotCity() && getOwner() == ePlayerX ? 1 : 0,
                    // default: current culture * 90% then - 1
                    getCulture(ePlayerX) * (1000 - decayPermille) / 1000 - decayFlat
                ),
                // Don't need to update borders, update culture is called next in doCulture
                false, false
            );

Here seems to happen an in-place buffer overflow if the current plot culture value is above 2,147,483 (or something close to it). The multitplication by 1,000 seems to trigger this, even though it's immediately divided by 1,000 again. My solution was to simply reverse the order of operation, so e.g.
C:
getCulture(ePlayerX) / 1000 * (1000 - decayPermille) - decayFlat
This seems to have fixed it in my very limited testing (a single turn where I concentrated on one plot).

In the latest SVN version the decayCulture() method was removed, but I assume it's still affected by the same problem:
C:
void CvPlot::doCulture()
[...]
            if (getCulture(ePlayerX) > 0)
            {
                if (GC.getGame().isOption(GAMEOPTION_CULTURE_EQUILIBRIUM))
                {
                    // By limiting decay to avoid 2+ -> 0, we can ensure that putting 2 culture on a tile will always be above 1 turn decay
                    const int iIsOverOne = getCulture(ePlayerX) > 1;
                    if (isInCultureRangeOfCityByPlayer(ePlayerX))
                    {
                        setCulture(ePlayerX, std::max(iIsOverOne, getCulture(ePlayerX) * (1000 - decayPermille) / 1000), false, false, true);
                    }
                    // Decay 15x faster (to 45% at default speeds) if outside of city control in equilibrium, since we can't immediately set to unowned when negative
                    else
                    {
                        setCulture(ePlayerX, std::max(iIsOverOne, getCulture(ePlayerX) * (1000 - 15 * decayPermille) / 1000), false, false, true);
                    }
                }
                else if (getCultureRateThisTurn(ePlayerX) < 1 && (!getPlotCity() || getOwner() != ePlayerX))
                {
                    setCulture(ePlayerX, std::max(0, getCulture(ePlayerX) * (1000 - decayPermille) / 1000), false, false, true);
                }
            }


By the way, just out of curiosity, is there a reason why the getCulture() value isn't stored/cached in its own variable? I'm not familiar with C++ coding techniques.
It at least made debugging a bit harder since there were multiple calls with the same return value that appeared in my log file.
 
Your current solution as posted will cause issues in the reverse direction, as culture under 1000 will evaluate as e.g. 50 / 1000 = 0, then 0 * (1000 - x) = 0, then 0 - decayFlat = negative culture. But I think you are right re: inplace overflow issue.
By the way, just out of curiosity, is there a reason why the getCulture() value isn't stored/cached in its own variable? I'm not familiar with C++ coding techniques.
Tl;dr: Civ4 was written before stackoverflow was a thing, did not have a good codebase to begin with, then add on a decade and a half of hobbyist coders.... don't necessarily take any structure here as 'good'. See the bolded text here lol... https://github.com/caveman2cosmos/Caveman2Cosmos/wiki/CPP-Style-Guide
For the snipped posted, I guess you could do that and save one or two calls each loop as ePlayerX cycles, if that's what you're asking. I'm not a skilled cpp dev tho, so 🤷‍♂️

To fix, yeah would probably just need to check to make sure value isn't more than 1000 under MAX_INT or some other random const, not too bad. If Toffer doesn't get to it first, will see if I have energy to do myself this weekend, or you can submit a pr if you want ;)
 
Indeed, that would introduce another issue.
I've created a pull request, but I was not able to test the generated DLL. I haven't successfully figured out yet how to run the git version along with my current running version.

 
Should probably use a 64 bit integer there for the calculation, convert the result back again at the end, good catch @sp00n.

Edit: Something like this:
C++:
    {
        const int decayPermille = GC.getTILE_CULTURE_DECAY_PERCENT() * 1000 / GC.getGameSpeedInfo(GC.getGame().getGameSpeedType()).getSpeedPercent();

        for (int i = 0; i < MAX_PLAYERS; i++)
        {
            const PlayerTypes ePlayerX = static_cast<PlayerTypes>(i);

            if (getCulture(ePlayerX) > 0)
            {
                if (GC.getGame().isOption(GAMEOPTION_CULTURE_EQUILIBRIUM))
                {
                    // By limiting decay to avoid 2+ -> 0, we can ensure that putting 2 culture on a tile will always be above 1 turn decay
                    const int iIsOverOne = getCulture(ePlayerX) > 1;
                    if (isInCultureRangeOfCityByPlayer(ePlayerX))
                    {
                        int64_t iCulture64 = getCulture(ePlayerX);
                        setCulture(ePlayerX, std::max(iIsOverOne, static_cast<int>(int64_t(getCulture(ePlayerX)) * (1000 - decayPermille) / 1000)), false, false, true);
                    }
                    // Decay 15x faster (to 45% at default speeds) if outside of city control in equilibrium, since we can't immediately set to unowned when negative
                    else
                    {
                        setCulture(ePlayerX, std::max(iIsOverOne, static_cast<int>(int64_t(getCulture(ePlayerX)) * (1000 - 15 * decayPermille) / 1000)), false, false, true);
                    }
                }
                else if (getCultureRateThisTurn(ePlayerX) < 1 && (!getPlotCity() || getOwner() != ePlayerX))
                {
                    setCulture(ePlayerX, std::max(0, static_cast<int>(int64_t(getCulture(ePlayerX)) * (1000 - decayPermille) / 1000)), false, false, true);
                }
            }
        }
    }
 
Last edited:
Top Bottom