tweak
This commit is contained in:
parent
6c10d5d70e
commit
3fe9c31453
12 changed files with 963 additions and 127 deletions
|
|
@ -1,11 +1,11 @@
|
|||
import {
|
||||
applyLandscapeUnitAdminPartition,
|
||||
generateAdminRegions,
|
||||
assignAdminRegionsFromNaturalCompartments,
|
||||
lockSmallUrbanComponentsToMunicipality,
|
||||
mergeTinyMunicipalities,
|
||||
removeMunicipalExclaves,
|
||||
smoothAdminRegionsTerrainAware,
|
||||
splitOversizedRuralMunicipalities,
|
||||
splitOversizedLowlandMunicipalities,
|
||||
snapAdminBoundariesToTerrain,
|
||||
} from "./adminRegions.js";
|
||||
import { INF, MAP_H, MAP_W, SIZE, MinHeap, clamp, hash2, indexOf, inside, pickEntities, rand, xyOf } from "./mapUtils.js";
|
||||
|
|
@ -26,6 +26,34 @@ function municipalityAreaById(adminId, prefectureMask, sea) {
|
|||
return area;
|
||||
}
|
||||
|
||||
function computeTargetMunicipalityCount({ prefectureMask, sea, slope, ridgeField, coastalLowland, basinField, modernCities, markets, ports, satelliteCities, villages }) {
|
||||
let landCells = 0;
|
||||
let habitableCells = 0;
|
||||
let coastlineComplexity = 0;
|
||||
let mountainCells = 0;
|
||||
for (let y = 1; y < MAP_H - 1; y++) {
|
||||
for (let x = 1; x < MAP_W - 1; x++) {
|
||||
const i = indexOf(x, y);
|
||||
if (!prefectureMask[i] || sea[i]) continue;
|
||||
landCells++;
|
||||
if (slope[i] < 0.42 && ridgeField[i] < 0.55) habitableCells++;
|
||||
if (ridgeField[i] > 0.52 || slope[i] > 0.48) mountainCells++;
|
||||
for (const [nx, ny] of [[x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]]) {
|
||||
const ni = indexOf(nx, ny);
|
||||
if (sea[ni]) {
|
||||
coastlineComplexity += 1 + coastalLowland[i] * 0.8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const independentSatellites = (satelliteCities || []).filter((p) => p.municipalityClass === "independentSatelliteMunicipality").length;
|
||||
const settlementNodes = modernCities.length * 1.25 + markets.length * 0.9 + ports.length * 0.7 + independentSatellites * 0.8 + villages.length * 0.35;
|
||||
const basinBonus = Math.min(8, [...basinField].filter((v, i) => prefectureMask[i] && !sea[i] && v > 0.34).length / 520);
|
||||
const mountainRatio = landCells ? mountainCells / landCells : 0;
|
||||
return clamp(Math.round(habitableCells / 260 + settlementNodes * 0.45 + coastlineComplexity * 0.04 + basinBonus + mountainRatio * 4), 18, 48);
|
||||
}
|
||||
|
||||
function estimateUrbanComponentArea(city, prefectureMask, sea, landuse, populationDensity) {
|
||||
if (!city || !inside(city.x, city.y)) return 0;
|
||||
const start = indexOf(city.x, city.y);
|
||||
|
|
@ -193,6 +221,7 @@ export function generateAdminLayout({
|
|||
slope,
|
||||
river,
|
||||
ridgeField,
|
||||
naturalBarrierScore,
|
||||
valleyField,
|
||||
basinField,
|
||||
coastalLowland,
|
||||
|
|
@ -216,7 +245,9 @@ export function generateAdminLayout({
|
|||
industrialZones,
|
||||
logisticsParks,
|
||||
}) {
|
||||
const prefectureArea = prefectureMask.reduce((sum, v) => sum + (v ? 1 : 0), 0);
|
||||
const boundaryRidgeField = naturalBarrierScore
|
||||
? Float32Array.from(ridgeField, (value, i) => clamp(value * 0.72 + naturalBarrierScore[i] * 0.46))
|
||||
: ridgeField;
|
||||
const municipalityCandidates = [];
|
||||
for (let y = 2; y < MAP_H - 2; y++) {
|
||||
for (let x = 2; x < MAP_W - 2; x++) {
|
||||
|
|
@ -235,7 +266,8 @@ export function generateAdminLayout({
|
|||
const nearSmallUrban = modernCities.some((city) => (city.population || 0) < 260000 && Math.hypot(city.x - p.x, city.y - p.y) < 8 && p.x !== city.x && p.y !== city.y);
|
||||
return !nearMajor && !nearSmallUrban;
|
||||
});
|
||||
const satelliteClassificationDebug = classifySatelliteMunicipalities(satelliteCities, modernCities, prefectureMask, sea, landuse, populationDensity, roadInfluence, railInfluence2, ridgeField, river, flowAccum);
|
||||
const satelliteClassificationDebug = classifySatelliteMunicipalities(satelliteCities, modernCities, prefectureMask, sea, landuse, populationDensity, roadInfluence, railInfluence2, boundaryRidgeField, river, flowAccum);
|
||||
const targetMunicipalityCount = computeTargetMunicipalityCount({ prefectureMask, sea, slope, ridgeField: boundaryRidgeField, coastalLowland, basinField, modernCities, markets, ports, satelliteCities, villages });
|
||||
const satelliteMunicipalSeeds = (satelliteCities || [])
|
||||
.filter((city) => prefectureMask[indexOf(city.x, city.y)] && city.municipalityClass === "independentSatelliteMunicipality")
|
||||
.map((city) => ({ x: city.x, y: city.y, score: 1.05 + (city.population || 40000) / 260000, protectedSatellite: city }));
|
||||
|
|
@ -243,24 +275,27 @@ export function generateAdminLayout({
|
|||
...majorMunicipalSeeds,
|
||||
...satelliteMunicipalSeeds,
|
||||
...pickEntities(filteredMunicipalityCandidates.filter((p) => satelliteMunicipalSeeds.every((s) => Math.hypot(s.x - p.x, s.y - p.y) >= 6)), {
|
||||
max: Math.min(20, Math.max(10, Math.floor(prefectureArea / 950) + 6 + Math.floor(rand(seed, 1301) * 3))),
|
||||
minDistance: 9 + Math.floor(rand(seed, 1302) * 3),
|
||||
threshold: 0.40,
|
||||
max: Math.max(0, targetMunicipalityCount - majorMunicipalSeeds.length - satelliteMunicipalSeeds.length),
|
||||
minDistance: 6 + Math.floor(rand(seed, 1302) * 3),
|
||||
threshold: 0.34,
|
||||
seed: seed + 1300,
|
||||
jitter: 0.025,
|
||||
}),
|
||||
];
|
||||
if (adminCentersRaw.length < 12) {
|
||||
const fallback = [...modernCities, ...(satelliteCities || []).filter((p) => p.municipalityClass === "independentSatelliteMunicipality"), ...markets, ...newTowns, ...stations, ...villages]
|
||||
if (adminCentersRaw.length < Math.min(targetMunicipalityCount, 18)) {
|
||||
const fallback = [...modernCities, ...(satelliteCities || []).filter((p) => p.municipalityClass === "independentSatelliteMunicipality"), ...markets, ...ports, ...newTowns, ...stations, ...villages]
|
||||
.filter((p) => prefectureMask[indexOf(p.x, p.y)])
|
||||
.map((p) => ({ x: p.x, y: p.y, score: p.score || 0.5 }));
|
||||
adminCentersRaw = pickEntities(fallback, { max: 12, minDistance: 8, threshold: 0, seed: seed + 1303 });
|
||||
.map((p) => ({ x: p.x, y: p.y, score: (p.score || 0.5) + (p.population || 0) / 900000 }));
|
||||
const extraFallback = pickEntities(fallback, { max: targetMunicipalityCount, minDistance: 5, threshold: 0, seed: seed + 1303 });
|
||||
for (const p of extraFallback) if (adminCentersRaw.length < targetMunicipalityCount && adminCentersRaw.every((q) => Math.hypot(p.x - q.x, p.y - q.y) >= 4)) adminCentersRaw.push(p);
|
||||
}
|
||||
if (adminCentersRaw.length < 10) {
|
||||
const extra = pickEntities(municipalityCandidates, { max: 10 - adminCentersRaw.length, minDistance: 8, threshold: 0.32, seed: seed + 1304 });
|
||||
adminCentersRaw.push(...extra.filter((p) => adminCentersRaw.every((q) => Math.hypot(p.x - q.x, p.y - q.y) >= 6)));
|
||||
if (adminCentersRaw.length < targetMunicipalityCount) {
|
||||
const extra = pickEntities(municipalityCandidates, { max: targetMunicipalityCount - adminCentersRaw.length, minDistance: 5, threshold: 0.26, seed: seed + 1304 });
|
||||
adminCentersRaw.push(...extra.filter((p) => adminCentersRaw.every((q) => Math.hypot(p.x - q.x, p.y - q.y) >= 4)));
|
||||
}
|
||||
const adminId = generateAdminRegions(adminCentersRaw, prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, populationDensity, landuse);
|
||||
if (adminCentersRaw.length > targetMunicipalityCount) adminCentersRaw = adminCentersRaw.slice(0, targetMunicipalityCount);
|
||||
const compartmentAssignment = assignAdminRegionsFromNaturalCompartments(prefectureMask, sea, elevation, slope, river, boundaryRidgeField, valleyField, basinField, coastalLowland, flowAccum, plain, agriculture, populationDensity, landuse, adminCentersRaw);
|
||||
const adminId = compartmentAssignment.adminId;
|
||||
let previousSnapshot = new Int16Array(adminId);
|
||||
const adminDebug = {
|
||||
changedAfterSmooth: 0,
|
||||
|
|
@ -273,6 +308,10 @@ export function generateAdminLayout({
|
|||
changedAfterOversizedRuralSplit: 0,
|
||||
changedAfterFinalExclaveRemoval: 0,
|
||||
changedAfterFinalMerge: 0,
|
||||
targetMunicipalityCount,
|
||||
actualMunicipalityCount: 0,
|
||||
changedAfterCompartmentAssignment: compartmentAssignment.debug?.naturalCompartmentCount || 0,
|
||||
oversizedRuralSplits: 0,
|
||||
satelliteMunicipalitiesCreated: satelliteMunicipalSeeds.length,
|
||||
satelliteMunicipalitiesMerged: 0,
|
||||
satelliteMunicipalitiesExpanded: 0,
|
||||
|
|
@ -282,12 +321,14 @@ export function generateAdminLayout({
|
|||
satelliteMunicipalityAreaByNameOrIndex: {},
|
||||
independentSatelliteMunicipalities: satelliteClassificationDebug.independent,
|
||||
attachedSatelliteDistricts: satelliteClassificationDebug.attached,
|
||||
satelliteMunicipalityStats: satelliteClassificationDebug,
|
||||
...compartmentAssignment.debug,
|
||||
};
|
||||
function markChanged(field) {
|
||||
adminDebug[field] = changedCellsSince(previousSnapshot, adminId, prefectureMask, sea);
|
||||
previousSnapshot = new Int16Array(adminId);
|
||||
}
|
||||
smoothAdminRegionsTerrainAware(adminId, prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, populationDensity, landuse, 7);
|
||||
smoothAdminRegionsTerrainAware(adminId, prefectureMask, sea, elevation, slope, river, boundaryRidgeField, valleyField, populationDensity, landuse, 2);
|
||||
markChanged("changedAfterSmooth");
|
||||
|
||||
function lockUrbanClusterToMunicipality(city, radius, allowSuburban = true) {
|
||||
|
|
@ -335,7 +376,7 @@ export function generateAdminLayout({
|
|||
if (bestAdmin < 0) continue;
|
||||
sat.parentAdminHint = bestAdmin;
|
||||
const changed = expandSatelliteMunicipalityCatchment(adminId, sat, bestAdmin, {
|
||||
prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum,
|
||||
prefectureMask, sea, elevation, slope, river, ridgeField: boundaryRidgeField, valleyField, basinField, coastalLowland, flowAccum,
|
||||
landuse, populationDensity, roadInfluence, railInfluence2, stationInfluence, modernCities,
|
||||
});
|
||||
if (changed > 0 && sat.municipalityClass === "independentSatelliteMunicipality") adminDebug.satelliteMunicipalitiesExpanded++;
|
||||
|
|
@ -348,22 +389,23 @@ export function generateAdminLayout({
|
|||
markChanged("changedAfterInitialMerge");
|
||||
removeMunicipalExclaves(adminId, prefectureMask, sea, adminCentersRaw, modernCities, 180);
|
||||
markChanged("changedAfterInitialExclaveRemoval");
|
||||
applyLandscapeUnitAdminPartition(adminId, prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum, plain, agriculture, populationDensity, landuse, adminCentersRaw);
|
||||
applyLandscapeUnitAdminPartition(adminId, prefectureMask, sea, elevation, slope, river, boundaryRidgeField, valleyField, basinField, coastalLowland, flowAccum, plain, agriculture, populationDensity, landuse, adminCentersRaw);
|
||||
for (const sat of satelliteCities || []) {
|
||||
if (sat.municipalityClass !== "independentSatelliteMunicipality" || !inside(sat.x, sat.y)) continue;
|
||||
const targetAdmin = adminId[indexOf(sat.x, sat.y)];
|
||||
if (targetAdmin < 0) continue;
|
||||
expandSatelliteMunicipalityCatchment(adminId, sat, targetAdmin, {
|
||||
prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum,
|
||||
prefectureMask, sea, elevation, slope, river, ridgeField: boundaryRidgeField, valleyField, basinField, coastalLowland, flowAccum,
|
||||
landuse, populationDensity, roadInfluence, railInfluence2, stationInfluence, modernCities,
|
||||
});
|
||||
}
|
||||
markChanged("changedAfterLandscapePartition");
|
||||
const oversizedSplitDebug = splitOversizedRuralMunicipalities(adminId, prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum, plain, agriculture, populationDensity, landuse, adminCentersRaw, [...(satelliteCities || []), ...newTowns, ...markets, ...villages]);
|
||||
const oversizedSplitDebug = splitOversizedLowlandMunicipalities(adminId, prefectureMask, sea, elevation, slope, river, boundaryRidgeField, valleyField, basinField, coastalLowland, flowAccum, plain, agriculture, populationDensity, landuse, adminCentersRaw, [...(satelliteCities || []), ...newTowns, ...markets, ...villages]);
|
||||
adminDebug.changedAfterOversizedRuralSplit = oversizedSplitDebug.changedCells;
|
||||
adminDebug.oversizedRuralMunicipalitiesSplit = oversizedSplitDebug.splitMunicipalities;
|
||||
adminDebug.oversizedRuralSplits = oversizedSplitDebug.splitMunicipalities;
|
||||
previousSnapshot = new Int16Array(adminId);
|
||||
snapAdminBoundariesToTerrain(adminId, prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, flowAccum, populationDensity, landuse, adminCentersRaw, [...modernCities, ...satelliteCities, ...ports, ...industrialZones, ...logisticsParks], 5);
|
||||
snapAdminBoundariesToTerrain(adminId, prefectureMask, sea, elevation, slope, river, boundaryRidgeField, valleyField, flowAccum, populationDensity, landuse, adminCentersRaw, [...modernCities, ...satelliteCities, ...ports, ...industrialZones, ...logisticsParks], 2);
|
||||
markChanged("changedAfterSnap");
|
||||
removeMunicipalExclaves(adminId, prefectureMask, sea, adminCentersRaw, modernCities, 360);
|
||||
markChanged("changedAfterFinalExclaveRemoval");
|
||||
|
|
@ -375,7 +417,7 @@ export function generateAdminLayout({
|
|||
const targetAdmin = adminId[indexOf(sat.x, sat.y)];
|
||||
if (targetAdmin < 0) continue;
|
||||
expandSatelliteMunicipalityCatchment(adminId, sat, targetAdmin, {
|
||||
prefectureMask, sea, elevation, slope, river, ridgeField, valleyField, basinField, coastalLowland, flowAccum,
|
||||
prefectureMask, sea, elevation, slope, river, ridgeField: boundaryRidgeField, valleyField, basinField, coastalLowland, flowAccum,
|
||||
landuse, populationDensity, roadInfluence, railInfluence2, stationInfluence, modernCities,
|
||||
});
|
||||
}
|
||||
|
|
@ -401,8 +443,11 @@ export function generateAdminLayout({
|
|||
});
|
||||
adminDebug.averageSatelliteMunicipalityArea = satelliteAreas.length ? satelliteAreas.reduce((sum, value) => sum + value, 0) / satelliteAreas.length : 0;
|
||||
adminDebug.minSatelliteMunicipalityArea = satelliteAreas.length ? Math.min(...satelliteAreas) : 0;
|
||||
adminDebug.satelliteMunicipalitiesIndependent = satelliteAreas.length;
|
||||
const landscapeDebug = applyLandscapeUnitAdminPartition.lastDebug || {};
|
||||
Object.assign(adminDebug, landscapeDebug);
|
||||
adminDebug.naturalCompartmentCount = adminDebug.naturalCompartmentCount || adminDebug.compartmentCount || 0;
|
||||
adminDebug.actualMunicipalityCount = new Set([...adminId].filter((id, i) => id >= 0 && prefectureMask[i] && !sea[i])).size;
|
||||
const adminBorders = extractAdminBorderSegments(adminId, prefectureMask);
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue