tweak
This commit is contained in:
parent
5d09ad30d7
commit
ff89ec62fd
4 changed files with 112 additions and 75 deletions
132
engine.js
132
engine.js
|
|
@ -1488,6 +1488,8 @@ agriculturalRadius: 2,
|
|||
tradeLinks: new Set(),
|
||||
activeVisitors: 0,
|
||||
age: 0,
|
||||
ageWear: 0,
|
||||
peakPopulation: seedPopulation,
|
||||
strength: 3,
|
||||
sedentaryCulture: seedSedentary,
|
||||
knowledge: { farming: 0, metallurgy: 0 },
|
||||
|
|
@ -1499,6 +1501,21 @@ tradeValue: 0,
|
|||
tradeReach: 0
|
||||
};
|
||||
}
|
||||
updateCityAging(city) {
|
||||
city.ageWear ??= 0;
|
||||
city.peakPopulation = Math.max(city.peakPopulation || 0, city.population || 0);
|
||||
const cfg = SimConfig.city;
|
||||
const ageStart = cfg.agingStartYears ?? 180;
|
||||
if ((city.age || 0) <= ageStart) return;
|
||||
city.ageWear = clamp(city.ageWear + (cfg.agingDecayPerYear ?? 0.002), 0, 1);
|
||||
}
|
||||
cityAgeCapacityFactor(city) {
|
||||
return clamp(1 - (city?.ageWear || 0), 0, 1);
|
||||
}
|
||||
cityAgePopulationLimit(city) {
|
||||
const peakPopulation = Math.max(city?.peakPopulation || 0, city?.population || 0, 1);
|
||||
return Math.max(0, peakPopulation * this.cityAgeCapacityFactor(city));
|
||||
}
|
||||
absorbUrbanPopulation() {
|
||||
if (!this.cities.length) return;
|
||||
const absorbed = [];
|
||||
|
|
@ -1535,6 +1552,7 @@ return whole + (this.rng.next() < value - whole ? 1 : 0);
|
|||
processCityEconomies() {
|
||||
const w = this.world;
|
||||
for (const city of this.cities) {
|
||||
this.updateCityAging(city);
|
||||
city.agriculturalRadius = clamp(Math.floor(1 + Math.sqrt(city.population) / 4.5), 2, 14);
|
||||
city.pheromoneOutput = clamp(Math.log2(city.population + 1) * 0.09, 0.15, 1.8);
|
||||
city.knowledge ??= { farming: 0, metallurgy: 0 };
|
||||
|
|
@ -1579,13 +1597,22 @@ city.tradeReach = trade.reach;
|
|||
city.tradeValue = 0;
|
||||
city.tradeReach = 0;
|
||||
}
|
||||
if (city.storedResources > city.population * 0.16 && city.population > 0) {
|
||||
const agePopulationLimit = this.cityAgePopulationLimit(city);
|
||||
if (city.storedResources > city.population * 0.16 && city.population > 0 && city.population < agePopulationLimit) {
|
||||
const prosperity = clamp(city.storedResources / Math.max(1, city.population) - 0.12 + trade.value * 0.012, 0, 0.8);
|
||||
const births = Math.max(1, Math.floor(city.population * (0.012 + prosperity * 0.012 + clamp(trade.value, 0, 1.8) * 0.0015)));
|
||||
const birthRoom = Math.max(0, Math.floor(agePopulationLimit - city.population));
|
||||
const births = Math.min(birthRoom, Math.max(1, Math.floor(city.population * (0.012 + prosperity * 0.012 + clamp(trade.value, 0, 1.8) * 0.0015))));
|
||||
city.population += births;
|
||||
city.storedResources -= births * 0.55;
|
||||
addBirthsToComposition(city.ethnicityComposition, births, this.rng);
|
||||
}
|
||||
if (city.population > agePopulationLimit) {
|
||||
const overLimit = city.population - agePopulationLimit;
|
||||
const ageLoss = Math.min(city.population, Math.max(1, Math.ceil(overLimit * (SimConfig.city.agingOverCapAttrition ?? 0.035))));
|
||||
city.population -= ageLoss;
|
||||
removeFromComposition(city.ethnicityComposition, ageLoss);
|
||||
city.strength -= Math.min(0.08, ageLoss / Math.max(1, city.population + ageLoss) * 0.18);
|
||||
}
|
||||
if (city.age > 20 && city.activeVisitors < 2 && city.storedResources < city.population * 0.03 && city.population > 0) {
|
||||
const attrition = Math.max(1, Math.ceil(city.population * (city.activeVisitors === 0 ? 0.025 : 0.010)));
|
||||
city.population -= attrition;
|
||||
|
|
@ -1733,55 +1760,19 @@ const from = this.tradeLinkEndpointId(link.from ?? link.fromCityId ?? link.cityA
|
|||
const to = this.tradeLinkEndpointId(link.to ?? link.toCityId ?? link.cityBId ?? link.bId ?? link.targetId ?? link.destinationId ?? link.target ?? link.destination ?? link.b ?? link.cityB);
|
||||
return from != null && to != null ? [from, to] : null;
|
||||
}
|
||||
polityTradeNeighbors(city, polityId) {
|
||||
if (!city || polityId == null) return [];
|
||||
const neighbors = [];
|
||||
for (const link of this.tradeLinks || []) {
|
||||
const endpoints = this.tradeLinkEndpointIds(link);
|
||||
if (!endpoints) continue;
|
||||
const [from, to] = endpoints;
|
||||
const otherId = from === city.id ? to : to === city.id ? from : null;
|
||||
if (otherId == null) continue;
|
||||
const other = this.getCityById(otherId);
|
||||
if (other?.polityId === polityId) neighbors.push(other);
|
||||
}
|
||||
return neighbors;
|
||||
}
|
||||
tradeAccessToCenter(city, center, polityId) {
|
||||
if (!city || !center || polityId == null) return { reachable: false, hops: Infinity, transitCities: [] };
|
||||
if (city.id === center.id) return { reachable: true, hops: 0, transitCities: [] };
|
||||
if (this.hasDirectTradeConnection(city, center)) return { reachable: true, hops: 1, transitCities: [] };
|
||||
const maxDepth = SimConfig.polityAccess?.maxSearchDepth ?? 8;
|
||||
const visited = new Set([city.id]);
|
||||
const queue = [{ city, hops: 0, transitCities: [] }];
|
||||
while (queue.length) {
|
||||
const current = queue.shift();
|
||||
if (current.hops >= maxDepth) continue;
|
||||
for (const neighbor of this.polityTradeNeighbors(current.city, polityId)) {
|
||||
if (visited.has(neighbor.id)) continue;
|
||||
const hops = current.hops + 1;
|
||||
if (neighbor.id === center.id) return { reachable: true, hops, transitCities: current.transitCities };
|
||||
visited.add(neighbor.id);
|
||||
queue.push({ city: neighbor, hops, transitCities: [...current.transitCities, neighbor] });
|
||||
}
|
||||
}
|
||||
return { reachable: false, hops: Infinity, transitCities: [] };
|
||||
}
|
||||
tradeAccessLoyaltyPenalty(city, center, polity) {
|
||||
const cfg = SimConfig.polityAccess;
|
||||
if (!cfg?.enabled || !city || !center || !polity || city.id === center.id || this.hasDirectTradeConnection(city, center)) return 0;
|
||||
const access = this.tradeAccessToCenter(city, center, polity.id);
|
||||
if (!access.reachable) return clamp(cfg.noAccessPenalty, 0, 0.07);
|
||||
let penalty = cfg.indirectPenalty + Math.max(0, access.hops - 1) * cfg.perHopPenalty;
|
||||
const originEthnicity = this.dominantCityEthnicity(city);
|
||||
for (const transitCity of access.transitCities) {
|
||||
if ((transitCity.loyalty ?? 0.5) < 0.35) penalty += cfg.lowLoyaltyTransitPenalty;
|
||||
const transitEthnicity = this.dominantCityEthnicity(transitCity);
|
||||
if (originEthnicity !== null && transitEthnicity !== null && transitEthnicity !== originEthnicity) {
|
||||
penalty += cfg.foreignTransitPenalty;
|
||||
return clamp(cfg.noAccessPenalty, 0, 0.24);
|
||||
}
|
||||
}
|
||||
return clamp(penalty, 0, 0.07);
|
||||
rollLeaderCharisma() {
|
||||
const cfg = SimConfig.polity;
|
||||
const min = cfg?.charismaMin ?? 0.5;
|
||||
const max = cfg?.charismaMax ?? 3.0;
|
||||
const targetAverage = cfg?.charismaAverage ?? 1.0;
|
||||
const normalizedAverage = clamp((targetAverage - min) / Math.max(0.001, max - min), 0.001, 0.999);
|
||||
const exponent = (1 / normalizedAverage) - 1;
|
||||
return min + (max - min) * Math.pow(this.rng.next(), exponent);
|
||||
}
|
||||
effectiveDistance(cityA, cityB) {
|
||||
let distance = this.distanceBetweenCities(cityA, cityB);
|
||||
|
|
@ -1799,7 +1790,7 @@ color: hslToRgb((id * 0.38196601125) % 1, 0.58, 0.62),
|
|||
founded: this.year,
|
||||
legitimacy: this.rng.range(0.68, 0.94),
|
||||
cohesion: this.rng.range(0.58, 0.9),
|
||||
charisma: this.rng.range(0.5, 1.5),
|
||||
charisma: this.rollLeaderCharisma(),
|
||||
leaderStarted: this.year,
|
||||
leaderTenureYears: this.rng.range(24, 68),
|
||||
crisis: 0,
|
||||
|
|
@ -1905,17 +1896,19 @@ while (this.graphEvents.length > 160) this.graphEvents.shift();
|
|||
}
|
||||
selectNewLeader(polity, forced = false) {
|
||||
if (!polity) return;
|
||||
const previousCharisma = clamp(polity.charisma ?? 1, 0.5, 1.5);
|
||||
const charismaMin = SimConfig.polity?.charismaMin ?? 0.5;
|
||||
const charismaMax = SimConfig.polity?.charismaMax ?? 3.0;
|
||||
const previousCharisma = clamp(polity.charisma ?? 1, charismaMin, charismaMax);
|
||||
const center = this.getCityById(polity.centerCityId);
|
||||
const centerStability = center ? clamp(center.loyalty ?? 0.5, 0, 1) : 0.5;
|
||||
const institutionalBias = ((polity.legitimacy ?? 0.7) + (polity.cohesion ?? 0.6) + centerStability) / 3;
|
||||
const randomLeader = this.rng.range(0.5, 1.5);
|
||||
const randomLeader = this.rollLeaderCharisma();
|
||||
const continuity = forced ? 0.18 : 0.34;
|
||||
const institutionalPull = 0.82 + institutionalBias * 0.36;
|
||||
const nextCharisma = clamp(
|
||||
previousCharisma * continuity + randomLeader * (1 - continuity) * institutionalPull,
|
||||
0.5,
|
||||
1.5
|
||||
charismaMin,
|
||||
charismaMax
|
||||
);
|
||||
polity.charisma = nextCharisma;
|
||||
polity.leaderStarted = this.year;
|
||||
|
|
@ -1927,7 +1920,7 @@ if (forced) polity.crisis = clamp((polity.crisis || 0) + 0.04, 0, 1.5);
|
|||
}
|
||||
updatePolityLeaders() {
|
||||
for (const polity of this.polities) {
|
||||
polity.charisma = clamp(polity.charisma ?? 1, 0.5, 1.5);
|
||||
polity.charisma = clamp(polity.charisma ?? 1, SimConfig.polity?.charismaMin ?? 0.5, SimConfig.polity?.charismaMax ?? 3.0);
|
||||
polity.leaderStarted ??= polity.founded ?? this.year;
|
||||
polity.leaderTenureYears ??= this.rng.range(24, 68);
|
||||
const tenureYears = (this.year - polity.leaderStarted) / WEEKS_PER_YEAR;
|
||||
|
|
@ -2194,7 +2187,7 @@ tech * 24 -
|
|||
instability * 12 -
|
||||
agePressure * 6
|
||||
);
|
||||
return basePower * clamp(polity.charisma ?? 1, 0.5, 1.5);
|
||||
return basePower * clamp(polity.charisma ?? 1, SimConfig.polity?.charismaMin ?? 0.5, SimConfig.polity?.charismaMax ?? 3.0);
|
||||
}
|
||||
polityInfluenceRange(polity, wartime = false) {
|
||||
const power = this.polityPower(polity);
|
||||
|
|
@ -2679,6 +2672,34 @@ city.receivedAid = true;
|
|||
city.loyalty = clamp(city.loyalty + 0.05, 0, 1);
|
||||
}
|
||||
}
|
||||
this.restoreAgedPolityCities(polity, cities);
|
||||
}
|
||||
}
|
||||
restoreAgedPolityCities(polity, cities) {
|
||||
const cfg = SimConfig.city;
|
||||
if (!polity || !cities?.length || (polity.treasury || 0) <= 0) return;
|
||||
const minWear = cfg.agingRestoreMinWear ?? 0.08;
|
||||
let budget = Math.min(polity.treasury, (polity.treasury || 0) * (cfg.agingRestoreBudgetShare ?? 0.18));
|
||||
if (budget <= 0) return;
|
||||
const costPerWear = Math.max(1, cfg.agingRestoreCostPerWear ?? 70);
|
||||
const candidates = [...cities]
|
||||
.filter(city => (city.ageWear || 0) > minWear && city.population > 0)
|
||||
.sort((a, b) => {
|
||||
const aPressure = Math.max(0, (a.population || 0) - this.cityAgePopulationLimit(a));
|
||||
const bPressure = Math.max(0, (b.population || 0) - this.cityAgePopulationLimit(b));
|
||||
return bPressure - aPressure || (b.population || 0) - (a.population || 0);
|
||||
});
|
||||
for (const city of candidates) {
|
||||
if (budget <= 0 || polity.treasury <= 0) break;
|
||||
const wear = city.ageWear || 0;
|
||||
const maxRestoreCost = Math.min(budget, polity.treasury, wear * costPerWear);
|
||||
if (maxRestoreCost <= 0) continue;
|
||||
const restoredWear = maxRestoreCost / costPerWear;
|
||||
city.ageWear = clamp(wear - restoredWear, 0, 1);
|
||||
polity.treasury -= maxRestoreCost;
|
||||
budget -= maxRestoreCost;
|
||||
city.receivedAid = true;
|
||||
city.loyalty = clamp((city.loyalty ?? 0.5) + restoredWear * 0.24, 0, 1);
|
||||
}
|
||||
}
|
||||
erodePolityLegitimacy() {
|
||||
|
|
@ -2767,8 +2788,7 @@ delta += clamp((perCapita - 0.10) * 0.07, -0.025, 0.045);
|
|||
delta += this.sameDominantEthnicity(city, center) ? 0.03 : -0.012;
|
||||
delta += clamp(0.045 - distance * 0.0011, -0.025, 0.045);
|
||||
const directCenterTrade = this.hasDirectTradeConnection(city, center);
|
||||
if (directCenterTrade) delta += 0.025;
|
||||
else delta -= this.tradeAccessLoyaltyPenalty(city, center, polity);
|
||||
if (!directCenterTrade) delta -= this.tradeAccessLoyaltyPenalty(city, center, polity);
|
||||
if (city.receivedAid) delta += 0.04;
|
||||
delta -= 0.004;
|
||||
if (city.storedResources < city.population * 0.05) delta -= 0.035;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue