city tweaks

This commit is contained in:
33333-33333 2026-05-24 22:52:22 +09:00
commit d762a88b22
12 changed files with 255 additions and 195 deletions

View file

@ -1,6 +1,7 @@
import { createNameDebug } from "./names.js";
import { CELL_SIZE, INF, MAP_H, MAP_W, clamp, indexOf, inside, rand } from "./mapUtils.js";
import { CELL_SIZE, INF, MAP_H, MAP_W, clamp, hash2, indexOf, inside, rand } from "./mapUtils.js";
import { applyOutputOptions, attachIdsAndNames, recalculatePopulationAfterLanduse, tagInsidePrefecture } from "./mapGeneratorHelpers.js";
import { LANDUSE } from "./landuseCodes.js";
const MUNICIPAL_SUFFIX_RE = /[市町村区]$/u;
@ -24,6 +25,48 @@ function municipalityNameFromRoot(root, center, fields, seed, ordinal = 0) {
return `${value}${municipalitySuffixForCenter(center, fields, seed, ordinal)}`;
}
function ensureMunicipalOfficeSettlementFootprints(adminCentersRaw, { sea, elevation, slope, ridgeField, plain, basinField, coastalLowland, populationDensity, landuse, humanRegionMask, roadInfluence, stationInfluence }, seed) {
let changedCells = 0;
const isBuildable = (i) => !sea[i] && (!humanRegionMask || humanRegionMask[i]) && elevation[i] < 0.78 && slope[i] < 0.62 && ridgeField[i] < 0.76;
for (const [n, center] of (adminCentersRaw || []).entries()) {
if (!center || !inside(center.x, center.y)) continue;
const ci = indexOf(center.x, center.y);
if (!isBuildable(ci)) continue;
const existingUrban = landuse[ci] >= LANDUSE.OLD_URBAN && landuse[ci] <= LANDUSE.NEW_TOWN;
const baseRadius = existingUrban ? 1.2 : (center.population || 0) >= 60000 ? 2.6 : 2.0;
const scoreBoost = clamp((populationDensity[ci] || 0) * 0.45 + (roadInfluence?.[ci] || 0) * 0.22 + (stationInfluence?.[ci] || 0) * 0.26 + 0.24);
const r = Math.ceil(baseRadius);
for (let dy = -r; dy <= r; dy++) {
for (let dx = -r; dx <= r; dx++) {
const x = center.x + dx;
const y = center.y + dy;
if (!inside(x, y)) continue;
const i = indexOf(x, y);
if (!isBuildable(i)) continue;
const d = Math.hypot(dx, dy);
if (d > baseRadius) continue;
const lowland = clamp(plain[i] * 0.28 + basinField[i] * 0.24 + coastalLowland[i] * 0.20 + (roadInfluence?.[i] || 0) * 0.14 - slope[i] * 0.28 - ridgeField[i] * 0.16 + 0.30);
if (lowland <= 0.12) continue;
if (d <= 0.85) {
if (landuse[i] < LANDUSE.OLD_URBAN || landuse[i] === LANDUSE.FARMLAND || landuse[i] === LANDUSE.RURAL) {
landuse[i] = LANDUSE.OLD_URBAN;
changedCells++;
}
populationDensity[i] = Math.max(populationDensity[i] || 0, 0.34 + scoreBoost * 0.35);
} else if (d <= baseRadius && landuse[i] <= LANDUSE.FARMLAND) {
const keep = lowland * (1 - d / (baseRadius + 0.1)) + hash2(x, y, seed + 18800 + n) * 0.08;
if (keep > 0.16) {
landuse[i] = LANDUSE.SUBURB;
changedCells++;
populationDensity[i] = Math.max(populationDensity[i] || 0, 0.20 + scoreBoost * 0.20);
}
}
}
}
}
return changedCells;
}
export function finishMapOutput({
seed,
options,
@ -141,9 +184,11 @@ export function finishMapOutput({
for (let i = 0; i < humanRegionMask.length; i++) {
humanRegionMask[i] = !sea[i] && ((prefectureRegionId?.[i] ?? -1) >= 0 || prefectureMask[i]) ? 1 : 0;
}
const municipalOfficeUrbanizedCells = ensureMunicipalOfficeSettlementFootprints(adminCentersRaw, {
sea, elevation, slope, ridgeField, plain, basinField, coastalLowland, populationDensity, landuse, humanRegionMask, roadInfluence, stationInfluence,
}, seed);
recalculatePopulationAfterLanduse(modernCities, satelliteCities, populationDensity, landuse, humanRegionMask, sea, stationInfluence, roadInfluence, railInfluence2);
for (const city of modernCities) {
if (city.isPrefecturalCapital) continue;
const cap = cityPopulationCap(city);
if (cap < INF && (city.population || 0) > cap) {
city.population = Math.round(cap / 1000) * 1000;
@ -153,29 +198,6 @@ export function finishMapOutput({
}
}
outputProgress("harbor works");
function makeHarborWorks(ports) {
const out = [];
for (const port of ports) {
const parts = [];
const limit = port.portClass === "major" ? 5 : port.portClass === "regional" ? 3 : 1;
for (const [dx, dy] of [[1,0],[-1,0],[0,1],[0,-1],[1,1],[-1,1],[1,-1],[-1,-1]]) {
const sx = port.x + dx;
const sy = port.y + dy;
if (!inside(sx, sy) || !sea[indexOf(sx, sy)]) continue;
parts.push([[port.x, port.y], [sx, sy]]);
const wx = sx + dx;
const wy = sy + dy;
if (port.portClass === "major" && inside(wx, wy) && sea[indexOf(wx, wy)] && rand(seed, sx * 101 + sy * 103) > 0.22) parts.push([[sx, sy], [wx, wy]]);
if (parts.length >= limit) break;
}
if (parts.length) out.push({ port, segments: parts, kind: port.portClass === "major" ? "Major Harbor Works" : "Harbor Works" });
}
return out;
}
const harborWorks = makeHarborWorks(ports);
let castleRuins = castles.filter((_, i) => i % 2 === 1).map((c) => ({ ...c, kind: "Castle Ruins" }));
const nameFields = { elevation, slope, sea, river, plain, agriculture, ridgeField, valleyField, basinField, coastalLowland, flowAccum, landuse, populationDensity };
const usedNames = new Set();
@ -340,12 +362,11 @@ export function finishMapOutput({
logisticsParks,
satelliteCities,
newTowns,
harborWorks,
landuse,
adminCenters,
adminId,
adminBorders,
adminDebug,
adminDebug: adminDebug ? { ...adminDebug, municipalOfficeUrbanizedCells } : { municipalOfficeUrbanizedCells },
castleRuins,
riverPaths,
mainRivers,