Inflation

So wait, rushing something like a lumber camp has the same effect as rushing some 3000 :hammers: building?

Without looking at the code I couldn't say for sure. ls612 can take a look and answer that. The way rush inflation is handled as a SOURCE of inflation has not been changed though (so it's whatever it was in ROM/AND), only how that is transformed into costs.

Edit - ok I had a quick look before I leave in an hour's time. Hurry inflation works like this:
  • Every time you hurry ANYTHING it increments a count of hurries
  • Every N turns (determined by global define "HURRY_INFLATION_DECAY_RATE", modified by the game speed) it decrements the count
  • The current count is exactly the inflation percentage used in cost calculations from hurrying (so every hurry is 1%, but it decays over time)

This has always been the way it worked, so changing it is not super urgent, but I agree with your (implied) comment that it should be changed (which I'll dio when I get back from vacation). The changes I propose are:
  • The amount it is increased by from any given rush should depend on how many hammers you are rushing in some way (the exact way has to be thought about carefully though, or else it won't scale correctly through the game, as things get more expensive in base costs)
  • The decay should be percentage based (so x% per turn rather than a straight decrement every N turns)
  • The decay rate should be dependent on map size (larger maps mean more cities, which mean more hurrying), or possibly normalized by your actual city count (so it's average hurry rate per city that drives it [this is my preference])
 
Ok, I have completely reworked how inflation is applied, in a way that should address the way it currently scales out of control the deeper you go into the game.

I'm not going to claim that the new inflation system is in any way a model of real world inflation, but I believe it fulfills the game purpose and has the right gameplay characteristics.

Before I describe the mechanism in detail, these are the characteristics I wanted:
  1. Costs due to inflation should increase the longer you spend with high inflation modifiers (i.e. - it kicks in progressively)
  2. Costs should not escalate indefinately. They should cap at some (reasonable) point
  3. Arbitrary contributors to inflation should be supported, so all current sources continue to feed into the new mechanism without requiring changes to them
  4. High inflation costs should be addresable by switching to a lower inflation environment (usually civics). This should (progressively, not instantly) reduce your costs, not just arrest their growth
  5. It should be cheap to calculate
  6. The exact parameters of the process should be tweakable via global defines since I am going on holiday and it may need tweaking!

So this is how it works:
  • The game (already, so in existing saves) has the cost modifier due to inflation. Cost calculation continues to use this as now. The new mechanism just changes how this value is modified over time.
  • The game already has a routine to calculate your current inflation 'rate'. Again I am leaving this unmodified (well, I reinstated hurry modfiiers, which ls612 had removed as an interim position, now that the entire mechanism is more clement), so that the various sources of inflation continue to act.
  • Finally, each turn the game ran a calculation that took the current modifier and the current inflation rate, and adjusted the modifier. This is where all the changes are (it's very self-contained):
    • Previously the calculation just took the current cost modifier and applied the current rate to it to get the next turn's modifier (it actually applied 100th of the displayed inflation percentage every turn). This is a ratchet unless you can actually get negative current inflation, and for constant inflation leads to an exponentially growing cost modifier over time.
    • In the new system it takes the current inflation rate and compounds it up for a fixed window of time (aka turns) or the number of turns since the game started (minus the gamespeed's 'inflation offset', which is the turn number at which inflation starts) if that is lower. This window is the first config parameter. If not present in the global defines it defaults to 200. Its name is "MAX_INFLATION_EFFECT_WINDOW". This gives a cost modifier that would be the one we wanted if the current inflation rate had been in force forever. This is your equlibrium cost modifer (i.e. - the one you'll end up with eventually if nothing ever changes from then on)
    • It then adjusts the current cost modifier towards the equilibrium one it just calculated in the previous bullet, by one Nth of the difference between them. This N is the second tunable parameter. If not present in the global defines it defaults to 50, but after reading that value it is adjusted by the game speed modifier, so 50 is actually more like 200 on snail - it is a measure of how long it takes your cost modifiers to move to the equilibrium position that your current inflation rate (if maintained) would imply. The effect of this is to cause your costs to drift up or down towards their new equlibrium level as the inflation environment changes. The name of this global define is "INFLATION_RATE_MOMENTUM"
Since I provided default values in the code (as above) I have not bothered adding the global defines, but if you need to override it based on tuning experience they are there for that purpose.

Note - I have also not attempted to change any descriptions in the pedia since I'm not sure what needs changing for concept entries like this.

Finally, I have made a small change to the financial advisor - the hover text for the inflation entry will now say "stable", "improving", or "worsening" in the line that tells you your current rate - this reflects whether your costs are currently moving down or up (towards the current equilibrium level), so when you first switch to a higher inflation civic (say) it will initially say 'worsening' an your costs will ramp up turn to turn. As you get close to the end of the calculation window period (circa 200 turns on snail) it will change to 'stable' and your costs won't ramp any further (well much - I change it to reporting stable slightly before it reaches the equilibrium point)

Expect this to hit the SVN later today...

I am going to make the Gamespeed modifier for INFLATION_RATE_MOMENTUM it's own tag, as opposed to it being linked to the Improvement rate modifier. This way it can be tweaked more easily later on.
 
The changes I propose are:
  • The amount it is increased by from any given rush should depend on how many hammers you are rushing in some way (the exact way has to be thought about carefully though, or else it won't scale correctly through the game, as things get more expensive in base costs)
  • The decay should be percentage based (so x% per turn rather than a straight decrement every N turns)
  • The decay rate should be dependent on map size (larger maps mean more cities, which mean more hurrying), or possibly normalized by your actual city count (so it's average hurry rate per city that drives it [this is my preference])

Shouldn't that be the gold spent rather than hammers earned? The ratio of gold spent to hammers earned is dramatically higher on one and two (I think) turn rush jobs than it is later.
Just trying to understand the effect here-does the hurry modifier then add into the equilibrium inflation figure? If so you wouldn't see a spike in inflation on the turn following a rush, but a gradual increase until the equilibrium + decaying hurry modifer comes down to meet the now mildly increasing current turn inflation (unless e+d is already less in which case currently improving inflation would just improve less quickly). I guess I had in mind that any hurry would pop the current inflation value a bunch (giving immediate feedback) and then the normal reversion towards equilibrium would cure it.
If the hurry inflation modifier per hurry is (eventually) designed to take into account the current income of the civ when the hurry occurs, I don't think you need to adjust the cure rate of it for size.
 
Well, I've done some looking at the extreme negative inflation issue chmoosi pointed out to me, and something isn't right here. This function

PHP:
int CvPlayer::calculateInflatedCosts() const
{
	int iCosts;

	iCosts = calculatePreInflatedCosts();

	iCosts *= std::max(0, (calculateInflationRate() + 100));
	iCosts /= 100;

	return iCosts;
}

is supposed to return the final costs after inflation, but when I stepped through the code, I found that the preInlfatedCosts were 379, but after going through the next two operations, iCosts went down to 11! (well, they went up to 1179, then were divided by 100 and went down to 11.) This is probably due to the fact that calculateInflation rate returns this

PHP:
int CvPlayer::calculateInflationRate() const
{
	int iRatePercent = (m_accruedCostRatioTimes10000 == -1 ? 0 : (m_accruedCostRatioTimes10000-10000)/100);

	return iRatePercent;
}

and the debugger said that the value of m_accruedCostRatioTimes10000 was 278. This would make the calculateInflationRate function return ((278-10000)/100), or about -97.2. This, when plugged in to the above function, would lead to said negative inflation.

The problem is that I don't know what to change to fix this, I'm still not very good at C++. If someone else can tell me what is wrong here I'll change it and commit the fix to the SVN.

Edit: That's not entirely true, I could fix it by adding an if/else statement to the calculateInflatedCosts function, but that would be a sticky-tape patch and would probably lead to other unintended consequences. I would like to figure out a way to fix it in the calculateInflationRate function, so that that function will return something reasonable.
 
Well, I've done some looking at the extreme negative inflation issue chmoosi pointed out to me, and something isn't right here. This function

PHP:
int CvPlayer::calculateInflatedCosts() const
{
	int iCosts;

	iCosts = calculatePreInflatedCosts();

	iCosts *= std::max(0, (calculateInflationRate() + 100));
	iCosts /= 100;

	return iCosts;
}

is supposed to return the final costs after inflation, but when I stepped through the code, I found that the preInlfatedCosts were 379, but after going through the next two operations, iCosts went down to 11! (well, they went up to 1179, then were divided by 100 and went down to 11.) This is probably due to the fact that calculateInflation rate returns this

PHP:
int CvPlayer::calculateInflationRate() const
{
	int iRatePercent = (m_accruedCostRatioTimes10000 == -1 ? 0 : (m_accruedCostRatioTimes10000-10000)/100);

	return iRatePercent;
}

and the debugger said that the value of m_accruedCostRatioTimes10000 was 278. This would make the calculateInflationRate function return ((278-10000)/100), or about -97.2. This, when plugged in to the above function, would lead to said negative inflation.

The problem is that I don't know what to change to fix this, I'm still not very good at C++. If someone else can tell me what is wrong here I'll change it and commit the fix to the SVN.

Edit: That's not entirely true, I could fix it by adding an if/else statement to the calculateInflatedCosts function, but that would be a sticky-tape patch and would probably lead to other unintended consequences. I would like to figure out a way to fix it in the calculateInflationRate function, so that that function will return something reasonable.
My guess would be that the original m_accruedCostRatioTimes10000 started from 10000 and got larger but the changes Koshling did base that on 0.
Check the code pieces that change m_accruedCostRatioTimes10000 to investigate if that is the case. If it is, then simply remove that -10000.
 
My guess would be that the original m_accruedCostRatioTimes10000 started from 10000 and got larger but the changes Koshling did base that on 0.
Check the code pieces that change m_accruedCostRatioTimes10000 to investigate if that is the case. If it is, then simply remove that -10000.

Ah, thanks, it appears you are right. The doInflation function looks like this

PHP:
void CvPlayer::doInflation(void)
{
	// Keep up to second order terms in binomial series
	int iAccruedCostRatioTimes10000 = getEquilibriumInflationCostModifier();

	// iAccruedCostRatioTimes10000 now holds the cost multiplier our CURRENT inflation rate would imply.  We
	// 'decay' the current cost multiplier towards this equilibrium value
	int iInflationRateMomentum = GC.getDefineINT("INFLATION_RATE_MOMENTUM", 50);

	iInflationRateMomentum *= GC.getGameSpeedInfo(GC.getGameINLINE().getGameSpeedType()).getImprovementPercent();
	iInflationRateMomentum /= 100;

	if ( iInflationRateMomentum < 1 )
	{
		iInflationRateMomentum = 1;
	}
	m_accruedCostRatioTimes10000 += (iAccruedCostRatioTimes10000 - m_accruedCostRatioTimes10000 + (iAccruedCostRatioTimes10000 > m_accruedCostRatioTimes10000 ? 1 : -1)*(iInflationRateMomentum - 1))/iInflationRateMomentum;
}

and does appear to start at 0, so I will implement your suggested fix. Thanks much.:goodjob:
 
Ah, thanks, it appears you are right. The doInflation function looks like this

PHP:
void CvPlayer::doInflation(void)
{
	// Keep up to second order terms in binomial series
	int iAccruedCostRatioTimes10000 = getEquilibriumInflationCostModifier();

	// iAccruedCostRatioTimes10000 now holds the cost multiplier our CURRENT inflation rate would imply.  We
	// 'decay' the current cost multiplier towards this equilibrium value
	int iInflationRateMomentum = GC.getDefineINT("INFLATION_RATE_MOMENTUM", 50);

	iInflationRateMomentum *= GC.getGameSpeedInfo(GC.getGameINLINE().getGameSpeedType()).getImprovementPercent();
	iInflationRateMomentum /= 100;

	if ( iInflationRateMomentum < 1 )
	{
		iInflationRateMomentum = 1;
	}
	m_accruedCostRatioTimes10000 += (iAccruedCostRatioTimes10000 - m_accruedCostRatioTimes10000 + (iAccruedCostRatioTimes10000 > m_accruedCostRatioTimes10000 ? 1 : -1)*(iInflationRateMomentum - 1))/iInflationRateMomentum;
}

and does appear to start at 0, so I will implement your suggested fix. Thanks much.:goodjob:

My recollection is that it DOES start at 10000 (check for I gets initialized in reset()), in which ca the interesting question is how it got to 278 in your game. Was this a game that had been played under the old system and just been loaded (in which case you'd expect to have seen the same -97% inflation in the old game under the old system at the point it was saved), or one that had been played for a while under the new system? If the latter than the bug is probably in getEquilibriumInflationCostModifier() rather than in the subtraction suggested above. I'm afraid the only connection I can get here is such that page loads on these boards takeabout 1-2 minutes, so there's no way I can check source from here.
 
Was this a game that had been played under the old system and just been loaded (in which case you'd expect to have seen the same -97% inflation in the old game under the old system at the point it was saved),

For my savegame yes.

JosEPh
 
My recollection is that it DOES start at 10000 (check for I gets initialized in reset()), in which ca the interesting question is how it got to 278 in your game. Was this a game that had been played under the old system and just been loaded (in which case you'd expect to have seen the same -97% inflation in the old game under the old system at the point it was saved), or one that had been played for a while under the new system? If the latter than the bug is probably in getEquilibriumInflationCostModifier() rather than in the subtraction suggested above. I'm afraid the only connection I can get here is such that page loads on these boards takeabout 1-2 minutes, so there's no way I can check source from here.

It says in the code for save compatability it gets initialized at -1, which should then be recalculated at the first opportunity. However, given that I stepped through the code doing a modifier recalculation, I suspect that it isn't actually getting recalculated, which the debugger results seem to confirm.
 
For my savegame yes.

JosEPh

Hi, I'm looking at that inflation bug, and I think that you already play some turns with the new system (m_accruedCostRatioTimes10000 doesn't start à 10000 but 7465).

Do you have any earlier savegame ?

@ls612 :
I think the modification you made by removing the -10000 is wrong : with that modif, you start with 100% inflation, has we expect 0%


@Koshling :

Code:
void CvPlayer::doInflation(void)
{
	// Keep up to second order terms in binomial series
	int iAccruedCostRatioTimes10000 = (getEquilibriumInflationCostModifier();

	// iAccruedCostRatioTimes10000 now holds the cost multiplier our CURRENT inflation rate would imply.  We
	// 'decay' the current cost multiplier towards this equilibrium value
	int iInflationRateMomentum = GC.getDefineINT("INFLATION_RATE_MOMENTUM", 50);

	iInflationRateMomentum *= GC.getGameSpeedInfo(GC.getGameINLINE().getGameSpeedType()).getImprovementPercent();
	iInflationRateMomentum /= 100;

	if ( iInflationRateMomentum < 1 )
	{
		iInflationRateMomentum = 1;
	}
	m_accruedCostRatioTimes10000 += (iAccruedCostRatioTimes10000 - m_accruedCostRatioTimes10000 + (iAccruedCostRatioTimes10000 > m_accruedCostRatioTimes10000 ? 1 : -1)*(iInflationRateMomentum - 1))/iInflationRateMomentum;
}


getEquilibriumInflationCostModifier() is returning a percent value :

Code:
	return iAccruedCostRatioTimes10000/100 - 100;

but with the name of variable iAccruedCostRatioTimes10000, I assume it has to be in 1xxxx format.

because at the end of the method doInflation(), you compare it to m_accruedCostRatioTimes10000

I'll try to modify that :

Code:
	int iAccruedCostRatioTimes10000 = (getEquilibriumInflationCostModifier() + 100 * 100;

and see what comes
 
@calvitix:

I think that what was originally happening is that there are two definitions of m_accruedcostratiotimes10000, one of them is commented as being for savegame compatability and is defined as having a value of -1, and the other is defined as having a value of 10000, and is not commented. The save I stepped through had a value of m_accruedCostRatioTimes10000 of -1, which means that it was not recalculated, as the comment said it should have been. This makes me think that the bug is in whatever function calculates the value of m_accruedCostRatioTimes10000 on a modifier recalc.
 
Hi, I'm looking at that inflation bug, and I think that you already play some turns with the new system (m_accruedCostRatioTimes10000 doesn't start à 10000 but 7465).

Do you have any earlier savegame ?

Here's the closest I have from before the Inflation change.

JosEPh
 
@calvitix:

I think that what was originally happening is that there are two definitions of m_accruedcostratiotimes10000, one of them is commented as being for savegame compatability and is defined as having a value of -1, and the other is defined as having a value of 10000, and is not commented. The save I stepped through had a value of m_accruedCostRatioTimes10000 of -1, which means that it was not recalculated, as the comment said it should have been. This makes me think that the bug is in whatever function calculates the value of m_accruedCostRatioTimes10000 on a modifier recalc.

I Agree, I saw the two definitions.

But there is a way to save percent values as integer in the code, is to use the "times10000" Format : you take a percent number (37,50% as example)

You can store the value as 13750 <=> (37,50 + 100) * 100

0% is coded as 10000

to revert the value, you have to do (myvalueTimes10000 -10000) / 100

in the example : (13750 -10000) / 100 = 37,50


That what is done in the following code :

Code:
int iRatePercent = (m_accruedCostRatioTimes10000 == -1 ? 0 : (m_accruedCostRatioTimes10000-10000)/100);

The -1 value of the save compatibility has exactly the same fonction as if the variable accruedCostRatioTimes10000 = 100000

by the way, I think it the first line is not needed :
Code:
		//	Cope with old saves not having an accrued cost ratio value
		//	Use a sentinel value of -1, which will be taken as a signal to
		//	initialise using the old calculation on first recalculation
		//	opportunity
		m_accruedCostRatioTimes10000 = -1;
		WRAPPER_READ(wrapper, "CvPlayer", &m_accruedCostRatioTimes10000);

I loaded a save that have been made far before that update, and the WRAPPER_READ() method always overwrite the variable accruedCostRatioTimes10000 with default value 10000.
--> it doesn't matter, because with a value of 10000, you will Get a Value of 0 for iRatePercent (as expected)




--> it gives 0% has value.
 
I Agree, I saw the two definitions.

But there is a way to save percent values as integer in the code, is to use the "times10000" Format : you take a percent number (37,50% as example)

You can store the value as 13750 <=> (37,50 + 100) * 100

0% is coded as 10000

to revert the value, you have to do (myvalueTimes10000 -10000) / 100

in the example : (13750 -10000) / 100 = 37,50


That what is done in the following code :

Code:
int iRatePercent = (m_accruedCostRatioTimes10000 == -1 ? 0 : (m_accruedCostRatioTimes10000-10000)/100);

The -1 value of the save compatibility has exactly the same fonction as if the variable accruedCostRatioTimes10000 = 100000

by the way, I think it the first line is not needed :
Code:
		//	Cope with old saves not having an accrued cost ratio value
		//	Use a sentinel value of -1, which will be taken as a signal to
		//	initialise using the old calculation on first recalculation
		//	opportunity
		m_accruedCostRatioTimes10000 = -1;
		WRAPPER_READ(wrapper, "CvPlayer", &m_accruedCostRatioTimes10000);

I loaded a save that have been made far before that update, and the WRAPPER_READ() method always overwrite the variable accruedCostRatioTimes10000 with default value 10000.
--> it doesn't matter, because with a value of 10000, you will Get a Value of 0 for iRatePercent (as expected)




--> it gives 0% has value.

I agree with all of that, and I think the issue was that the game was not replacing that -1 value with 10000, which led to the screwy numbers I saw (in fact, I'm pretty sure now that that was the issue). Are you saying that if we remove the first definition of m_accruedCostRatioTimes10000 and revert my change to calculateInflationRate() that that will fix the issue?
 
I agree with all of that, and I think the issue was that the game was not replacing that -1 value with 10000, which led to the screwy numbers I saw (in fact, I'm pretty sure now that that was the issue). Are you saying that if we remove the first definition of m_accruedCostRatioTimes10000 and revert my change to calculateInflationRate() that that will fix the issue?

No, there is still another bug.

I think in Joseph's Game, the value started at 10000, but has lose ~200 every turn til a value of 7465, and that 's not intend if I have understand the principle that Koshling applied.
 
No, there is still another bug.

I think in Joseph's Game, the value started at 10000, but has lose ~200 every turn til a value of 7465, and that 's not intend if I have understand the principle that Koshling applied.

Well, 10000/50 is equal to 200, and 50 is the value Koshling defined by default for the number of turns for inflation equlibrium, so that might be related. I'm currently looking at a possible bug with civic costs in one of my personal games, so I'll post what I can find about that.
 
I Agree, I saw the two definitions.

But there is a way to save percent values as integer in the code, is to use the "times10000" Format : you take a percent number (37,50% as example)

You can store the value as 13750 <=> (37,50 + 100) * 100

0% is coded as 10000

to revert the value, you have to do (myvalueTimes10000 -10000) / 100

in the example : (13750 -10000) / 100 = 37,50


That what is done in the following code :

Code:
int iRatePercent = (m_accruedCostRatioTimes10000 == -1 ? 0 : (m_accruedCostRatioTimes10000-10000)/100);

The -1 value of the save compatibility has exactly the same fonction as if the variable accruedCostRatioTimes10000 = 100000

by the way, I think it the first line is not needed :
Code:
		//	Cope with old saves not having an accrued cost ratio value
		//	Use a sentinel value of -1, which will be taken as a signal to
		//	initialise using the old calculation on first recalculation
		//	opportunity
		m_accruedCostRatioTimes10000 = -1;
		WRAPPER_READ(wrapper, "CvPlayer", &m_accruedCostRatioTimes10000);

I loaded a save that have been made far before that update, and the WRAPPER_READ() method always overwrite the variable accruedCostRatioTimes10000 with default value 10000.
--> it doesn't matter, because with a value of 10000, you will Get a Value of 0 for iRatePercent (as expected)




--> it gives 0% has value.

Without full access to th code I'm not sure, but he are so e observations:

Does the value assigned to iaccruedCostRatioTimes10000 at the start of doInflation() need to be the return value of the equilibrium method need to be converted from a percentage back t an accrued scaled modifier in that line? (multiply by 100 and add 10000)?

The assignment to -1 in the load IS needed for loading saves that predate the inflation changes that happened about a year ago. Saves befo THAT have no value for it and the read will leave it unchanged (and therefore at -1 which will force a best efforts recalculation)
 
The assignment to -1 in the load IS needed for loading saves that predate the inflation changes that happened about a year ago. Saves befo THAT have no value for it and the read will leave it unchanged (and therefore at -1 which will force a best efforts recalculation)

OK, I thought the -1 was for save before your last changes (1 weeks ago), not so old. But I hadn't modify that part, so it will be ok.
 
Back
Top Bottom