This commit is contained in:
33333-33333 2026-05-26 00:45:01 +09:00
commit 5c82bfcab7
13 changed files with 1028 additions and 8801 deletions

View file

@ -1,7 +1,6 @@
import { createNameDebug } from "./names.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";
import { CELL_SIZE, INF, MAP_H, MAP_W, clamp, indexOf, inside, rand } from "./mapUtils.js";
import { applyOutputOptions, attachIdsAndNames, tagInsidePrefecture } from "./mapGeneratorHelpers.js";
const MUNICIPAL_SUFFIX_RE = /[市町村区]$/u;
@ -25,48 +24,6 @@ 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,
@ -177,17 +134,17 @@ export function finishMapOutput({
let externalGateways = inputExternalGateways;
const outputProgress = (step) => options?.onProgress?.({ status: "output-step", key: "output", label: `Output: ${step}`, step });
outputProgress("population recalculation");
// Final population pass after land-use cleanup, satellite municipality locking, and isolated urban deletion.
// Use all generated prefecture regions for human-geography density, not only the focused prefecture.
outputProgress("final packaging");
// Use all generated prefecture regions for human-geography masks, not only
// the focused prefecture. Population density itself is already generated in
// the human stage and is not rebuilt here.
const humanRegionMask = new Uint8Array(MAP_W * MAP_H);
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);
// Population density is generated directly in the human stage. Do not rebuild
// it here from land-use or municipal offices; output should only package and
// name features.
for (const city of modernCities) {
const cap = cityPopulationCap(city);
if (cap < INF && (city.population || 0) > cap) {
@ -366,7 +323,7 @@ export function finishMapOutput({
adminCenters,
adminId,
adminBorders,
adminDebug: adminDebug ? { ...adminDebug, municipalOfficeUrbanizedCells } : { municipalOfficeUrbanizedCells },
adminDebug,
castleRuins,
riverPaths,
mainRivers,