From 8ff52b3fbd46e7e79606ce257c9d1bff9dd93bab Mon Sep 17 00:00:00 2001 From: 33333-33333 Date: Tue, 12 May 2026 22:19:44 +0900 Subject: [PATCH] :) --- script.js | 106 ++++++++++++++++++++++++++++++++--------------------- styles.css | 6 ++- 2 files changed, 69 insertions(+), 43 deletions(-) diff --git a/script.js b/script.js index cd3d9f2..4a14ff6 100644 --- a/script.js +++ b/script.js @@ -668,8 +668,8 @@ class Simulation { a.farmingWork = 0; a.lastFarmTile = to; const depositScale = w.terrain[to] === Terrain.WATER ? 0.35 : 1; - w.pheromone[from] += (w.tradeRoute[from] ? 0.08 : 0.22) * depositScale; - w.pheromone[to] += (w.tradeRoute[to] ? 0.06 : 0.15) * depositScale; + w.pheromone[from] += (w.tradeRoute[from] ? 0.14 : 0.42) * depositScale; + w.pheromone[to] += (w.tradeRoute[to] ? 0.10 : 0.30) * depositScale; a.settled = Math.max(0, a.settled - 1); } else { a.movedThisStep = false; @@ -720,6 +720,7 @@ class Simulation { if (farming <= 0.02) return; if (agent.settled > 0) { agent.farmingWork = clamp((agent.farmingWork || 0) + farming * 0.015, 0, 1); + agent.tech.farming = clamp(agent.tech.farming + farming * this.farmabilityAt(tile) * 0.00018, 0, 1); } } @@ -729,7 +730,7 @@ class Simulation { const settledFactor = clamp((agent.settled || 0) / 12, 0, 1); if ((agent.tech?.farming || 0) <= 0.02) { - const farmingBase = 0.000090; + const farmingBase = 0.00016; const farmability = this.farmabilityAt(tile); const sedentary = getSedentary(agent.traits); const farmingChance = @@ -737,11 +738,11 @@ class Simulation { (0.25 + farmability) * (0.4 + sedentary) * (0.3 + settledFactor); - if (this.rng.next() < farmingChance) this.grantTechnologyAround(agent, "farming", 0.24); + if (this.rng.next() < farmingChance) this.grantTechnologyAround(agent, "farming", 0.32); } if ((agent.tech?.metallurgy || 0) <= 0.02) { - const metallurgyBase = 0.000055; + const metallurgyBase = 0.00011; const mineral = clamp(w.mineral[tile], 0, 1); const mineralTerrainBonus = w.terrain[tile] === Terrain.MINERAL || w.terrain[tile] === Terrain.MOUNTAIN @@ -752,7 +753,7 @@ class Simulation { (0.2 + mineral) * mineralTerrainBonus * (0.5 + settledFactor); - if (this.rng.next() < metallurgyChance) this.grantTechnologyAround(agent, "metallurgy", 0.20); + if (this.rng.next() < metallurgyChance) this.grantTechnologyAround(agent, "metallurgy", 0.28); } } @@ -773,7 +774,7 @@ class Simulation { this.ensureAgentTech(agent); const farming = agent.tech.farming || 0; const metallurgy = agent.tech.metallurgy || 0; - const cost = farming * 0.010 + metallurgy * 0.022; + const cost = farming * 0.004 + metallurgy * 0.010; if (cost <= 0) return; if (agent.resources >= cost) { @@ -783,7 +784,7 @@ class Simulation { const shortage = cost - Math.max(0, agent.resources); agent.resources = Math.max(0, agent.resources - cost); - const decay = clamp(0.015 + shortage * 0.04, 0.015, 0.12); + const decay = clamp(0.006 + shortage * 0.025, 0.006, 0.06); agent.tech.farming = Math.max(0, farming - decay); agent.tech.metallurgy = Math.max(0, metallurgy - decay * 1.15); if (agent.tech.farming < 0.02) agent.tech.farming = 0; @@ -810,7 +811,7 @@ class Simulation { ethnocentrism * 0.0012; if (sameEthnicity) chance += 0.004; - if (w.tradeRoute[tile]) chance += 0.003; + if (w.tradeRoute[tile]) chance += 0.006; if (w.city[tile] >= 0) chance += 0.003; chance = clamp(chance, 0.0008, 0.028); @@ -850,14 +851,14 @@ class Simulation { } } - if (farmingCount >= 3) { + if (farmingCount >= 2) { const avgFarming = farmingSum / farmingCount; - agent.tech.farming = clamp(agent.tech.farming + 0.0012 * farmingCount * avgFarming, 0, 1); + agent.tech.farming = clamp(agent.tech.farming + 0.0024 * farmingCount * avgFarming, 0, 1); } - if (metallurgyCount >= 3) { + if (metallurgyCount >= 2) { const avgMetallurgy = metallurgySum / metallurgyCount; - agent.tech.metallurgy = clamp(agent.tech.metallurgy + 0.00085 * metallurgyCount * avgMetallurgy, 0, 1); + agent.tech.metallurgy = clamp(agent.tech.metallurgy + 0.0018 * metallurgyCount * avgMetallurgy, 0, 1); } } @@ -885,6 +886,9 @@ class Simulation { metallurgyGatherMultiplier(agent, tile) { const metallurgyLevel = agent.tech?.metallurgy || 0; + if (metallurgyLevel > 0.02) { + agent.tech.metallurgy = clamp(agent.tech.metallurgy + metallurgyLevel * clamp(this.world.mineral[tile], 0, 1) * 0.00012, 0, 1); + } return 1 + metallurgyLevel * clamp(this.world.mineral[tile], 0, 1); } @@ -929,7 +933,10 @@ class Simulation { const childResources = a.resources * childShare; a.resources -= childResources; const childTraits = mutateTraits(a.traits, this.rng, 0.035); - offspring.push(this.makeAgent(a.x, a.y, a.ethnicity, childTraits, childResources)); + const child = this.makeAgent(a.x, a.y, a.ethnicity, childTraits, childResources); + child.tech.farming = clamp((a.tech?.farming || 0) * this.rng.range(0.75, 0.95), 0, 1); + child.tech.metallurgy = clamp((a.tech?.metallurgy || 0) * this.rng.range(0.70, 0.92), 0, 1); + offspring.push(child); } } @@ -1029,7 +1036,7 @@ class Simulation { const w = this.world; for (let i = 0; i < w.count; i++) { w.resource[i] = Math.min(58, w.resource[i] + w.regen[i] * (1 + w.farmland[i] * 1.15)); - w.pheromone[i] *= 0.992; + w.pheromone[i] *= 0.996; w.pressure[i] *= 0.88; } } @@ -1061,20 +1068,20 @@ class Simulation { } let foundedThisTick = 0; - const canFoundCities = this.year >= years(180) && this.year % years(30) === 0; - const foundingLimit = canFoundCities ? 1 + Math.floor(this.year / years(1200)) : 0; + const canFoundCities = this.year >= years(80) && this.year % years(10) === 0; + const foundingLimit = canFoundCities ? 1 + Math.floor(this.year / years(600)) : 0; const candidateEntries = [...candidates].sort((a, b) => { const sedentaryA = a[1].sedentary / Math.max(1, a[1].count); const sedentaryB = b[1].sedentary / Math.max(1, b[1].count); return (b[1].count * (0.7 + sedentaryB) + b[1].resources * 0.02) - (a[1].count * (0.7 + sedentaryA) + a[1].resources * 0.02); }); for (const [i, group] of candidateEntries) { - if (group.count < 6) continue; + if (group.count < 3) continue; const avgSedentary = group.sedentary / group.count; let city = this.cities.find(c => Math.abs(c.x - (i % w.size)) + Math.abs(c.y - Math.floor(i / w.size)) < 7); - if (!city && canFoundCities && foundedThisTick < foundingLimit && this.cities.length < 50) { - const foundingChance = clamp((avgSedentary - 0.28) * 1.55 * 3, 0, 0.98); - if (avgSedentary < 0.34 || this.rng.next() > foundingChance) continue; + if (!city && canFoundCities && foundedThisTick < foundingLimit && this.cities.length < 80) { + const foundingChance = clamp((avgSedentary - 0.18) * 2.4 * 3, 0.04, 0.98); + if (avgSedentary < 0.22 || this.rng.next() > foundingChance) continue; city = this.createCity(i % w.size, Math.floor(i / w.size), group); this.cities.push(city); foundedThisTick++; @@ -1097,9 +1104,14 @@ class Simulation { this.cities = this.cities.filter(c => { c.age++; - c.strength *= 0.996; - if (c.population < 10) c.strength -= 0.02; - if (c.population <= 0 || c.strength <= 0.12) return false; + c.strength *= 0.992; + const foodPerCapita = c.storedResources / Math.max(1, c.population); + if (c.age > 20) { + if (c.activeVisitors < 2 && foodPerCapita < 0.04) c.strength -= 0.035; + else if (c.activeVisitors < 5 && foodPerCapita < 0.02) c.strength -= 0.015; + if (c.population < 25 && foodPerCapita < 0.05) c.strength -= 0.018; + } + if (c.population <= 0 || c.strength <= 0.06) return false; const radius = c.agriculturalRadius; for (let dy = -radius; dy <= radius; dy++) { for (let dx = -radius; dx <= radius; dx++) { @@ -1121,7 +1133,7 @@ class Simulation { } createCity(x, y, seedGroup = null) { - const seedPopulation = seedGroup ? Math.max(8, seedGroup.count * 3) : 8; + const seedPopulation = seedGroup ? Math.max(14, seedGroup.count * 5) : 10; const seedSedentary = seedGroup ? seedGroup.sedentary / Math.max(1, seedGroup.count) : 0.5; const composition = new Map(); if (seedGroup) { @@ -1136,14 +1148,14 @@ class Simulation { x, y, population: seedPopulation, - storedResources: 24 + (seedGroup?.resources || 0) * 0.4, + storedResources: 34 + (seedGroup?.resources || 0) * 0.55, ethnicityComposition: composition, pheromoneOutput: 0, agriculturalRadius: 2, tradeLinks: new Set(), activeVisitors: 0, age: 0, - strength: 2, + strength: 3, sedentaryCulture: seedSedentary, polityId: null, loyalty: 0.5, @@ -1202,13 +1214,14 @@ class Simulation { const extraction = Math.min(w.resource[i], (0.07 + w.fertility[i] * 0.18 + w.mineral[i] * 0.045) * pull); w.resource[i] -= extraction; w.farmland[i] = Math.max(w.farmland[i], pull); - w.pheromone[i] += city.pheromoneOutput * pull * 0.04; + w.pheromone[i] += city.pheromoneOutput * pull * 0.09; harvested += extraction; } } city.storedResources += harvested; - const upkeep = city.population * 0.010; + const supportRatio = city.activeVisitors / Math.max(1, city.population); + const upkeep = city.population * (0.010 + Math.max(0, 0.025 - supportRatio) * 0.09); city.storedResources -= upkeep; if (city.storedResources > city.population * 0.16 && city.population > 0) { const prosperity = clamp(city.storedResources / Math.max(1, city.population) - 0.12, 0, 0.8); @@ -1217,6 +1230,12 @@ class Simulation { city.storedResources -= births * 0.55; addBirthsToComposition(city.ethnicityComposition, births); } + 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; + removeFromComposition(city.ethnicityComposition, attrition); + city.strength -= city.activeVisitors === 0 ? 0.030 : 0.012; + } if (city.storedResources < 0) { const deficit = Math.abs(city.storedResources); const dominant = dominantComposition(city.ethnicityComposition); @@ -1797,7 +1816,7 @@ class Simulation { reinforcePolityTradeRoutes() { const w = this.world; for (const polity of this.polities) { - if (this.rng.next() > 0.32) continue; + if (this.rng.next() > 0.48) continue; const center = this.getCityById(polity.centerCityId); if (!center) continue; const candidates = this.getPolityCities(polity) @@ -1844,24 +1863,24 @@ class Simulation { const c1 = this.cities[a]; const c2 = this.cities[b]; const d = Math.abs(c1.x - c2.x) + Math.abs(c1.y - c2.y); - if (d > 34) continue; + if (d > 44) continue; const path = this.findTerrainRoute(c1.x, c1.y, c2.x, c2.y); if (!this.isValidRoutePath(path, c2.x, c2.y)) continue; const strength = this.routeStrengthForPath(path); - if (strength < 0.12) continue; + if (strength < 0.08) continue; if (!this.canPayRouteConstructionCost(c1, c2, path)) continue; candidates.push({ c1, c2, strength, path }); } } candidates.sort((a, b) => b.strength - a.strength); - const maxLinks = Math.max(1, Math.floor(this.cities.length / 3)); + const maxLinks = Math.max(1, Math.floor(this.cities.length / 2)); const supportedRoutes = new Set(); for (const candidate of candidates) { if (this.tradeLinks.length >= maxLinks) break; const { c1, c2, strength, path } = candidate; - const c1Limit = c1.population > 90 ? 2 : 1; - const c2Limit = c2.population > 90 ? 2 : 1; + const c1Limit = c1.population > 90 ? 3 : 2; + const c2Limit = c2.population > 90 ? 3 : 2; if (c1.tradeLinks.size >= c1Limit || c2.tradeLinks.size >= c2Limit) continue; if (!this.payRouteConstructionCost(c1, c2, path)) continue; c1.tradeLinks.add(c2.id); @@ -1870,12 +1889,12 @@ class Simulation { this.exchangeCityResources(c1, c2, strength, path); for (const tile of path) { supportedRoutes.add(tile); - w.tradeRoute[tile] = Math.min(255, w.tradeRoute[tile] + 12); + w.tradeRoute[tile] = Math.min(255, w.tradeRoute[tile] + 18); } } for (let i = 0; i < w.count; i++) { - if (w.tradeRoute[i] && !supportedRoutes.has(i)) w.tradeRoute[i] = Math.max(0, w.tradeRoute[i] - 10); + if (w.tradeRoute[i] && !supportedRoutes.has(i)) w.tradeRoute[i] = Math.max(0, w.tradeRoute[i] - 4); } } @@ -2475,10 +2494,15 @@ function renderTooltip() { ${ethnicity ? `Lineage pop${ethnicity.population}` : ""} `; els.tooltip.hidden = false; - const left = Math.min(hoverState.left, hoverState.width - 280); - const top = Math.min(hoverState.top, hoverState.height - 230); - els.tooltip.style.left = `${Math.max(8, left)}px`; - els.tooltip.style.top = `${Math.max(8, top)}px`; + const margin = 8; + const width = els.tooltip.offsetWidth; + const height = els.tooltip.offsetHeight; + let left = hoverState.left; + let top = hoverState.top; + if (left + width + margin > hoverState.width) left = hoverState.left - width - 32; + if (top + height + margin > hoverState.height) top = hoverState.height - height - margin; + els.tooltip.style.left = `${clamp(left, margin, Math.max(margin, hoverState.width - width - margin))}px`; + els.tooltip.style.top = `${clamp(top, margin, Math.max(margin, hoverState.height - height - margin))}px`; } function hideTooltip() { diff --git a/styles.css b/styles.css index 48ae1e8..72e281f 100644 --- a/styles.css +++ b/styles.css @@ -284,7 +284,9 @@ canvas#world { position: absolute; z-index: 3; min-width: 180px; - max-width: 260px; + max-width: min(300px, calc(100% - 16px)); + max-height: calc(100% - 16px); + overflow: auto; padding: 8px 10px; border: 1px solid #435057; border-radius: 6px; @@ -292,7 +294,7 @@ canvas#world { color: var(--text); font-size: 11px; line-height: 1.35; - pointer-events: none; + pointer-events: auto; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28); }