feat: Cards writings and balancing (M8)
This commit is contained in:
@@ -143,3 +143,16 @@
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.ledger input[type="range"]:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
|
||||
|
||||
.ledger__emergency {
|
||||
margin: 0 0 6px;
|
||||
padding: 3px 6px;
|
||||
font-size: 11px;
|
||||
line-height: 1.3;
|
||||
color: #8a1c14;
|
||||
border: 1.5px solid #8a1c14;
|
||||
border-radius: 2px;
|
||||
transform: rotate(-0.6deg);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
+112
-67
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"version": 1,
|
||||
|
||||
"time": {
|
||||
"daysPerWeek": 7,
|
||||
"daysPerQuarter": 91,
|
||||
@@ -10,16 +9,33 @@
|
||||
"speeds": [1, 2, 4],
|
||||
"historyEvery": 7
|
||||
},
|
||||
|
||||
"limits": { "allocMaxRatio": 3, "sliderMaxRatio": 2, "taxMaxFactorOfPeak": 2 },
|
||||
|
||||
"noise": { "indicator": 0.35 },
|
||||
|
||||
"gain": 3,
|
||||
"world": { "tau": 220, "noise": 0.22, "max": 4, "shockChancePerDay": 0.0012, "shock": 2.5, "boomShare": 0.3 },
|
||||
"fiscal": { "growthSens": 0.25 },
|
||||
"needGrowth": {
|
||||
"education": 0.015,
|
||||
"health": 0.035,
|
||||
"social": 0.025,
|
||||
"police": 0.02,
|
||||
"army": 0.015,
|
||||
"justice": 0.02,
|
||||
"industry": 0.01,
|
||||
"transport": 0.02,
|
||||
"infrastructure": 0.03,
|
||||
"housing": 0.025,
|
||||
"research": 0.01,
|
||||
"culture": 0.01,
|
||||
"foreignAffairs": 0.01,
|
||||
"environment": 0.02,
|
||||
"administration": 0.015
|
||||
},
|
||||
"cards": {
|
||||
"recentWindow": 3,
|
||||
"recentPenalty": 0.4,
|
||||
"boostBelow": 40,
|
||||
"boostMax": 1.0,
|
||||
"boostMax": 1,
|
||||
"defaultCooldown": 180,
|
||||
"boost": {
|
||||
"economy": { "indicator": "economy" },
|
||||
@@ -37,13 +53,7 @@
|
||||
"culture": { "sector": "culture" }
|
||||
}
|
||||
},
|
||||
|
||||
"sector": {
|
||||
"qualityCurve": 0.9,
|
||||
"industryDebtPenaltyFrom": 90,
|
||||
"industryDebtPenaltyPerPoint": 0.25
|
||||
},
|
||||
|
||||
"sector": { "qualityCurve": 0.9, "industryDebtPenaltyFrom": 90, "industryDebtPenaltyPerPoint": 0.25 },
|
||||
"sectorTau": {
|
||||
"education": 365,
|
||||
"health": 90,
|
||||
@@ -61,117 +71,152 @@
|
||||
"environment": 365,
|
||||
"administration": 90
|
||||
},
|
||||
|
||||
"debt": {
|
||||
"freeBelow": 60,
|
||||
"baseRate": 3.0,
|
||||
"premiumFrom": 40,
|
||||
"premiumCoef": 0.0003
|
||||
},
|
||||
|
||||
"debt": { "freeBelow": 60, "baseRate": 3, "premiumFrom": 40, "premiumCoef": 0.0008 },
|
||||
"crackdown": { "unrestStart": 45 },
|
||||
|
||||
"gameOver": {
|
||||
"default": { "debtAbove": 200, "warnAbove": 160 },
|
||||
"default": { "debtAbove": 165, "warnAbove": 135 },
|
||||
"impeachment": { "civilRightsBelow": 10, "opinionBelow": 25, "days": 60 },
|
||||
"coup": { "armyBelow": 35, "stabilityBelow": 35, "chancePerDay": 0.015 },
|
||||
"revolution": { "unrestAbove": 70, "opinionBelow": 20, "chancePerDay": 0.02 }
|
||||
"coup": { "armyBelow": 35, "stabilityBelow": 50, "armyAlone": 18, "chancePerDay": 0.004 },
|
||||
"revolution": { "unrestAbove": 62, "opinionBelow": 28, "chancePerDay": 0.004 },
|
||||
"emergencyCooldown": 45
|
||||
},
|
||||
|
||||
"score": {
|
||||
"weights": {
|
||||
"opinion": 0.20, "stability": 0.15, "economy": 0.15, "order": 0.10,
|
||||
"civilRights": 0.10, "softPower": 0.10, "hardPower": 0.10, "calm": 0.10
|
||||
"opinion": 0.2,
|
||||
"stability": 0.15,
|
||||
"economy": 0.15,
|
||||
"order": 0.1,
|
||||
"civilRights": 0.1,
|
||||
"softPower": 0.1,
|
||||
"hardPower": 0.1,
|
||||
"calm": 0.1
|
||||
},
|
||||
"debt": { "ref": 60, "per": 0.12, "min": -10, "max": 6 },
|
||||
"debt": { "ref": 60, "per": 0.2, "min": -16, "max": 6 },
|
||||
"growth": { "ref": 2, "per": 2.5, "min": -5, "max": 7.5 },
|
||||
"unemployment": { "ref": 6.5, "per": 1, "min": -5, "max": 3 },
|
||||
"overCap": 35,
|
||||
"grades": [[75, "S"], [66, "A"], [58, "B"], [50, "C"], [40, "D"], [0, "F"]],
|
||||
"topDecisions": 5
|
||||
"grades": [[73, "S"], [64, "A"], [56, "B"], [47, "C"], [38, "D"], [0, "F"]],
|
||||
"topDecisions": 5,
|
||||
"lastYearShare": 0.5
|
||||
},
|
||||
|
||||
"tax": {
|
||||
"income": { "coef": 0.50, "peak": 60, "opinion": 0.50, "growth": 0.030 },
|
||||
"corporate": { "coef": 0.25, "peak": 50, "opinion": 0.15, "growth": 0.060 },
|
||||
"vat": { "coef": 0.55, "peak": 40, "opinion": 0.60, "growth": 0.025 }
|
||||
"income": { "coef": 0.5, "peak": 60, "opinion": 0.5, "growth": 0.03 },
|
||||
"corporate": { "coef": 0.25, "peak": 50, "opinion": 0.15, "growth": 0.06 },
|
||||
"vat": { "coef": 0.55, "peak": 40, "opinion": 0.6, "growth": 0.025 }
|
||||
},
|
||||
|
||||
"vars": {
|
||||
"growth": {
|
||||
"tau": 60, "min": -15, "max": 12, "noise": 0.02,
|
||||
"sectors": { "industry": 0.02, "infrastructure": 0.012, "research": 0.015, "education": 0.010, "transport": 0.006 },
|
||||
"tau": 60,
|
||||
"min": -15,
|
||||
"max": 12,
|
||||
"noise": 0.02,
|
||||
"sectors": { "industry": 0.02, "infrastructure": 0.012, "research": 0.015, "education": 0.01, "transport": 0.006 },
|
||||
"econ": { "debt": -0.03, "taxGrowthDrag": -1 },
|
||||
"ind": { "unrest": -0.02 }
|
||||
"world": { "cycle": 0.8 },
|
||||
"ind": { "unrest": -0.02 },
|
||||
"excess": { "research": [70, 0.012], "education": [70, 0.01], "infrastructure": [70, 0.012], "industry": [70, 0.012] },
|
||||
"short": { "infrastructure": [40, 0.01], "industry": [40, 0.012] }
|
||||
},
|
||||
"unemployment": {
|
||||
"tau": 120, "min": 1, "max": 40, "noise": 0.01,
|
||||
"tau": 120,
|
||||
"min": 1,
|
||||
"max": 40,
|
||||
"noise": 0.01,
|
||||
"sectors": { "industry": -0.03, "education": -0.015, "infrastructure": -0.01 },
|
||||
"econ": { "growth": -0.6 }
|
||||
},
|
||||
"inflation": {
|
||||
"tau": 90, "min": -2, "max": 40, "noise": 0.015,
|
||||
"econ": { "deficit": 0.35, "growth": 0.15, "debt": 0.02 }
|
||||
"tau": 90,
|
||||
"min": -2,
|
||||
"max": 40,
|
||||
"noise": 0.015,
|
||||
"econ": { "deficit": 0.35, "growth": 0.15, "debt": 0.02 },
|
||||
"world": { "cycle": 0.3 }
|
||||
}
|
||||
},
|
||||
|
||||
"indicators": {
|
||||
"order": {
|
||||
"tau": 30,
|
||||
"sectors": { "police": 0.35, "justice": 0.20, "administration": 0.08 },
|
||||
"sectors": { "police": 0.35, "justice": 0.2, "administration": 0.08 },
|
||||
"econ": { "unemployment": -0.6, "crackdown": 25 },
|
||||
"ind": { "unrest": -0.30 }
|
||||
"ind": { "unrest": -0.3 },
|
||||
"drift": -0.8,
|
||||
"excess": { "police": [75, 0.15], "justice": [70, 0.2] }
|
||||
},
|
||||
"stability": {
|
||||
"tau": 45,
|
||||
"sectors": { "social": 0.15, "health": 0.08, "housing": 0.08, "justice": 0.08, "administration": 0.10 },
|
||||
"sectors": { "social": 0.15, "health": 0.08, "housing": 0.08, "justice": 0.08, "administration": 0.1 },
|
||||
"econ": { "debt": -0.15, "unemployment": -0.7, "inflation": -0.4 },
|
||||
"ind": { "unrest": -0.25, "opinion": 0.15, "order": 0.10, "economy": 0.08 }
|
||||
},
|
||||
"civilRights": {
|
||||
"tau": 40,
|
||||
"sectors": { "justice": 0.30 },
|
||||
"alloc": { "police": -20, "army": -4 },
|
||||
"econ": { "crackdown": -35 }
|
||||
"ind": { "unrest": -0.25, "opinion": 0.15, "order": 0.1, "economy": 0.08 },
|
||||
"drift": -0.8
|
||||
},
|
||||
"civilRights": { "tau": 40, "sectors": { "justice": 0.3 }, "alloc": { "police": -20, "army": -4 }, "econ": { "crackdown": -35 } },
|
||||
"opinion": {
|
||||
"tau": 30,
|
||||
"sectors": { "health": 0.22, "education": 0.08, "social": 0.28, "housing": 0.18, "transport": 0.10, "infrastructure": 0.08, "culture": 0.05, "environment": 0.04 },
|
||||
"sectors": {
|
||||
"health": 0.22,
|
||||
"education": 0.08,
|
||||
"social": 0.28,
|
||||
"housing": 0.18,
|
||||
"transport": 0.1,
|
||||
"infrastructure": 0.08,
|
||||
"culture": 0.05,
|
||||
"environment": 0.04
|
||||
},
|
||||
"econ": { "unemployment": -1.4, "inflation": -1.1, "growth": 1.8, "taxPressure": -1.2, "crackdown": -4 },
|
||||
"ind": { "civilRights": 0.08, "order": 0.08, "stability": 0.10 }
|
||||
"ind": { "civilRights": 0.08, "order": 0.08, "stability": 0.1 },
|
||||
"drift": -1.4,
|
||||
"excess": { "health": [70, 0.25], "social": [70, 0.2], "housing": [70, 0.15] },
|
||||
"short": { "health": [40, 0.25], "social": [40, 0.2] }
|
||||
},
|
||||
"economy": {
|
||||
"tau": 30,
|
||||
"sectors": { "industry": 0.30, "infrastructure": 0.15, "research": 0.12, "transport": 0.10, "education": 0.10, "administration": 0.05 },
|
||||
"sectors": {
|
||||
"industry": 0.3,
|
||||
"infrastructure": 0.15,
|
||||
"research": 0.12,
|
||||
"transport": 0.1,
|
||||
"education": 0.1,
|
||||
"administration": 0.05
|
||||
},
|
||||
"econ": { "growth": 6, "unemployment": -1.2, "inflation": -0.8, "debt": -0.12 },
|
||||
"ind": { "stability": 0.10 }
|
||||
"ind": { "stability": 0.1 },
|
||||
"excess": { "education": [70, 0.2], "research": [70, 0.25], "infrastructure": [70, 0.2], "industry": [70, 0.25] },
|
||||
"short": { "infrastructure": [40, 0.2], "industry": [40, 0.2] }
|
||||
},
|
||||
"hardPower": {
|
||||
"tau": 60,
|
||||
"sectors": { "army": 0.50, "research": 0.10, "industry": 0.08 },
|
||||
"sectors": { "army": 0.5, "research": 0.1, "industry": 0.08 },
|
||||
"alloc": { "army": 8 },
|
||||
"econ": { "debt": -0.05 },
|
||||
"ind": { "economy": 0.08 }
|
||||
"ind": { "economy": 0.08 },
|
||||
"drift": -0.8,
|
||||
"excess": { "army": [75, 0.3], "research": [70, 0.15] }
|
||||
},
|
||||
"softPower": {
|
||||
"tau": 60,
|
||||
"sectors": { "culture": 0.30, "foreignAffairs": 0.35, "education": 0.08, "research": 0.08, "environment": 0.05 },
|
||||
"sectors": { "culture": 0.3, "foreignAffairs": 0.35, "education": 0.08, "research": 0.08, "environment": 0.05 },
|
||||
"alloc": { "police": -4 },
|
||||
"econ": { "crackdown": -15 },
|
||||
"ind": { "civilRights": 0.15, "economy": 0.05 }
|
||||
"ind": { "civilRights": 0.15, "economy": 0.05 },
|
||||
"drift": -0.55,
|
||||
"excess": { "culture": [70, 0.3], "foreignAffairs": [70, 0.3] }
|
||||
},
|
||||
"unrest": {
|
||||
"tau": 20,
|
||||
"sectors": { "social": -0.15, "housing": -0.10 },
|
||||
"econ": { "unemployment": 2.5, "inflation": 1.0, "debt": 0.20, "crackdown": -10 },
|
||||
"ind": { "opinion": -0.50, "stability": -0.25, "order": -0.15, "civilRights": -0.12 }
|
||||
"sectors": { "social": -0.15, "housing": -0.1 },
|
||||
"econ": { "unemployment": 2.5, "inflation": 1, "debt": 0.2, "crackdown": -10 },
|
||||
"ind": { "opinion": -0.5, "stability": -0.25, "order": -0.15, "civilRights": -0.12 },
|
||||
"drift": 1.1
|
||||
},
|
||||
"armyLoyalty": {
|
||||
"tau": 60,
|
||||
"sectors": { "army": 0.40 },
|
||||
"sectors": { "army": 0.4 },
|
||||
"alloc": { "army": 15 },
|
||||
"econ": { "debt": -0.10, "inflation": -0.3 },
|
||||
"ind": { "opinion": 0.05, "stability": 0.10 }
|
||||
"econ": { "debt": -0.1, "inflation": -0.3 },
|
||||
"ind": { "opinion": 0.05, "stability": 0.1 },
|
||||
"drift": -1.1
|
||||
}
|
||||
}
|
||||
},
|
||||
"driftScale": 1.1
|
||||
}
|
||||
|
||||
+2987
-13
File diff suppressed because it is too large
Load Diff
+1040
-12
File diff suppressed because it is too large
Load Diff
+1629
-16
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1493
-13
File diff suppressed because it is too large
Load Diff
+1377
-17
File diff suppressed because it is too large
Load Diff
+1927
-21
File diff suppressed because it is too large
Load Diff
+1528
-14
File diff suppressed because it is too large
Load Diff
+1158
-11
File diff suppressed because it is too large
Load Diff
+1864
-14
File diff suppressed because it is too large
Load Diff
+1620
-8
File diff suppressed because it is too large
Load Diff
+1920
-18
File diff suppressed because it is too large
Load Diff
+2038
-26
File diff suppressed because it is too large
Load Diff
+1594
-14
File diff suppressed because it is too large
Load Diff
+296
-107
@@ -1,214 +1,403 @@
|
||||
{
|
||||
"name": { "en": "The Daily Herald", "fr": "Le Courrier du Jour" },
|
||||
|
||||
"intro": [
|
||||
{ "h": { "en": "A new government takes office", "fr": "Un nouveau gouvernement prend ses fonctions" },
|
||||
"d": { "en": "{country} awaits its first decisions.", "fr": "{country} attend ses premières décisions." } }
|
||||
{
|
||||
"h": { "en": "A new government takes office", "fr": "Un nouveau gouvernement prend ses fonctions" },
|
||||
"d": { "en": "{country} awaits its first decisions.", "fr": "{country} attend ses premières décisions." }
|
||||
}
|
||||
],
|
||||
|
||||
"quiet": [
|
||||
{ "h": { "en": "A quiet week in the capital", "fr": "Semaine calme dans la capitale" },
|
||||
"d": { "en": "Ministers report nothing of note.", "fr": "Les ministres n'ont rien à signaler." } },
|
||||
{ "h": { "en": "Markets and streets stay calm", "fr": "Les marchés et les rues restent calmes" },
|
||||
"d": { "en": "Analysts expect no change in the weeks ahead.", "fr": "Les analystes n'attendent pas de changement dans les semaines à venir." } }
|
||||
{
|
||||
"h": { "en": "A quiet week in the capital", "fr": "Semaine calme dans la capitale" },
|
||||
"d": { "en": "Ministers report nothing of note.", "fr": "Les ministres n'ont rien à signaler." }
|
||||
},
|
||||
{
|
||||
"h": { "en": "Markets and streets stay calm", "fr": "Les marchés et les rues restent calmes" },
|
||||
"d": {
|
||||
"en": "Analysts expect no change in the weeks ahead.",
|
||||
"fr": "Les analystes n'attendent pas de changement dans les semaines à venir."
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"ind": {
|
||||
"order": {
|
||||
"up": [
|
||||
{ "h": { "en": "Streets calmer as order improves", "fr": "Les rues s'apaisent, l'ordre se rétablit" },
|
||||
"d": { "en": "Police report fewer incidents across the country.", "fr": "La police signale moins d'incidents dans tout le pays." } },
|
||||
{ "h": { "en": "Crime figures fall for a month", "fr": "La criminalité recule depuis un mois" },
|
||||
"d": { "en": "The interior ministry credits tighter patrols.", "fr": "Le ministère de l'Intérieur salue des patrouilles renforcées." } }
|
||||
{
|
||||
"h": { "en": "Streets calmer as order improves", "fr": "Les rues s'apaisent, l'ordre se rétablit" },
|
||||
"d": {
|
||||
"en": "Police report fewer incidents across the country.",
|
||||
"fr": "La police signale moins d'incidents dans tout le pays."
|
||||
}
|
||||
},
|
||||
{
|
||||
"h": { "en": "Crime figures fall for a month", "fr": "La criminalité recule depuis un mois" },
|
||||
"d": {
|
||||
"en": "The interior ministry credits tighter patrols.",
|
||||
"fr": "Le ministère de l'Intérieur salue des patrouilles renforcées."
|
||||
}
|
||||
}
|
||||
],
|
||||
"down": [
|
||||
{ "h": { "en": "Disorder spreads in the cities", "fr": "Le désordre gagne les villes" },
|
||||
"d": { "en": "Officials admit control is slipping.", "fr": "Les autorités admettent perdre le contrôle." } },
|
||||
{ "h": { "en": "Lawlessness on the rise", "fr": "L'insécurité progresse" },
|
||||
"d": { "en": "Shopkeepers demand protection.", "fr": "Les commerçants réclament une protection." } }
|
||||
{
|
||||
"h": { "en": "Disorder spreads in the cities", "fr": "Le désordre gagne les villes" },
|
||||
"d": { "en": "Officials admit control is slipping.", "fr": "Les autorités admettent perdre le contrôle." }
|
||||
},
|
||||
{
|
||||
"h": { "en": "Lawlessness on the rise", "fr": "L'insécurité progresse" },
|
||||
"d": { "en": "Shopkeepers demand protection.", "fr": "Les commerçants réclament une protection." }
|
||||
}
|
||||
]
|
||||
},
|
||||
"stability": {
|
||||
"up": [
|
||||
{ "h": { "en": "Government steadies after turbulent weeks", "fr": "Le gouvernement se reprend après des semaines agitées" },
|
||||
"d": { "en": "Coalition partners close ranks.", "fr": "Les partenaires de la coalition resserrent les rangs." } },
|
||||
{ "h": { "en": "Political climate cools", "fr": "Le climat politique se détend" },
|
||||
"d": { "en": "Analysts note fewer cabinet rifts.", "fr": "Les analystes notent moins de divisions au gouvernement." } }
|
||||
{
|
||||
"h": {
|
||||
"en": "Government steadies after turbulent weeks",
|
||||
"fr": "Le gouvernement se reprend après des semaines agitées"
|
||||
},
|
||||
"d": { "en": "Coalition partners close ranks.", "fr": "Les partenaires de la coalition resserrent les rangs." }
|
||||
},
|
||||
{
|
||||
"h": { "en": "Political climate cools", "fr": "Le climat politique se détend" },
|
||||
"d": {
|
||||
"en": "Analysts note fewer cabinet rifts.",
|
||||
"fr": "Les analystes notent moins de divisions au gouvernement."
|
||||
}
|
||||
}
|
||||
],
|
||||
"down": [
|
||||
{ "h": { "en": "Cracks show in the ruling circle", "fr": "Des fissures dans le cercle dirigeant" },
|
||||
"d": { "en": "Rumours of a reshuffle grow.", "fr": "Les rumeurs de remaniement se multiplient." } },
|
||||
{ "h": { "en": "Instability worries diplomats", "fr": "L'instabilité inquiète les diplomates" },
|
||||
"d": { "en": "Embassies advise caution.", "fr": "Les ambassades recommandent la prudence." } }
|
||||
{
|
||||
"h": { "en": "Cracks show in the ruling circle", "fr": "Des fissures dans le cercle dirigeant" },
|
||||
"d": { "en": "Rumours of a reshuffle grow.", "fr": "Les rumeurs de remaniement se multiplient." }
|
||||
},
|
||||
{
|
||||
"h": { "en": "Instability worries diplomats", "fr": "L'instabilité inquiète les diplomates" },
|
||||
"d": { "en": "Embassies advise caution.", "fr": "Les ambassades recommandent la prudence." }
|
||||
}
|
||||
]
|
||||
},
|
||||
"civilRights": {
|
||||
"up": [
|
||||
{ "h": { "en": "Freedoms widen, say rights groups", "fr": "Les libertés s'élargissent, selon les ONG" },
|
||||
"d": { "en": "Press and assembly face fewer limits.", "fr": "La presse et les rassemblements sont moins entravés." } },
|
||||
{ "h": { "en": "Courts restore protections", "fr": "Les tribunaux rétablissent des protections" },
|
||||
"d": { "en": "Lawyers hail landmark rulings.", "fr": "Les avocats saluent des décisions historiques." } }
|
||||
{
|
||||
"h": { "en": "Freedoms widen, say rights groups", "fr": "Les libertés s'élargissent, selon les ONG" },
|
||||
"d": {
|
||||
"en": "Press and assembly face fewer limits.",
|
||||
"fr": "La presse et les rassemblements sont moins entravés."
|
||||
}
|
||||
},
|
||||
{
|
||||
"h": { "en": "Courts restore protections", "fr": "Les tribunaux rétablissent des protections" },
|
||||
"d": { "en": "Lawyers hail landmark rulings.", "fr": "Les avocats saluent des décisions historiques." }
|
||||
}
|
||||
],
|
||||
"down": [
|
||||
{ "h": { "en": "Rights groups sound the alarm", "fr": "Les ONG tirent la sonnette d'alarme" },
|
||||
"d": { "en": "Curbs on press and assembly tighten.", "fr": "Les restrictions sur la presse et les rassemblements se durcissent." } },
|
||||
{ "h": { "en": "Civil liberties under pressure", "fr": "Les libertés civiles sous pression" },
|
||||
"d": { "en": "Critics speak of a creeping crackdown.", "fr": "Les critiques dénoncent une répression rampante." } }
|
||||
{
|
||||
"h": { "en": "Rights groups sound the alarm", "fr": "Les ONG tirent la sonnette d'alarme" },
|
||||
"d": {
|
||||
"en": "Curbs on press and assembly tighten.",
|
||||
"fr": "Les restrictions sur la presse et les rassemblements se durcissent."
|
||||
}
|
||||
},
|
||||
{
|
||||
"h": { "en": "Civil liberties under pressure", "fr": "Les libertés civiles sous pression" },
|
||||
"d": { "en": "Critics speak of a creeping crackdown.", "fr": "Les critiques dénoncent une répression rampante." }
|
||||
}
|
||||
]
|
||||
},
|
||||
"opinion": {
|
||||
"up": [
|
||||
{ "h": { "en": "Public mood brightens", "fr": "L'humeur du public s'éclaircit" },
|
||||
"d": { "en": "Polls show growing approval of the head of state.", "fr": "Les sondages montrent une approbation croissante du chef de l'État." } },
|
||||
{ "h": { "en": "Approval ratings climb", "fr": "La cote de popularité grimpe" },
|
||||
"d": { "en": "Citizens credit recent measures.", "fr": "Les citoyens saluent les dernières mesures." } }
|
||||
{
|
||||
"h": { "en": "Public mood brightens", "fr": "L'humeur du public s'éclaircit" },
|
||||
"d": {
|
||||
"en": "Polls show growing approval of the head of state.",
|
||||
"fr": "Les sondages montrent une approbation croissante du chef de l'État."
|
||||
}
|
||||
},
|
||||
{
|
||||
"h": { "en": "Approval ratings climb", "fr": "La cote de popularité grimpe" },
|
||||
"d": { "en": "Citizens credit recent measures.", "fr": "Les citoyens saluent les dernières mesures." }
|
||||
}
|
||||
],
|
||||
"down": [
|
||||
{ "h": { "en": "Anger grows among the public", "fr": "La colère monte dans la population" },
|
||||
"d": { "en": "Approval slides in the latest polls.", "fr": "La cote de popularité chute dans les derniers sondages." } },
|
||||
{ "h": { "en": "Discontent in the queues", "fr": "Le mécontentement gagne les files d'attente" },
|
||||
"d": { "en": "Citizens blame the government.", "fr": "Les citoyens accusent le gouvernement." } }
|
||||
{
|
||||
"h": { "en": "Anger grows among the public", "fr": "La colère monte dans la population" },
|
||||
"d": {
|
||||
"en": "Approval slides in the latest polls.",
|
||||
"fr": "La cote de popularité chute dans les derniers sondages."
|
||||
}
|
||||
},
|
||||
{
|
||||
"h": { "en": "Discontent in the queues", "fr": "Le mécontentement gagne les files d'attente" },
|
||||
"d": { "en": "Citizens blame the government.", "fr": "Les citoyens accusent le gouvernement." }
|
||||
}
|
||||
]
|
||||
},
|
||||
"economy": {
|
||||
"up": [
|
||||
{ "h": { "en": "Business confidence recovers", "fr": "La confiance des entreprises revient" },
|
||||
"d": { "en": "Factories report fuller order books.", "fr": "Les usines annoncent des carnets de commandes mieux remplis." } },
|
||||
{ "h": { "en": "Economy gathers pace", "fr": "L'économie prend de la vitesse" },
|
||||
"d": { "en": "Investors return to the capital.", "fr": "Les investisseurs reviennent dans la capitale." } }
|
||||
{
|
||||
"h": { "en": "Business confidence recovers", "fr": "La confiance des entreprises revient" },
|
||||
"d": {
|
||||
"en": "Factories report fuller order books.",
|
||||
"fr": "Les usines annoncent des carnets de commandes mieux remplis."
|
||||
}
|
||||
},
|
||||
{
|
||||
"h": { "en": "Economy gathers pace", "fr": "L'économie prend de la vitesse" },
|
||||
"d": { "en": "Investors return to the capital.", "fr": "Les investisseurs reviennent dans la capitale." }
|
||||
}
|
||||
],
|
||||
"down": [
|
||||
{ "h": { "en": "Economy loses momentum", "fr": "L'économie perd de son élan" },
|
||||
"d": { "en": "Firms cut their hiring plans.", "fr": "Les entreprises réduisent leurs projets d'embauche." } },
|
||||
{ "h": { "en": "Investors turn cautious", "fr": "Les investisseurs se montrent prudents" },
|
||||
"d": { "en": "The stock exchange ends the week lower.", "fr": "La Bourse termine la semaine en baisse." } }
|
||||
{
|
||||
"h": { "en": "Economy loses momentum", "fr": "L'économie perd de son élan" },
|
||||
"d": { "en": "Firms cut their hiring plans.", "fr": "Les entreprises réduisent leurs projets d'embauche." }
|
||||
},
|
||||
{
|
||||
"h": { "en": "Investors turn cautious", "fr": "Les investisseurs se montrent prudents" },
|
||||
"d": { "en": "The stock exchange ends the week lower.", "fr": "La Bourse termine la semaine en baisse." }
|
||||
}
|
||||
]
|
||||
},
|
||||
"hardPower": {
|
||||
"up": [
|
||||
{ "h": { "en": "Armed forces strengthened", "fr": "Les forces armées se renforcent" },
|
||||
"d": { "en": "Generals welcome new readiness figures.", "fr": "Les généraux se félicitent des nouveaux chiffres de préparation." } },
|
||||
{ "h": { "en": "Military posture hardens", "fr": "La posture militaire se durcit" },
|
||||
"d": { "en": "Neighbours take note.", "fr": "Les voisins en prennent note." } }
|
||||
{
|
||||
"h": { "en": "Armed forces strengthened", "fr": "Les forces armées se renforcent" },
|
||||
"d": {
|
||||
"en": "Generals welcome new readiness figures.",
|
||||
"fr": "Les généraux se félicitent des nouveaux chiffres de préparation."
|
||||
}
|
||||
},
|
||||
{
|
||||
"h": { "en": "Military posture hardens", "fr": "La posture militaire se durcit" },
|
||||
"d": { "en": "Neighbours take note.", "fr": "Les voisins en prennent note." }
|
||||
}
|
||||
],
|
||||
"down": [
|
||||
{ "h": { "en": "Army readiness questioned", "fr": "La préparation de l'armée en question" },
|
||||
"d": { "en": "Equipment shortages reported.", "fr": "Des pénuries d'équipement sont signalées." } },
|
||||
{ "h": { "en": "Defence capacity slipping", "fr": "Les capacités de défense s'érodent" },
|
||||
"d": { "en": "Foreign analysts see a weaker military.", "fr": "Des analystes étrangers voient une armée affaiblie." } }
|
||||
{
|
||||
"h": { "en": "Army readiness questioned", "fr": "La préparation de l'armée en question" },
|
||||
"d": { "en": "Equipment shortages reported.", "fr": "Des pénuries d'équipement sont signalées." }
|
||||
},
|
||||
{
|
||||
"h": { "en": "Defence capacity slipping", "fr": "Les capacités de défense s'érodent" },
|
||||
"d": {
|
||||
"en": "Foreign analysts see a weaker military.",
|
||||
"fr": "Des analystes étrangers voient une armée affaiblie."
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"softPower": {
|
||||
"up": [
|
||||
{ "h": { "en": "Country's standing grows abroad", "fr": "Le prestige du pays grandit à l'étranger" },
|
||||
"d": { "en": "Foreign press praises the national image.", "fr": "La presse étrangère loue l'image du pays." } },
|
||||
{ "h": { "en": "Cultural influence expands", "fr": "L'influence culturelle s'étend" },
|
||||
"d": { "en": "Embassies report warm receptions.", "fr": "Les ambassades signalent un accueil chaleureux." } }
|
||||
{
|
||||
"h": { "en": "Country's standing grows abroad", "fr": "Le prestige du pays grandit à l'étranger" },
|
||||
"d": { "en": "Foreign press praises the national image.", "fr": "La presse étrangère loue l'image du pays." }
|
||||
},
|
||||
{
|
||||
"h": { "en": "Cultural influence expands", "fr": "L'influence culturelle s'étend" },
|
||||
"d": { "en": "Embassies report warm receptions.", "fr": "Les ambassades signalent un accueil chaleureux." }
|
||||
}
|
||||
],
|
||||
"down": [
|
||||
{ "h": { "en": "Reputation abroad takes a hit", "fr": "La réputation à l'étranger en prend un coup" },
|
||||
"d": { "en": "Foreign editorials turn critical.", "fr": "Les éditoriaux étrangers se font critiques." } },
|
||||
{ "h": { "en": "Diplomatic isolation feared", "fr": "On craint l'isolement diplomatique" },
|
||||
"d": { "en": "Partners cool towards the capital.", "fr": "Les partenaires se refroidissent envers la capitale." } }
|
||||
{
|
||||
"h": { "en": "Reputation abroad takes a hit", "fr": "La réputation à l'étranger en prend un coup" },
|
||||
"d": { "en": "Foreign editorials turn critical.", "fr": "Les éditoriaux étrangers se font critiques." }
|
||||
},
|
||||
{
|
||||
"h": { "en": "Diplomatic isolation feared", "fr": "On craint l'isolement diplomatique" },
|
||||
"d": { "en": "Partners cool towards the capital.", "fr": "Les partenaires se refroidissent envers la capitale." }
|
||||
}
|
||||
]
|
||||
},
|
||||
"unrest": {
|
||||
"up": [
|
||||
{ "h": { "en": "Protests spread across the country", "fr": "Les manifestations gagnent tout le pays" },
|
||||
"d": { "en": "Police on alert as crowds gather.", "fr": "La police en alerte alors que les foules se rassemblent." } }
|
||||
{
|
||||
"h": { "en": "Protests spread across the country", "fr": "Les manifestations gagnent tout le pays" },
|
||||
"d": {
|
||||
"en": "Police on alert as crowds gather.",
|
||||
"fr": "La police en alerte alors que les foules se rassemblent."
|
||||
}
|
||||
}
|
||||
],
|
||||
"down": [
|
||||
{ "h": { "en": "Tensions ease in the streets", "fr": "Les tensions retombent dans la rue" },
|
||||
"d": { "en": "Protest movements lose steam.", "fr": "Les mouvements de protestation s'essoufflent." } }
|
||||
{
|
||||
"h": { "en": "Tensions ease in the streets", "fr": "Les tensions retombent dans la rue" },
|
||||
"d": { "en": "Protest movements lose steam.", "fr": "Les mouvements de protestation s'essoufflent." }
|
||||
}
|
||||
]
|
||||
},
|
||||
"armyLoyalty": {
|
||||
"up": [
|
||||
{ "h": { "en": "Barracks reaffirm their loyalty", "fr": "Les casernes réaffirment leur loyauté" },
|
||||
"d": { "en": "Officers pledge allegiance.", "fr": "Les officiers prêtent allégeance." } }
|
||||
{
|
||||
"h": { "en": "Barracks reaffirm their loyalty", "fr": "Les casernes réaffirment leur loyauté" },
|
||||
"d": { "en": "Officers pledge allegiance.", "fr": "Les officiers prêtent allégeance." }
|
||||
}
|
||||
],
|
||||
"down": [
|
||||
{ "h": { "en": "Unease reported in the barracks", "fr": "Malaise signalé dans les casernes" },
|
||||
"d": { "en": "Officers whisper about the leadership.", "fr": "Les officiers murmurent à propos de la direction." } }
|
||||
{
|
||||
"h": { "en": "Unease reported in the barracks", "fr": "Malaise signalé dans les casernes" },
|
||||
"d": {
|
||||
"en": "Officers whisper about the leadership.",
|
||||
"fr": "Les officiers murmurent à propos de la direction."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"var": {
|
||||
"growth": {
|
||||
"up": [
|
||||
{ "h": { "en": "Growth picks up", "fr": "La croissance repart" },
|
||||
"d": { "en": "Statisticians revise forecasts upward.", "fr": "Les statisticiens revoient leurs prévisions à la hausse." } }
|
||||
{
|
||||
"h": { "en": "Growth picks up", "fr": "La croissance repart" },
|
||||
"d": {
|
||||
"en": "Statisticians revise forecasts upward.",
|
||||
"fr": "Les statisticiens revoient leurs prévisions à la hausse."
|
||||
}
|
||||
}
|
||||
],
|
||||
"down": [
|
||||
{ "h": { "en": "Growth slows", "fr": "La croissance ralentit" },
|
||||
"d": { "en": "Forecasts are revised downward.", "fr": "Les prévisions sont revues à la baisse." } }
|
||||
{
|
||||
"h": { "en": "Growth slows", "fr": "La croissance ralentit" },
|
||||
"d": { "en": "Forecasts are revised downward.", "fr": "Les prévisions sont revues à la baisse." }
|
||||
}
|
||||
]
|
||||
},
|
||||
"unemployment": {
|
||||
"up": [
|
||||
{ "h": { "en": "Jobless queues lengthen", "fr": "Les files de chômeurs s'allongent" },
|
||||
"d": { "en": "Unemployment edges higher.", "fr": "Le chômage progresse." } }
|
||||
{
|
||||
"h": { "en": "Jobless queues lengthen", "fr": "Les files de chômeurs s'allongent" },
|
||||
"d": { "en": "Unemployment edges higher.", "fr": "Le chômage progresse." }
|
||||
}
|
||||
],
|
||||
"down": [
|
||||
{ "h": { "en": "Unemployment falls", "fr": "Le chômage recule" },
|
||||
"d": { "en": "Employment offices report more vacancies.", "fr": "Les agences pour l'emploi signalent plus d'offres." } }
|
||||
{
|
||||
"h": { "en": "Unemployment falls", "fr": "Le chômage recule" },
|
||||
"d": {
|
||||
"en": "Employment offices report more vacancies.",
|
||||
"fr": "Les agences pour l'emploi signalent plus d'offres."
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"inflation": {
|
||||
"up": [
|
||||
{ "h": { "en": "Prices climb faster", "fr": "Les prix s'envolent" },
|
||||
"d": { "en": "Households feel the squeeze at the market.", "fr": "Les ménages ressentent la hausse au marché." } }
|
||||
{
|
||||
"h": { "en": "Prices climb faster", "fr": "Les prix s'envolent" },
|
||||
"d": { "en": "Households feel the squeeze at the market.", "fr": "Les ménages ressentent la hausse au marché." }
|
||||
}
|
||||
],
|
||||
"down": [
|
||||
{ "h": { "en": "Inflation cools", "fr": "L'inflation se calme" },
|
||||
"d": { "en": "Prices stabilise in the shops.", "fr": "Les prix se stabilisent en magasin." } }
|
||||
{
|
||||
"h": { "en": "Inflation cools", "fr": "L'inflation se calme" },
|
||||
"d": { "en": "Prices stabilise in the shops.", "fr": "Les prix se stabilisent en magasin." }
|
||||
}
|
||||
]
|
||||
},
|
||||
"debtRatio": {
|
||||
"up": [
|
||||
{ "h": { "en": "Public debt keeps growing", "fr": "La dette publique continue d'augmenter" },
|
||||
"d": { "en": "Bond traders watch the deficit.", "fr": "Les opérateurs obligataires surveillent le déficit." } }
|
||||
{
|
||||
"h": { "en": "Public debt keeps growing", "fr": "La dette publique continue d'augmenter" },
|
||||
"d": { "en": "Bond traders watch the deficit.", "fr": "Les opérateurs obligataires surveillent le déficit." }
|
||||
}
|
||||
],
|
||||
"down": [
|
||||
{ "h": { "en": "Debt burden eases", "fr": "Le poids de la dette s'allège" },
|
||||
"d": { "en": "The Treasury reports progress on the deficit.", "fr": "Le Trésor annonce des progrès sur le déficit." } }
|
||||
{
|
||||
"h": { "en": "Debt burden eases", "fr": "Le poids de la dette s'allège" },
|
||||
"d": {
|
||||
"en": "The Treasury reports progress on the deficit.",
|
||||
"fr": "Le Trésor annonce des progrès sur le déficit."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"level": {
|
||||
"unrest.high": [
|
||||
{ "h": { "en": "Capital braces for unrest", "fr": "La capitale se prépare aux troubles" },
|
||||
"d": { "en": "Police leave cancelled as crowds swell.", "fr": "Les congés de la police sont annulés alors que les foules grossissent." } }
|
||||
{
|
||||
"h": { "en": "Capital braces for unrest", "fr": "La capitale se prépare aux troubles" },
|
||||
"d": {
|
||||
"en": "Police leave cancelled as crowds swell.",
|
||||
"fr": "Les congés de la police sont annulés alors que les foules grossissent."
|
||||
}
|
||||
}
|
||||
],
|
||||
"opinion.low": [
|
||||
{ "h": { "en": "A nation out of patience", "fr": "Une nation à bout de patience" },
|
||||
"d": { "en": "The opposition calls for the leader to go.", "fr": "L'opposition réclame le départ du dirigeant." } }
|
||||
{
|
||||
"h": { "en": "A nation out of patience", "fr": "Une nation à bout de patience" },
|
||||
"d": { "en": "The opposition calls for the leader to go.", "fr": "L'opposition réclame le départ du dirigeant." }
|
||||
}
|
||||
],
|
||||
"debt.high": [
|
||||
{ "h": { "en": "Lenders demand a premium", "fr": "Les prêteurs exigent une prime" },
|
||||
"d": { "en": "The Treasury warns of a debt crisis.", "fr": "Le Trésor met en garde contre une crise de la dette." } }
|
||||
{
|
||||
"h": { "en": "Lenders demand a premium", "fr": "Les prêteurs exigent une prime" },
|
||||
"d": { "en": "The Treasury warns of a debt crisis.", "fr": "Le Trésor met en garde contre une crise de la dette." }
|
||||
}
|
||||
],
|
||||
"stability.low": [
|
||||
{ "h": { "en": "Government on the brink", "fr": "Le gouvernement au bord du gouffre" },
|
||||
"d": { "en": "Allies quietly count the votes.", "fr": "Les alliés comptent discrètement les voix." } }
|
||||
{
|
||||
"h": { "en": "Government on the brink", "fr": "Le gouvernement au bord du gouffre" },
|
||||
"d": { "en": "Allies quietly count the votes.", "fr": "Les alliés comptent discrètement les voix." }
|
||||
}
|
||||
],
|
||||
"civilRights.low": [
|
||||
{ "h": { "en": "Dissent muzzled", "fr": "La contestation muselée" },
|
||||
"d": { "en": "Prisons fill as freedoms shrink.", "fr": "Les prisons se remplissent alors que les libertés se réduisent." } }
|
||||
{
|
||||
"h": { "en": "Dissent muzzled", "fr": "La contestation muselée" },
|
||||
"d": {
|
||||
"en": "Prisons fill as freedoms shrink.",
|
||||
"fr": "Les prisons se remplissent alors que les libertés se réduisent."
|
||||
}
|
||||
}
|
||||
],
|
||||
"inflation.high": [
|
||||
{ "h": { "en": "Prices spiral out of control", "fr": "Les prix s'envolent hors de contrôle" },
|
||||
"d": { "en": "Shoppers face another round of rises.", "fr": "Les consommateurs subissent une nouvelle vague de hausses." } }
|
||||
{
|
||||
"h": { "en": "Prices spiral out of control", "fr": "Les prix s'envolent hors de contrôle" },
|
||||
"d": {
|
||||
"en": "Shoppers face another round of rises.",
|
||||
"fr": "Les consommateurs subissent une nouvelle vague de hausses."
|
||||
}
|
||||
}
|
||||
],
|
||||
"deficit.high": [
|
||||
{ "h": { "en": "Treasury bleeds red ink", "fr": "Le Trésor saigne" },
|
||||
"d": { "en": "The budget gap widens to alarming levels.", "fr": "Le déficit budgétaire atteint des niveaux alarmants." } }
|
||||
{
|
||||
"h": { "en": "Treasury bleeds red ink", "fr": "Le Trésor saigne" },
|
||||
"d": {
|
||||
"en": "The budget gap widens to alarming levels.",
|
||||
"fr": "Le déficit budgétaire atteint des niveaux alarmants."
|
||||
}
|
||||
}
|
||||
],
|
||||
"army.low": [
|
||||
{ "h": { "en": "The army grows restless", "fr": "L'armée s'impatiente" },
|
||||
"d": { "en": "The generals' loyalty is in doubt.", "fr": "La loyauté des généraux est mise en doute." } }
|
||||
{
|
||||
"h": { "en": "The army grows restless", "fr": "L'armée s'impatiente" },
|
||||
"d": { "en": "The generals' loyalty is in doubt.", "fr": "La loyauté des généraux est mise en doute." }
|
||||
}
|
||||
],
|
||||
"world.slump": [
|
||||
{
|
||||
"h": { "en": "Global slump hits export orders", "fr": "La récession mondiale frappe les commandes à l'export" },
|
||||
"d": {
|
||||
"en": "Trading partners cut back; the Treasury sees receipts fall.",
|
||||
"fr": "Les partenaires commerciaux freinent ; le Trésor voit ses recettes reculer."
|
||||
}
|
||||
},
|
||||
{
|
||||
"h": { "en": "Foreign markets sour on the region", "fr": "Les marchés étrangers se détournent de la région" },
|
||||
"d": {
|
||||
"en": "Economists blame a downturn abroad, not policy at home.",
|
||||
"fr": "Les économistes accusent la conjoncture mondiale, pas la politique intérieure."
|
||||
}
|
||||
}
|
||||
],
|
||||
"world.boom": [
|
||||
{
|
||||
"h": { "en": "World demand lifts the economy", "fr": "La demande mondiale tire l'économie" },
|
||||
"d": {
|
||||
"en": "Exporters report full order books.",
|
||||
"fr": "Les exportateurs affichent des carnets de commandes pleins."
|
||||
}
|
||||
},
|
||||
{
|
||||
"h": { "en": "A tailwind from abroad", "fr": "Un vent favorable venu de l'étranger" },
|
||||
"d": { "en": "Global growth is good news for the Treasury.", "fr": "La croissance mondiale réjouit le Trésor." }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
// title, body, yes: Option, no: Option }
|
||||
// Option: { label, effects: [...see modifiers.js], setFlags?, clearFlags?, followUp?: { id, inDays } }
|
||||
// Condition stats: any indicator, growth/unemployment/inflation, "debt" (% of GDP),
|
||||
// "deficit" (% of GDP) and "q:<sector>" (sector quality).
|
||||
// "deficit" (% of GDP), "q:<sector>" (sector quality), "ratio:<sector>" (budget / need)
|
||||
// and "tax:<income|corporate|vat>" (rate).
|
||||
|
||||
import { IND_IDS, VAR_IDS, clamp } from "./state.js";
|
||||
import { computeFiscal } from "./economy.js";
|
||||
@@ -20,6 +21,8 @@ function statValue(state, key, fiscal) {
|
||||
if (key === "debt") return fiscal.debtRatio;
|
||||
if (key === "deficit") return fiscal.deficit;
|
||||
if (key.startsWith("q:")) return state.sectors[key.slice(2)]?.quality ?? NaN;
|
||||
if (key.startsWith("ratio:")) { const s = state.sectors[key.slice(6)]; return s ? s.alloc / s.need : NaN; }
|
||||
if (key.startsWith("tax:")) return state.economy.taxes[key.slice(4)] ?? NaN;
|
||||
return NaN; // unknown key: never eligible (content tests catch typos)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ export function computeFiscal(state, bal) {
|
||||
const e = state.economy;
|
||||
let revenue = e.otherRevenue;
|
||||
for (const id of TAX_IDS) revenue += taxRevenue(id, e.taxes[id], bal);
|
||||
// Receipts follow the cycle: a slump costs revenue, a boom adds some.
|
||||
revenue += (bal.fiscal?.growthSens ?? 0) * (e.growth - state.ref.vars.growth);
|
||||
|
||||
const debtRatio = (e.debt / e.gdp) * 100;
|
||||
const rate = interestRate(debtRatio, bal);
|
||||
@@ -85,4 +87,5 @@ export function setBudget(state, bal, { alloc = {}, taxes = {} } = {}) {
|
||||
|
||||
export function confirmBudget(state) {
|
||||
state.pending.review = false;
|
||||
state.risk.emergency = false;
|
||||
}
|
||||
|
||||
+15
-2
@@ -4,6 +4,7 @@ import { STATE_VERSION, IND_IDS, VAR_IDS, qualityFromRatio } from "./state.js";
|
||||
import { computeFiscal, computeDevs, advanceEconomy } from "./economy.js";
|
||||
import { applyShifts, ageBias } from "./modifiers.js";
|
||||
import { stepSectors } from "./sectors.js";
|
||||
import { stepWorld } from "./world.js";
|
||||
import { stepIndicators, stepVars } from "./indicators.js";
|
||||
import { checkGameOver } from "./gameover.js";
|
||||
|
||||
@@ -43,6 +44,7 @@ export function createGame({ scenario, balance, seed = Date.now(), countryName,
|
||||
sectors,
|
||||
indicators: { ...scenario.indicators },
|
||||
modifiers: [],
|
||||
world: { cycle: 0 }, // global business cycle, see world.js
|
||||
flags: {},
|
||||
cardHistory: [],
|
||||
pendingCard: null,
|
||||
@@ -51,7 +53,8 @@ export function createGame({ scenario, balance, seed = Date.now(), countryName,
|
||||
// Any true flag here pauses time (see advanceDay).
|
||||
pending: { profile: !PRONOUNS.includes(pronouns), weeklyCard: true, review: true, alert: false },
|
||||
over: null,
|
||||
risk: { revolution: 0, coup: 0, impeachment: 0, defaultZone: false }, // days in each danger zone
|
||||
// days in each danger zone; emergency = an unscheduled budget session is open (see advanceDay)
|
||||
risk: { revolution: 0, coup: 0, impeachment: 0, defaultZone: false, emergency: false, lastEmergency: -999 },
|
||||
alerts: [], // urgent telegrams waiting to be read, oldest first
|
||||
history: { t: [], series: {} },
|
||||
log: [],
|
||||
@@ -111,6 +114,7 @@ export function advanceDay(state, bal) {
|
||||
if (isWaiting(state)) return { advanced: false, blocked: "pending", events: null };
|
||||
|
||||
applyShifts(state, bal);
|
||||
stepWorld(state, bal);
|
||||
const fiscal = computeFiscal(state, bal);
|
||||
const devs = computeDevs(state, bal, fiscal);
|
||||
|
||||
@@ -123,6 +127,7 @@ export function advanceDay(state, bal) {
|
||||
state.day += 1;
|
||||
|
||||
const events = { weeklyCard: false, review: false, termEnd: false, gameOver: null };
|
||||
const alertsBefore = state.alerts.length;
|
||||
const fallen = checkGameOver(state, bal);
|
||||
if (fallen) {
|
||||
state.over = { type: fallen, day: state.day };
|
||||
@@ -135,7 +140,15 @@ export function advanceDay(state, bal) {
|
||||
} else {
|
||||
if (state.day % bal.time.historyEvery === 0) recordHistory(state, bal);
|
||||
if (state.day % bal.time.daysPerWeek === 0) state.pending.weeklyCard = events.weeklyCard = true;
|
||||
if (state.day % bal.time.daysPerQuarter === 0) state.pending.review = events.review = true;
|
||||
if (state.day % bal.time.daysPerQuarter === 0) {
|
||||
state.pending.review = events.review = true;
|
||||
state.risk.emergency = false;
|
||||
} else if (state.alerts.length > alertsBefore && state.day - state.risk.lastEmergency >= bal.gameOver.emergencyCooldown) {
|
||||
// A new danger warning: the cabinet meets at once, so the player can react before the next quarter.
|
||||
state.pending.review = events.review = true;
|
||||
state.risk.emergency = true;
|
||||
state.risk.lastEmergency = state.day;
|
||||
}
|
||||
}
|
||||
return { advanced: true, blocked: null, events };
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export function checkGameOver(state, bal) {
|
||||
}
|
||||
|
||||
const inImpeachment = ind.civilRights < g.impeachment.civilRightsBelow && ind.opinion < g.impeachment.opinionBelow;
|
||||
const inCoup = ind.armyLoyalty < g.coup.armyBelow && ind.stability < g.coup.stabilityBelow;
|
||||
const inCoup = (ind.armyLoyalty < g.coup.armyBelow && ind.stability < g.coup.stabilityBelow) || ind.armyLoyalty < (g.coup.armyAlone ?? 0);
|
||||
const inRevolution = ind.unrest > g.revolution.unrestAbove && ind.opinion < g.revolution.opinionBelow;
|
||||
track("impeachment", inImpeachment, "reset");
|
||||
track("coup", inCoup, 1);
|
||||
|
||||
@@ -6,11 +6,17 @@ import { IND_IDS, VAR_IDS, clamp } from "./state.js";
|
||||
import { biasFor } from "./modifiers.js";
|
||||
import { nextNoise } from "./rng.js";
|
||||
|
||||
function computeTarget(def, base, group, key, state, devs) {
|
||||
function computeTarget(def, base, group, key, state, devs, bal) {
|
||||
let t = base;
|
||||
const gain = group === "ind" ? bal.gain ?? 1 : 1;
|
||||
for (const [id, w] of Object.entries(def.sectors ?? {})) {
|
||||
t += w * (state.sectors[id].quality - state.ref.sectors[id]);
|
||||
t += gain * w * (state.sectors[id].quality - state.ref.sectors[id]);
|
||||
}
|
||||
// Going big pays off: extra weight for quality above `from` (excess) or below it (short).
|
||||
for (const [id, [from, w]] of Object.entries(def.excess ?? {})) t += w * Math.max(0, state.sectors[id].quality - from);
|
||||
for (const [id, [from, w]] of Object.entries(def.short ?? {})) t -= w * Math.max(0, from - state.sectors[id].quality);
|
||||
for (const [k, w] of Object.entries(def.world ?? {})) t += w * state.world[k];
|
||||
if (def.drift) t += ((def.drift * (bal.driftScale ?? 1)) * state.day) / 365;
|
||||
for (const [id, w] of Object.entries(def.alloc ?? {})) {
|
||||
const s = state.sectors[id];
|
||||
t += w * (s.alloc / s.need - state.ref.ratio[id]);
|
||||
@@ -28,7 +34,7 @@ export function stepVars(state, bal, devs) {
|
||||
const next = {};
|
||||
for (const id of VAR_IDS) {
|
||||
const def = bal.vars[id];
|
||||
const target = computeTarget(def, state.ref.vars[id], "var", id, state, devs);
|
||||
const target = computeTarget(def, state.ref.vars[id], "var", id, state, devs, bal);
|
||||
const v = state.economy[id];
|
||||
next[id] = clamp(v + (target - v) / def.tau + nextNoise(state, def.noise), def.min, def.max);
|
||||
}
|
||||
@@ -39,7 +45,7 @@ export function stepIndicators(state, bal, devs) {
|
||||
const next = {};
|
||||
for (const id of IND_IDS) {
|
||||
const def = bal.indicators[id];
|
||||
const target = computeTarget(def, state.ref.ind[id], "ind", id, state, devs);
|
||||
const target = computeTarget(def, state.ref.ind[id], "ind", id, state, devs, bal);
|
||||
const v = state.indicators[id];
|
||||
next[id] = clamp(v + (target - v) / def.tau + nextNoise(state, def.noise ?? bal.noise.indicator), 0, 100);
|
||||
}
|
||||
|
||||
+10
-5
@@ -13,12 +13,17 @@ export const METRIC_IDS = [
|
||||
|
||||
const mean = (xs) => (xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : 0);
|
||||
|
||||
// Averages over the whole term, plus the final debt ratio.
|
||||
function termAverages(state) {
|
||||
const AVERAGED = ["order", "stability", "civilRights", "opinion", "economy", "hardPower", "softPower", "unrest", "armyLoyalty", "growth", "unemployment", "inflation"];
|
||||
|
||||
// Averages over the whole term, plus the final debt ratio. With `lastYearShare` the average
|
||||
// leans on the last 52 weekly samples: the historian judges what you leave behind, so a
|
||||
// boom bought on credit does not pay for itself.
|
||||
function termAverages(state, lastYearShare = 0) {
|
||||
const s = state.history.series;
|
||||
const m = {};
|
||||
for (const id of ["order", "stability", "civilRights", "opinion", "economy", "hardPower", "softPower", "unrest", "armyLoyalty", "growth", "unemployment", "inflation"]) {
|
||||
m[id] = mean(s[id] ?? []);
|
||||
for (const id of AVERAGED) {
|
||||
const all = s[id] ?? [];
|
||||
m[id] = lastYearShare > 0 ? (1 - lastYearShare) * mean(all) + lastYearShare * mean(all.slice(-52)) : mean(all);
|
||||
}
|
||||
m.debt = s.debtRatio?.at(-1) ?? 0;
|
||||
m.years = state.day / 365;
|
||||
@@ -76,7 +81,7 @@ function meets(entry, m) {
|
||||
// endgame: the parsed data/endgame.json (its `titles` list is in priority order).
|
||||
export function buildReview(state, bal, cards, endgame) {
|
||||
const metrics = termAverages(state);
|
||||
let score = computeScore(metrics, bal);
|
||||
let score = computeScore(termAverages(state, bal.score.lastYearShare ?? 0), bal);
|
||||
if (state.over && state.over.type !== "term") score = Math.min(score, bal.score.overCap);
|
||||
metrics.score = score;
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import { biasFor } from "./modifiers.js";
|
||||
export function stepSectors(state, bal, fiscal) {
|
||||
const s = bal.sector;
|
||||
for (const [id, sec] of Object.entries(state.sectors)) {
|
||||
// Needs grow with population, ageing and prices: standing still buys less each year.
|
||||
sec.need *= Math.pow(1 + (bal.needGrowth?.[id] ?? 0), 1 / 365);
|
||||
let target = qualityFromRatio(sec.alloc / sec.need, bal) + biasFor(state, "sector", id);
|
||||
if (id === "industry" && fiscal.debtRatio > s.industryDebtPenaltyFrom) {
|
||||
target -= s.industryDebtPenaltyPerPoint * (fiscal.debtRatio - s.industryDebtPenaltyFrom);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Shared constants and tiny helpers. No game logic here.
|
||||
|
||||
export const STATE_VERSION = 1;
|
||||
export const STATE_VERSION = 2;
|
||||
|
||||
export const IND_IDS = [
|
||||
"order", "stability", "civilRights", "opinion", "economy",
|
||||
|
||||
@@ -63,6 +63,9 @@ export function restoreState(s, { scenarios, balance, cards }) {
|
||||
if (m.type === "shift" ? !(isNum(m.perDay) && isNum(m.remaining, 1)) : !(m.type === "bias" && isNum(m.value) && (m.remaining === null || isNum(m.remaining, 1)))) return fail();
|
||||
}
|
||||
|
||||
// World cycle
|
||||
if (!isObj(s.world) || !isNum(s.world.cycle, -50, 50)) return fail();
|
||||
|
||||
// Decisions and flags
|
||||
if (!isObj(s.flags) || !Object.values(s.flags).every((v) => isNum(v))) return fail();
|
||||
if (!Array.isArray(s.cardHistory)) return fail();
|
||||
@@ -77,6 +80,8 @@ export function restoreState(s, { scenarios, balance, cards }) {
|
||||
for (const k of PENDING_KINDS) s.pending[k] = s.pending[k] === true;
|
||||
if (!Array.isArray(s.alerts) || !s.alerts.every((a) => isObj(a) && ALERT_KINDS.includes(a.kind) && isInt(a.day, 0, balance.time.termDays))) return fail();
|
||||
if (!isObj(s.risk) || ![s.risk.revolution, s.risk.coup, s.risk.impeachment].every((v) => isNum(v, 0)) || typeof s.risk.defaultZone !== "boolean") return fail();
|
||||
s.risk.emergency = s.risk.emergency === true;
|
||||
if (!isNum(s.risk.lastEmergency)) s.risk.lastEmergency = -999;
|
||||
if (s.over !== null && !(isObj(s.over) && OVER_TYPES.includes(s.over.type) && isInt(s.over.day, 0, balance.time.termDays))) return fail();
|
||||
|
||||
// History for the charts: one equal-length series per tracked value
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// The world outside the country: a slow business cycle with occasional shocks (global
|
||||
// slumps, rare booms). It moves growth and inflation, so even a perfect budget meets
|
||||
// good and bad years. state.world.cycle is roughly -4 (deep global recession) to +4.
|
||||
|
||||
import { clamp } from "./state.js";
|
||||
import { nextFloat, nextNoise } from "./rng.js";
|
||||
|
||||
export function stepWorld(state, bal) {
|
||||
const w = bal.world;
|
||||
const s = state.world;
|
||||
s.cycle -= s.cycle / w.tau;
|
||||
s.cycle += nextNoise(state, w.noise);
|
||||
if (nextFloat(state) < w.shockChancePerDay) {
|
||||
const size = w.shock * (0.6 + 0.8 * nextFloat(state));
|
||||
s.cycle += nextFloat(state) < w.boomShare ? size * 0.6 : -size;
|
||||
}
|
||||
s.cycle = clamp(s.cycle, -w.max, w.max);
|
||||
}
|
||||
+2
-1
@@ -70,8 +70,9 @@
|
||||
"tax.corporate": "Corporate tax",
|
||||
"tax.vat": "VAT",
|
||||
"ledger.title": "Treasury ledger",
|
||||
"ledger.emergency": "Emergency session: the cabinet has met after the warning. Changes take effect at once.",
|
||||
"ledger.spending": "Spending, % of GDP",
|
||||
"ledger.spendingHint": "The tick on each line marks what the sector needs.",
|
||||
"ledger.spendingHint": "The tick on each line marks what the sector needs. Needs grow a little every year.",
|
||||
"ledger.totalAlloc": "Allocated to sectors",
|
||||
"ledger.revenue": "Revenue: tax rates",
|
||||
"ledger.revenueHint": "The tick marks the rate where revenue peaks. Beyond it, you collect less.",
|
||||
|
||||
+2
-1
@@ -69,9 +69,10 @@
|
||||
"tax.income": "Impôt revenu",
|
||||
"tax.corporate": "Impôt sociétés",
|
||||
"tax.vat": "TVA",
|
||||
"ledger.emergency": "Séance d'urgence : le cabinet s'est réuni après l'alerte. Les changements s'appliquent aussitôt.",
|
||||
"ledger.title": "Registre du Trésor",
|
||||
"ledger.spending": "Dépenses, % du PIB",
|
||||
"ledger.spendingHint": "Le repère sur chaque ligne marque le besoin du secteur.",
|
||||
"ledger.spendingHint": "Le repère sur chaque ligne marque le besoin du secteur. Les besoins augmentent un peu chaque année.",
|
||||
"ledger.totalAlloc": "Alloué aux secteurs",
|
||||
"ledger.revenue": "Recettes : taux d'imposition",
|
||||
"ledger.revenueHint": "Le repère marque le taux où les recettes sont maximales. Au-delà, vous collectez moins.",
|
||||
|
||||
@@ -242,6 +242,7 @@ function devGame() {
|
||||
prepareWeek();
|
||||
if (s.pendingCard) answerCard(s, balance, cards, nextFloat(s) < 0.5 ? "yes" : "no");
|
||||
advanceDay(s, balance);
|
||||
if (params.has("untilAlert") && s.pending.alert) break; // stop on the first danger warning
|
||||
}
|
||||
if (cardsById.has(params.get("card"))) {
|
||||
s.pendingCard = { id: params.get("card"), day: s.day };
|
||||
|
||||
@@ -43,7 +43,10 @@ export function encodeSave(state) {
|
||||
|
||||
// Upgrades older saves one version at a time. Add an entry here whenever STATE_VERSION
|
||||
// changes: MIGRATIONS[n] turns a version n state into a version n+1 state.
|
||||
export const MIGRATIONS = {};
|
||||
export const MIGRATIONS = {
|
||||
// v2 added the world cycle: older saves start it at neutral.
|
||||
1: (s) => ({ ...s, version: 2, world: { cycle: 0 } }),
|
||||
};
|
||||
|
||||
export function migrate(state) {
|
||||
if (!Number.isInteger(state?.version)) return { ok: false, error: "format" };
|
||||
|
||||
@@ -73,6 +73,7 @@ export function createLedger(root, scenario, { onConfirm }) {
|
||||
<div class="ledger__book">
|
||||
<article class="ledger__page ledger__page--left">
|
||||
<h2>${t("ledger.title")}</h2>
|
||||
${state.risk.emergency ? `<p class="ledger__emergency">${t("ledger.emergency")}</p>` : ""}
|
||||
<p class="ledger__sub">${t("ledger.spending")}. ${t("ledger.spendingHint")}</p>
|
||||
<div class="ledger__rows">${sectorRows}</div>
|
||||
<footer class="ledger__total">${t("ledger.totalAlloc")} <b data-total></b></footer>
|
||||
|
||||
@@ -19,6 +19,8 @@ const LEVELS = [
|
||||
["army.low", (s, f) => s.indicators.armyLoyalty <= 35, 1.7],
|
||||
["inflation.high", (s, f) => s.economy.inflation >= 6, 1.5],
|
||||
["deficit.high", (s, f) => f.deficit >= 8, 1.5],
|
||||
["world.slump", (s) => (s.world?.cycle ?? 0) <= -1.8, 1.4],
|
||||
["world.boom", (s) => (s.world?.cycle ?? 0) >= 1.8, 1.2],
|
||||
];
|
||||
|
||||
function candidates(state, tpl) {
|
||||
|
||||
Reference in New Issue
Block a user