From b214f6b8bdb206b61a11d1d6dd5f6bd28eb9e071 Mon Sep 17 00:00:00 2001 From: Brian Fertig Date: Fri, 17 Jul 2026 11:05:01 -0600 Subject: [PATCH] feat(civ): add shield waste mechanic and AI corruption triage - Implement "waste" as corruption's production sibling: distant cities lose shields to waste (half the corruption decay rate), halved by courthouses. - Update city yields, UI summaries, and government screen to display waste alongside corruption and support costs. - AI now builds courthouses early when combined corruption + waste loss exceeds 4 per turn, preventing distant cities from becoming useless. - Refresh building effect tooltips and palace description to reflect waste. --- src/games/civilization/CivilizationAI.js | 7 +++++++ src/games/civilization/CivilizationCityScreen.js | 2 +- src/games/civilization/CivilizationLogic.js | 11 +++++++++-- src/games/civilization/CivilizationScreens.js | 16 ++++++++-------- src/games/civilization/CivilizationTooltips.js | 4 ++-- 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/games/civilization/CivilizationAI.js b/src/games/civilization/CivilizationAI.js index 5d838b7..8839e8c 100644 --- a/src/games/civilization/CivilizationAI.js +++ b/src/games/civilization/CivilizationAI.js @@ -319,6 +319,13 @@ function manageCityBuild(rules, state, civIdx, city, strategy) { setBuild(rules, state, city, 'unit', cvType); return; } + // Corruption triage: distant cities bleed trade to corruption and shields + // to waste — when the combined loss is worth a building's upkeep several + // times over, jump the chain straight to a courthouse (halves both). + if ((yields.corruption + yields.waste) >= 4 && avail.some((b) => b.id === 'courthouse')) { + setBuild(rules, state, city, 'building', 'courthouse'); + return; + } for (const id of DEVELOP_CHAIN) { if (avail.some((b) => b.id === id)) { // Skip aqueduct/sewer until the city is close to its cap. diff --git a/src/games/civilization/CivilizationCityScreen.js b/src/games/civilization/CivilizationCityScreen.js index 69a0d80..a4ae905 100644 --- a/src/games/civilization/CivilizationCityScreen.js +++ b/src/games/civilization/CivilizationCityScreen.js @@ -61,7 +61,7 @@ export function openCityScreen(scene, rules, state, city, onClose) { // Yields breakdown. const lines = [ `Food ${y.food} (need ${y.foodNeed}) surplus ${y.foodSurplus >= 0 ? '+' : ''}${y.foodSurplus}`, - `Shields ${y.shield}${y.supportShields ? ` (support −${y.supportShields})` : ''}`, + `Shields ${y.shield}${y.waste ? ` waste −${y.waste}` : ''}${y.supportShields ? ` (support −${y.supportShields})` : ''}`, `Trade ${y.trade} corruption −${y.corruption}${y.routeTrade ? ` routes +${y.routeTrade}` : ''}`, `Gold +${y.gold} · Science +${y.science} · Upkeep −${y.upkeep}`, ]; diff --git a/src/games/civilization/CivilizationLogic.js b/src/games/civilization/CivilizationLogic.js index 2d149d4..1f7813d 100644 --- a/src/games/civilization/CivilizationLogic.js +++ b/src/games/civilization/CivilizationLogic.js @@ -380,14 +380,21 @@ export function cityYields(rules, state, city) { } const grossShield = Math.floor(shield * shieldMult); - const netShield = Math.max(0, grossShield - supportShields); + // Waste — corruption's production sibling: the same capital-distance decay + // applied to shields, at half strength (full strength would zero out + // distant cities' build boxes entirely under Despotism). Courthouse halves + // it, same as corruption. + let waste = Math.floor(grossShield * gov.corruptionFactor * Math.min(1, dist / 20) * 0.5); + if (city.buildings.courthouse) waste = Math.floor(waste * 0.5); + waste = Math.min(waste, grossShield); + const netShield = Math.max(0, grossShield - waste - supportShields); const foodNeed = city.size * FOOD_PER_CITIZEN + settlerFood; const upkeep = Object.keys(city.buildings) .reduce((sum, id) => sum + (rules.buildings[id]?.upkeep ?? 0), 0); return { food, foodNeed, foodSurplus: food - foodNeed, - shield: netShield, grossShield, supportShields, + shield: netShield, grossShield, waste, supportShields, trade, corruption, netTrade, routeTrade, gold: Math.floor(baseGold * goldMult), science: Math.floor(baseScience * sciMult), upkeep, supportGold, diff --git a/src/games/civilization/CivilizationScreens.js b/src/games/civilization/CivilizationScreens.js index e36ebff..8055e98 100644 --- a/src/games/civilization/CivilizationScreens.js +++ b/src/games/civilization/CivilizationScreens.js @@ -466,12 +466,12 @@ function govBullets(g) { const pro = (text) => out.push({ text, pro: true }); const con = (text) => out.push({ text, pro: false }); if (g.noScience) con('No research — science output is zero'); - if (g.corruptionFactor === 0) pro('No corruption at all'); + if (g.corruptionFactor === 0) pro('No corruption or waste at all'); else if (g.corruptionFactor <= 0.4) { - pro(g.flatCorruption ? 'Low corruption, the same across the whole empire' : 'Low corruption'); - } else if (g.corruptionFactor <= 0.7) pro('Reduced corruption'); - else if (g.corruptionFactor >= 1.5) con('Crippling corruption'); - else con('Heavy corruption, worse far from the capital'); + pro(g.flatCorruption ? 'Low corruption & waste, the same across the whole empire' : 'Low corruption & waste'); + } else if (g.corruptionFactor <= 0.7) pro('Reduced corruption & waste'); + else if (g.corruptionFactor >= 1.5) con('Crippling corruption & waste'); + else con('Heavy corruption & waste, worse far from the capital'); if (g.tradeBonus) pro(`+${g.tradeBonus} trade on tiles already producing trade`); if (g.despotPenalty) con('−1 food/shield/trade on tiles yielding 3 or more'); if (g.freeUnits > 0) pro(`${g.freeUnits} free units per city, then 1 ${g.unitUpkeep}/unit each turn`); @@ -502,12 +502,12 @@ export function openGovernmentScreen(scene, rules, state, onClose) { // aggregated from the same cityYields the cities themselves run on. const cities = Logic.civCities(state, state.humanIndex); const sum = { - gross: 0, support: 0, shield: 0, trade: 0, corruption: 0, netTrade: 0, + gross: 0, waste: 0, support: 0, shield: 0, trade: 0, corruption: 0, netTrade: 0, gold: 0, upkeep: 0, supportGold: 0, science: 0, pop: 0, }; for (const c of cities) { const y = Logic.cityYields(rules, state, c); - sum.gross += y.grossShield; sum.support += y.supportShields; sum.shield += y.shield; + sum.gross += y.grossShield; sum.waste += y.waste; sum.support += y.supportShields; sum.shield += y.shield; sum.trade += y.trade; sum.corruption += y.corruption; sum.netTrade += y.netTrade; sum.gold += y.gold; sum.upkeep += y.upkeep; sum.supportGold += y.supportGold; sum.science += y.science; sum.pop += c.size; @@ -521,7 +521,7 @@ export function openGovernmentScreen(scene, rules, state, onClose) { })); const netGold = sum.gold - sum.upkeep - sum.supportGold; content.add(scene.add.text(left, top + 44, - `Production: ${sum.shield} shields/turn (${sum.gross} gross − ${sum.support} unit support) ` + `Production: ${sum.shield} shields/turn (${sum.gross} gross − ${sum.waste} waste − ${sum.support} unit support) ` + `Trade: ${sum.netTrade}/turn (${sum.trade} − ${sum.corruption} corruption)`, { fontFamily: FONT, fontSize: '19px', color: COLORS.textHex, })); diff --git a/src/games/civilization/CivilizationTooltips.js b/src/games/civilization/CivilizationTooltips.js index ea2f490..e8b6366 100644 --- a/src/games/civilization/CivilizationTooltips.js +++ b/src/games/civilization/CivilizationTooltips.js @@ -43,7 +43,7 @@ const EFFECT_TEXT = { farmfood: (b) => `+${Math.round(b.value * 100)}% food from farmland-improved tiles worked by this city.`, defenseair: (b) => `Multiplies this city's defense against air attacks by ${b.value}.`, power: (b) => `+${Math.round(b.value * 100)}% shield output from the city's Factory (requires a Factory).`, - corruption: (b) => `Cuts this city's corruption by ${Math.round(b.value * 100)}%.`, + corruption: (b) => `Cuts this city's corruption and waste by ${Math.round(b.value * 100)}%.`, gold: (b) => `+${Math.round(b.value * 100)}% gold income from this city's trade.`, veterans: () => 'Units built here start as veterans, with a combat bonus.', sizecap: (b) => (b.id === 'sewersystem' @@ -52,7 +52,7 @@ const EFFECT_TEXT = { oceanfood: (b) => `+${b.value} food from every ocean tile this city works.`, shields: (b) => `+${Math.round(b.value * 100)}% shield (production) output from this city.`, oceanshield: (b) => `+${b.value} shield from every ocean tile this city works.`, - palace: () => 'Makes this your capital — sharply reduces corruption and stores the seat of government.', + palace: () => 'Makes this your capital — corruption and waste grow with distance from it.', defensesea: (b) => `Multiplies this city's defense against naval attacks by ${b.value}.`, };