GDP per capita had no natural growth, so a nation's economy only moved when population, buildings or war did. The summary stats also offered inconsistent hover text: a native browser tooltip for popularity and none at all for the budget. - Every land tile now gains a constant 0.01% of the GDP per capita it opened with, once a day. Because the amount is fixed the relative growth rate tapers over the years. Each tile's opening figure is snapshotted once and shipped to the browser, so the tile panel and the GDP-per-capita map mode stay in step with the server. - The population, GDP and GDP/capita tooltips show the relative growth: against 1 January 2000 through the first year, then year on year. The monthly captures now keep a rolling thirteen months for the baseline. - Every summary stat explains itself through the same custom bubble. Popularity's native title moved onto it, and Budget forecasts the next calendar month at the current hourly net, as a compact figure such as "+€3.28B/mo" (hoursInNextMonth follows the real calendar). - The tile GDP breakdown shows gains with a plus sign (natural growth, ports) rather than the old minus. Added tests for the constant daily gain, the falling relative rate, the first-year and year-on-year baselines, the snapshot baseline, the budget forecast and the calendar month length.
97 lines
4.5 KiB
JavaScript
97 lines
4.5 KiB
JavaScript
import { TestCase } from "./framework/test_case.js";
|
|
import { smallState } from "./framework/helpers.js";
|
|
import { ECONOMY } from "../shared/data.js";
|
|
import { HOURS_PER_DAY } from "../shared/game_state/constants.js";
|
|
import { parseKey } from "../shared/hex.js";
|
|
|
|
// Natural GDP-per-capita growth: a constant daily amount measured from each
|
|
// tile's opening figure, so the relative growth rate tapers over the years.
|
|
export class GdpGrowthTest extends TestCase {
|
|
firstBaseline(state) {
|
|
const k = state.tileGdpBaseline.keys().next().value;
|
|
return { k, baseline: state.tileGdpBaseline.get(k), coords: parseKey(k) };
|
|
}
|
|
|
|
test_a_day_adds_a_constant_share_of_the_opening_figure() {
|
|
const state = smallState();
|
|
const { k, baseline, coords } = this.firstBaseline(state);
|
|
this.assertTrue(baseline > 0, "the tile has an opening GDP per capita");
|
|
const opening = state.getTileGdpPerCapita(coords);
|
|
this.assertApprox(opening, baseline, 1e-9, "day zero is the baseline");
|
|
for (let i = 0; i < HOURS_PER_DAY; i++) state.tickHour();
|
|
const dayOne = state.getTileGdpPerCapita(coords);
|
|
const step = baseline * ECONOMY.naturalGdpGrowthPerDay;
|
|
this.assertApprox(dayOne - opening, step, 1e-6, "one day adds one step");
|
|
// Thirty more days add exactly thirty more steps: the amount is constant.
|
|
state.totalHours = 31 * HOURS_PER_DAY;
|
|
state._gdpPerCapitaCache.clear();
|
|
const dayThirtyOne = state.getTileGdpPerCapita(coords);
|
|
this.assertApprox(dayThirtyOne - dayOne, 30 * step, 1e-6, "the daily amount never grows");
|
|
this.assertEqual(k, state.tileGdpBaseline.keys().next().value, "the baseline never moves");
|
|
}
|
|
|
|
test_the_relative_growth_rate_falls_as_the_tile_grows() {
|
|
const state = smallState();
|
|
const { baseline, coords } = this.firstBaseline(state);
|
|
const step = baseline * ECONOMY.naturalGdpGrowthPerDay;
|
|
const earlyRate = step / baseline;
|
|
state.totalHours = 366 * HOURS_PER_DAY;
|
|
state._gdpPerCapitaCache.clear();
|
|
const value = state.getTileGdpPerCapita(coords);
|
|
// The same constant step, a year later, is a smaller share of a bigger
|
|
// figure: the relative rate has fallen.
|
|
const laterRate = step / (value - step);
|
|
this.assertTrue(value > baseline, "the tile is richer than it began");
|
|
this.assertTrue(laterRate < earlyRate, "the same gain is now a smaller share");
|
|
}
|
|
|
|
test_growth_is_measured_from_january_2000_in_the_first_year() {
|
|
const state = smallState();
|
|
const growth = state.viewerStats(0).growth;
|
|
this.assertNotNull(growth, "the viewer carries growth figures");
|
|
this.assertTrue(growth.sinceStart, "the first year measures from January 2000");
|
|
this.assertApprox(growth.gdpPerCapita, 0, 1e-9, "nothing has grown yet");
|
|
for (let i = 0; i < HOURS_PER_DAY; i++) state.tickHour();
|
|
const after = state.viewerStats(0).growth;
|
|
this.assertTrue(after.sinceStart, "still the first year");
|
|
this.assertTrue(after.gdpPerCapita > 0, "a day of growth is reported");
|
|
this.assertTrue(after.gdp > after.gdpPerCapita, "GDP also carries population growth");
|
|
}
|
|
|
|
test_growth_becomes_year_on_year_after_the_first_year() {
|
|
const state = smallState();
|
|
const current = state._monthIndexOf(state.totalHours);
|
|
// A year on, the same month last year is the baseline. Halve it so the
|
|
// comparison has an unmistakable answer.
|
|
const past = state._captureMonthlyStats();
|
|
for (const entry of past) {
|
|
entry.population = Math.floor(entry.population / 2);
|
|
entry.gdp = entry.gdp / 2;
|
|
entry.gdpPerCapita = entry.gdpPerCapita / 2;
|
|
}
|
|
state._monthIndexNow = current;
|
|
state._monthStartHistory = [
|
|
{ index: current - 13, stats: past },
|
|
{ index: current - 12, stats: past },
|
|
{ index: current, stats: state._captureMonthlyStats() },
|
|
];
|
|
const growth = state._growthRates(0);
|
|
this.assertFalse(growth.sinceStart, "with a year of history it is year on year");
|
|
this.assertApprox(growth.population, 1, 1e-6, "the population doubled");
|
|
this.assertApprox(growth.gdp, 1, 1e-6, "the GDP doubled");
|
|
}
|
|
|
|
test_the_snapshot_ships_the_growth_baseline_once() {
|
|
const state = smallState();
|
|
const first = state.snapshot(0);
|
|
this.assertSize(
|
|
first.gdpBaseline,
|
|
state.tileGdpBaseline.size,
|
|
"every baseline tile travels"
|
|
);
|
|
this.assertNotNull(first.viewerStats.growth, "the viewer's growth travels too");
|
|
// The version never moves, so the delta wire drops the baseline afterwards.
|
|
this.assertEqual(first.versions.gdpBaseline, 0, "the baseline version is static");
|
|
}
|
|
}
|