not bad city system
This commit is contained in:
parent
f5e7a1df1d
commit
15c1031d72
6 changed files with 329 additions and 195 deletions
2
app.js
2
app.js
|
|
@ -8,11 +8,9 @@ const modes = [
|
||||||
["suitability", "Suitability"],
|
["suitability", "Suitability"],
|
||||||
["history", "Premodern"],
|
["history", "Premodern"],
|
||||||
["modern", "Modern"],
|
["modern", "Modern"],
|
||||||
["roads", "Roads"],
|
|
||||||
["development", "Development"],
|
["development", "Development"],
|
||||||
["landuse", "Land Use"],
|
["landuse", "Land Use"],
|
||||||
["admin", "Municipal Borders"],
|
["admin", "Municipal Borders"],
|
||||||
["terrain-debug", "Terrain Debug"],
|
|
||||||
["admin-debug", "Admin Debug"],
|
["admin-debug", "Admin Debug"],
|
||||||
["borders-debug", "Borders Debug"],
|
["borders-debug", "Borders Debug"],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
166
mapFeaturesV2.js
166
mapFeaturesV2.js
|
|
@ -108,16 +108,18 @@ export function generateMapFeatures(seed, terrain) {
|
||||||
floodplain[i] * 0.03
|
floodplain[i] * 0.03
|
||||||
);
|
);
|
||||||
valleySettlement[i] = clamp(
|
valleySettlement[i] = clamp(
|
||||||
valleyField[i] * 0.50 +
|
valleyField[i] * 0.52 +
|
||||||
river[i] * 0.16 +
|
river[i] * 0.08 +
|
||||||
confluence * 0.72 +
|
confluence * 0.38 +
|
||||||
depositional * 0.16 +
|
depositional * 0.20 +
|
||||||
basinField[i] * 0.10 +
|
basinField[i] * 0.16 +
|
||||||
lowSlope * 0.10 -
|
plain[i] * 0.08 +
|
||||||
slope[i] * 0.58 -
|
lowSlope * 0.12 -
|
||||||
ridgeField[i] * 0.34 -
|
slope[i] * 0.54 -
|
||||||
spine * 0.18 -
|
ridgeField[i] * 0.30 -
|
||||||
highPenalty * 0.72
|
spine * 0.16 -
|
||||||
|
highPenalty * 0.70 -
|
||||||
|
floodplain[i] * 0.10
|
||||||
);
|
);
|
||||||
coastalSettlement[i] = clamp(
|
coastalSettlement[i] = clamp(
|
||||||
coastalLowland[i] * 0.50 +
|
coastalLowland[i] * 0.50 +
|
||||||
|
|
@ -333,20 +335,21 @@ export function generateMapFeatures(seed, terrain) {
|
||||||
const i = indexOf(x, y);
|
const i = indexOf(x, y);
|
||||||
if (sea[i]) continue;
|
if (sea[i]) continue;
|
||||||
const featurePull = Math.max(
|
const featurePull = Math.max(
|
||||||
distanceToNearest(ports, x, y) < 8 ? 0.12 : 0,
|
distanceToNearest(ports, x, y) < 8 ? 0.10 : 0,
|
||||||
distanceToNearest(crossings, x, y) < 6 ? 0.10 : 0,
|
distanceToNearest(crossings, x, y) < 6 ? 0.06 : 0,
|
||||||
confluenceField[i] * 0.26
|
confluenceField[i] * 0.16
|
||||||
);
|
);
|
||||||
const valleyMouth = valleyField[i] > 0.22 && (plain[i] > 0.22 || basinField[i] > 0.18 || coastalLowland[i] > 0.18) ? 0.12 : 0;
|
const valleyMouth = valleyField[i] > 0.22 && (plain[i] > 0.22 || basinField[i] > 0.18 || coastalLowland[i] > 0.18) ? 0.14 : 0;
|
||||||
marketScore[i] = clamp(
|
marketScore[i] = clamp(
|
||||||
townSuitability[i] * 0.66 +
|
townSuitability[i] * 0.62 +
|
||||||
villageInfluence[i] * 0.42 +
|
villageInfluence[i] * 0.38 +
|
||||||
featurePull +
|
featurePull +
|
||||||
valleyMouth +
|
valleyMouth +
|
||||||
basinField[i] * 0.10 +
|
basinField[i] * 0.12 +
|
||||||
plain[i] * 0.09 -
|
plain[i] * 0.14 +
|
||||||
slope[i] * 0.20 -
|
coastalLowland[i] * 0.08 -
|
||||||
ridgeField[i] * 0.10
|
slope[i] * 0.18 -
|
||||||
|
ridgeField[i] * 0.08
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -446,7 +449,7 @@ export function generateMapFeatures(seed, terrain) {
|
||||||
Math.log10(capacity + 1) * 0.72 +
|
Math.log10(capacity + 1) * 0.72 +
|
||||||
townSuitability[i] * 1.40 +
|
townSuitability[i] * 1.40 +
|
||||||
developable[i] * 1.05 +
|
developable[i] * 1.05 +
|
||||||
confluenceField[i] * 0.50 +
|
confluenceField[i] * 0.22 +
|
||||||
(p.candidateKind === "port" ? 0.48 : 0) +
|
(p.candidateKind === "port" ? 0.48 : 0) +
|
||||||
(p.candidateKind === "castleTown" ? 0.22 : 0) +
|
(p.candidateKind === "castleTown" ? 0.22 : 0) +
|
||||||
hash2(p.x, p.y, seed + 12000) * 0.16;
|
hash2(p.x, p.y, seed + 12000) * 0.16;
|
||||||
|
|
@ -523,9 +526,10 @@ export function generateMapFeatures(seed, terrain) {
|
||||||
city.isRegionalCapital = isRegionalCapital;
|
city.isRegionalCapital = isRegionalCapital;
|
||||||
city.rank = isPrefecturalCapital ? "Prefectural Capital" : isRegionalCapital ? "Regional Capital" : city.population >= 200000 ? "Regional City" : "Local City";
|
city.rank = isPrefecturalCapital ? "Prefectural Capital" : isRegionalCapital ? "Regional Capital" : city.population >= 200000 ? "Regional City" : "Local City";
|
||||||
city.kind = city.rank;
|
city.kind = city.rank;
|
||||||
city.urbanRadius = clamp(4.5 + Math.sqrt(city.population) / 95, 7, isPrefecturalCapital ? 32 : isRegionalCapital ? 27 : 22);
|
city.urbanRadius = clamp(5.8 + Math.sqrt(city.population) / 72, 8, isPrefecturalCapital ? 40 : isRegionalCapital ? 32 : 24);
|
||||||
city.coreRadius = clamp(1.8 + Math.sqrt(city.population) / 380, 2.2, isPrefecturalCapital ? 8.5 : 6.5);
|
city.coreRadius = clamp(1.8 + Math.sqrt(city.population) / 380, 2.2, isPrefecturalCapital ? 8.5 : 6.5);
|
||||||
city.urbanWeight = clamp(0.85 + Math.log10(Math.max(10000, city.population)) * 0.25, 1.0, 2.2);
|
city.sprawlRadius = clamp(city.urbanRadius * (isPrefecturalCapital ? 1.65 : isRegionalCapital ? 1.45 : city.population >= 120000 ? 1.28 : 1.15), city.urbanRadius + 2, isPrefecturalCapital ? 56 : isRegionalCapital ? 42 : 30);
|
||||||
|
city.urbanWeight = clamp(0.95 + Math.log10(Math.max(10000, city.population)) * 0.29, 1.08, 2.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
function cityPopulationCap(city) {
|
function cityPopulationCap(city) {
|
||||||
|
|
@ -715,7 +719,7 @@ export function generateMapFeatures(seed, terrain) {
|
||||||
const oldTownInfluence = influenceFromPoints([...markets, ...castleTowns, ...ports], 7, (p) => p.kind === "Major Port" ? 1.2 : 0.9);
|
const oldTownInfluence = influenceFromPoints([...markets, ...castleTowns, ...ports], 7, (p) => p.kind === "Major Port" ? 1.2 : 0.9);
|
||||||
const populationDensity = new Float32Array(SIZE);
|
const populationDensity = new Float32Array(SIZE);
|
||||||
|
|
||||||
function addKernel(grid, p, radius, weight, exponent = 1.7, terrainWeighted = true) {
|
function addKernel(grid, p, radius, weight, exponent = 1.7, terrainWeighted = true, combine = "max") {
|
||||||
const r = Math.ceil(radius);
|
const r = Math.ceil(radius);
|
||||||
for (let dy = -r; dy <= r; dy++) {
|
for (let dy = -r; dy <= r; dy++) {
|
||||||
for (let dx = -r; dx <= r; dx++) {
|
for (let dx = -r; dx <= r; dx++) {
|
||||||
|
|
@ -726,16 +730,18 @@ export function generateMapFeatures(seed, terrain) {
|
||||||
if (sea[i]) continue;
|
if (sea[i]) continue;
|
||||||
const d = Math.hypot(dx, dy);
|
const d = Math.hypot(dx, dy);
|
||||||
if (d > radius) continue;
|
if (d > radius) continue;
|
||||||
const terrain = terrainWeighted ? clamp(0.20 + developable[i] * 1.05 + valleySettlement[i] * 0.18 + coastalSettlement[i] * 0.10 - slope[i] * 0.22 - ridgeField[i] * 0.14, 0, 1.28) : 1;
|
const terrain = terrainWeighted ? clamp(0.24 + developable[i] * 1.00 + valleySettlement[i] * 0.16 + coastalSettlement[i] * 0.10 - slope[i] * 0.20 - ridgeField[i] * 0.12 + roadInfluence[i] * 0.08 + railInfluence2[i] * 0.06, 0, 1.34) : 1;
|
||||||
const v = weight * Math.pow(1 - d / Math.max(1, radius), exponent) * terrain;
|
const v = weight * Math.pow(1 - d / Math.max(1, radius), exponent) * terrain;
|
||||||
if (v > grid[i]) grid[i] = v;
|
if (combine === "add") grid[i] = Math.min(3.4, grid[i] + v);
|
||||||
|
else if (v > grid[i]) grid[i] = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const city of modernCities) {
|
for (const city of modernCities) {
|
||||||
addKernel(cityInfluence, city, city.urbanRadius || 10, city.urbanWeight || 1.0, 1.25, true);
|
addKernel(cityInfluence, city, city.sprawlRadius || Math.round((city.urbanRadius || 10) * 1.4), (city.urbanWeight || 1.0) * (city.isPrefecturalCapital ? 0.46 : city.isRegionalCapital ? 0.38 : 0.30), 2.75, true, "add");
|
||||||
addKernel(coreInfluence, city, city.coreRadius || 3, (city.urbanWeight || 1.0) * 1.10, 1.65, true);
|
addKernel(cityInfluence, city, city.urbanRadius || 10, city.urbanWeight || 1.0, 1.18, true, "add");
|
||||||
|
addKernel(coreInfluence, city, city.coreRadius || 3, (city.urbanWeight || 1.0) * 1.10, 1.65, true, "max");
|
||||||
}
|
}
|
||||||
const townInfluence = influenceFromPoints(markets, 6, (m) => clamp((m.population || 10000) / 26000, 0.45, 1.25));
|
const townInfluence = influenceFromPoints(markets, 6, (m) => clamp((m.population || 10000) / 26000, 0.45, 1.25));
|
||||||
|
|
||||||
|
|
@ -774,26 +780,106 @@ export function generateMapFeatures(seed, terrain) {
|
||||||
function classifyLanduse() {
|
function classifyLanduse() {
|
||||||
landuse.fill(LANDUSE.RURAL);
|
landuse.fill(LANDUSE.RURAL);
|
||||||
let maxDensity = 0;
|
let maxDensity = 0;
|
||||||
|
const baseNoiseSeed = seed + 15000;
|
||||||
|
const urbanCapacity = new Float32Array(SIZE);
|
||||||
for (let y = 0; y < MAP_H; y++) {
|
for (let y = 0; y < MAP_H; y++) {
|
||||||
for (let x = 0; x < MAP_W; x++) {
|
for (let x = 0; x < MAP_W; x++) {
|
||||||
const i = indexOf(x, y);
|
const i = indexOf(x, y);
|
||||||
if (sea[i]) continue;
|
if (sea[i]) continue;
|
||||||
const urban = cityInfluence[i] * 0.78 + stationInfluence[i] * 0.22 + roadInfluence[i] * 0.08;
|
const transport = Math.max(roadInfluence[i] * 0.95, railInfluence2[i] * 0.95, stationInfluence[i] * 0.82);
|
||||||
|
const urban = cityInfluence[i] * 0.76 + stationInfluence[i] * 0.26 + roadInfluence[i] * 0.12 + railInfluence2[i] * 0.10;
|
||||||
const core = coreInfluence[i];
|
const core = coreInfluence[i];
|
||||||
const oldTown = oldTownInfluence[i] * 0.72 + townInfluence[i] * 0.42;
|
const oldTown = oldTownInfluence[i] * 0.70 + townInfluence[i] * 0.38;
|
||||||
const rural = villageInfluence[i] * 0.25 + ruralSuitability[i] * 0.30;
|
const rural = villageInfluence[i] * 0.24 + ruralSuitability[i] * 0.30;
|
||||||
populationDensity[i] = clamp(urban * 0.74 + core * 0.32 + oldTown * 0.28 + townInfluence[i] * 0.18 + villageInfluence[i] * 0.16 + roadInfluence[i] * 0.05);
|
const riverUrban = clamp(river[i] * 0.12 + valleyField[i] * 0.10 + plain[i] * 0.08 + basinField[i] * 0.08 - floodplain[i] * 0.10);
|
||||||
|
urbanCapacity[i] = clamp(
|
||||||
|
developable[i] * 0.66 +
|
||||||
|
plain[i] * 0.16 +
|
||||||
|
basinField[i] * 0.16 +
|
||||||
|
valleyField[i] * 0.16 +
|
||||||
|
coastalLowland[i] * 0.12 +
|
||||||
|
transport * 0.18 +
|
||||||
|
riverUrban * 0.14 -
|
||||||
|
slope[i] * 0.18 -
|
||||||
|
ridgeField[i] * 0.12 -
|
||||||
|
floodplain[i] * 0.08
|
||||||
|
);
|
||||||
|
populationDensity[i] = clamp(urban * 0.66 + core * 0.46 + oldTown * 0.28 + townInfluence[i] * 0.16 + villageInfluence[i] * 0.14 + transport * 0.12);
|
||||||
maxDensity = Math.max(maxDensity, populationDensity[i]);
|
maxDensity = Math.max(maxDensity, populationDensity[i]);
|
||||||
if (elevation[i] > 0.67 || (slope[i] > 0.56 && ridgeField[i] > 0.30) || ridgeField[i] > 0.70) landuse[i] = LANDUSE.FOREST;
|
|
||||||
else if (industrialInfluence[i] > 0.22 && developable[i] > 0.08) landuse[i] = LANDUSE.INDUSTRIAL;
|
if (elevation[i] > 0.67 || (slope[i] > 0.56 && ridgeField[i] > 0.30) || ridgeField[i] > 0.70) {
|
||||||
else if (core > 0.38 && developable[i] > 0.07) landuse[i] = LANDUSE.CBD;
|
landuse[i] = LANDUSE.FOREST;
|
||||||
else if (oldTown > 0.24 && developable[i] > 0.06) landuse[i] = LANDUSE.OLD_URBAN;
|
continue;
|
||||||
else if (urban > 0.22 && developable[i] > 0.08) landuse[i] = LANDUSE.SUBURB;
|
}
|
||||||
else if (roadInfluence[i] > 0.22 && developable[i] > 0.16 && townInfluence[i] > 0.07) landuse[i] = LANDUSE.ROADSIDE;
|
if (industrialInfluence[i] > 0.22 && urbanCapacity[i] > 0.10) {
|
||||||
else if (agriculture[i] > 0.22 || rural > 0.22 || (developable[i] > 0.18 && plain[i] > 0.16)) landuse[i] = LANDUSE.FARMLAND;
|
landuse[i] = LANDUSE.INDUSTRIAL;
|
||||||
else landuse[i] = elevation[i] > 0.52 || slope[i] > 0.34 ? LANDUSE.FOREST : LANDUSE.RURAL;
|
continue;
|
||||||
|
}
|
||||||
|
if (core > 0.38 && urbanCapacity[i] > 0.10) {
|
||||||
|
landuse[i] = LANDUSE.CBD;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (oldTown > 0.18 && urbanCapacity[i] > 0.09) {
|
||||||
|
landuse[i] = LANDUSE.OLD_URBAN;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const suburbanity = urban * 0.88 + transport * 0.23 + stationInfluence[i] * 0.12 + townInfluence[i] * 0.08 + riverUrban * 0.08;
|
||||||
|
const edgeTaper = clamp(cityInfluence[i] * 0.52 + stationInfluence[i] * 0.16 + roadInfluence[i] * 0.10 + 0.28);
|
||||||
|
const sprawlBias = clamp(0.58 + hash2(x, y, baseNoiseSeed) * 0.42);
|
||||||
|
const sprawlScore = suburbanity * sprawlBias * edgeTaper - core * 0.12;
|
||||||
|
if (sprawlScore > 0.24 && urbanCapacity[i] > 0.10) {
|
||||||
|
landuse[i] = LANDUSE.SUBURB;
|
||||||
|
} else if (transport > 0.18 && urbanCapacity[i] > 0.10 && (townInfluence[i] > 0.04 || cityInfluence[i] > 0.09)) {
|
||||||
|
landuse[i] = transport > 0.28 && stationInfluence[i] > 0.10 ? LANDUSE.SUBURB : LANDUSE.ROADSIDE;
|
||||||
|
} else if (agriculture[i] > 0.24 || rural > 0.22 || (developable[i] > 0.18 && plain[i] > 0.18)) {
|
||||||
|
landuse[i] = LANDUSE.FARMLAND;
|
||||||
|
} else {
|
||||||
|
landuse[i] = elevation[i] > 0.52 || slope[i] > 0.34 ? LANDUSE.FOREST : LANDUSE.RURAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseLanduse = landuse.slice();
|
||||||
|
const isBuilt = (lu) => lu >= LANDUSE.OLD_URBAN && lu <= LANDUSE.ROADSIDE;
|
||||||
|
for (let y = 1; y < MAP_H - 1; y++) {
|
||||||
|
for (let x = 1; x < MAP_W - 1; x++) {
|
||||||
|
const i = indexOf(x, y);
|
||||||
|
if (sea[i] || baseLanduse[i] === LANDUSE.FOREST) continue;
|
||||||
|
const transport = Math.max(roadInfluence[i] * 0.95, railInfluence2[i] * 0.95, stationInfluence[i] * 0.82);
|
||||||
|
let urbanNeighbors = 0;
|
||||||
|
let cbdNeighbors = 0;
|
||||||
|
for (let dy = -1; dy <= 1; dy++) {
|
||||||
|
for (let dx = -1; dx <= 1; dx++) {
|
||||||
|
if (!dx && !dy) continue;
|
||||||
|
const lu = baseLanduse[indexOf(x + dx, y + dy)];
|
||||||
|
if (isBuilt(lu)) urbanNeighbors++;
|
||||||
|
if (lu === LANDUSE.CBD) cbdNeighbors++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (baseLanduse[i] === LANDUSE.OLD_URBAN && coreInfluence[i] > 0.31 && cbdNeighbors >= 3) {
|
||||||
|
landuse[i] = LANDUSE.CBD;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((baseLanduse[i] === LANDUSE.FARMLAND || baseLanduse[i] === LANDUSE.RURAL) && urbanCapacity[i] > 0.10) {
|
||||||
|
const fringeChance = urbanNeighbors * 0.055 + cityInfluence[i] * 0.13 + transport * 0.12 + stationInfluence[i] * 0.08;
|
||||||
|
const noise = 0.23 + hash2(x, y, seed + 15050) * 0.24;
|
||||||
|
if (fringeChance > 0.34 + noise) {
|
||||||
|
landuse[i] = urbanNeighbors >= 4 || transport > 0.28 ? LANDUSE.SUBURB : LANDUSE.ROADSIDE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((river[i] > 0.10 || railInfluence2[i] > 0.16 || roadInfluence[i] > 0.22) && urbanNeighbors >= 3 && landuse[i] <= LANDUSE.FARMLAND && urbanCapacity[i] > 0.09) {
|
||||||
|
landuse[i] = cbdNeighbors >= 1 || coreInfluence[i] > 0.15 ? LANDUSE.OLD_URBAN : LANDUSE.SUBURB;
|
||||||
|
}
|
||||||
|
if (landuse[i] === LANDUSE.SUBURB && urbanNeighbors <= 1) {
|
||||||
|
const keep = clamp(cityInfluence[i] * 0.52 + transport * 0.32 + stationInfluence[i] * 0.18 + 0.08);
|
||||||
|
if (hash2(x, y, seed + 15051) > keep) {
|
||||||
|
landuse[i] = agriculture[i] > 0.22 ? LANDUSE.FARMLAND : LANDUSE.RURAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (maxDensity > 0) for (let i = 0; i < SIZE; i++) populationDensity[i] = clamp(populationDensity[i] / maxDensity);
|
if (maxDensity > 0) for (let i = 0; i < SIZE; i++) populationDensity[i] = clamp(populationDensity[i] / maxDensity);
|
||||||
}
|
}
|
||||||
classifyLanduse();
|
classifyLanduse();
|
||||||
|
|
|
||||||
102
mapOutput.js
102
mapOutput.js
|
|
@ -27,12 +27,13 @@ function municipalityNameFromRoot(root, center, fields, seed, ordinal = 0) {
|
||||||
export function finishMapOutput({
|
export function finishMapOutput({
|
||||||
seed,
|
seed,
|
||||||
options,
|
options,
|
||||||
|
terrain,
|
||||||
|
features,
|
||||||
|
admin,
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
terrainTemplate,
|
terrainTemplate,
|
||||||
seaLevel,
|
seaLevel,
|
||||||
cityPopulationCap,
|
|
||||||
stationInfluence,
|
|
||||||
roadInfluence,
|
|
||||||
railInfluence2,
|
|
||||||
elevation,
|
elevation,
|
||||||
moisture,
|
moisture,
|
||||||
slope,
|
slope,
|
||||||
|
|
@ -43,7 +44,6 @@ export function finishMapOutput({
|
||||||
floodplain,
|
floodplain,
|
||||||
plain,
|
plain,
|
||||||
agriculture,
|
agriculture,
|
||||||
settlementCluster,
|
|
||||||
ridgeField,
|
ridgeField,
|
||||||
valleyField,
|
valleyField,
|
||||||
visibleRavineField,
|
visibleRavineField,
|
||||||
|
|
@ -59,23 +59,41 @@ export function finishMapOutput({
|
||||||
alluvialFanField,
|
alluvialFanField,
|
||||||
deltaField,
|
deltaField,
|
||||||
naturalBarrierScore,
|
naturalBarrierScore,
|
||||||
villages,
|
riverPaths,
|
||||||
ports,
|
mainRivers,
|
||||||
crossings,
|
tributaryRivers,
|
||||||
passes,
|
smallStreams,
|
||||||
markets,
|
prefectureMask,
|
||||||
castles,
|
prefectureBorder,
|
||||||
castleTowns,
|
prefectureRegionId,
|
||||||
|
regionalDebug,
|
||||||
|
terrainDebug,
|
||||||
|
regionalPrefectureBorders,
|
||||||
|
} = terrain;
|
||||||
|
|
||||||
|
const {
|
||||||
|
cityPopulationCap,
|
||||||
|
stationInfluence,
|
||||||
|
roadInfluence,
|
||||||
|
railInfluence2,
|
||||||
|
settlementCluster,
|
||||||
|
villages: inputVillages,
|
||||||
|
ports: inputPorts,
|
||||||
|
crossings: inputCrossings,
|
||||||
|
passes: inputPasses,
|
||||||
|
markets: inputMarkets,
|
||||||
|
castles: inputCastles,
|
||||||
|
castleTowns: inputCastleTowns,
|
||||||
premodernRoads,
|
premodernRoads,
|
||||||
minorRoads,
|
minorRoads,
|
||||||
modernCities,
|
modernCities: inputModernCities,
|
||||||
populationDensity,
|
populationDensity,
|
||||||
railways,
|
railways,
|
||||||
branchRailways,
|
branchRailways,
|
||||||
ringRailways,
|
ringRailways,
|
||||||
externalRailways,
|
externalRailways,
|
||||||
stations,
|
stations: inputStations,
|
||||||
industrialZones,
|
industrialZones: inputIndustrialZones,
|
||||||
nationalRoads,
|
nationalRoads,
|
||||||
ringRoads,
|
ringRoads,
|
||||||
expressways,
|
expressways,
|
||||||
|
|
@ -83,28 +101,38 @@ export function finishMapOutput({
|
||||||
icAccessRoads,
|
icAccessRoads,
|
||||||
externalRoads,
|
externalRoads,
|
||||||
externalExpressways,
|
externalExpressways,
|
||||||
interchanges,
|
interchanges: inputInterchanges,
|
||||||
logisticsParks,
|
logisticsParks: inputLogisticsParks,
|
||||||
satelliteCities,
|
satelliteCities: inputSatelliteCities,
|
||||||
newTowns,
|
newTowns: inputNewTowns,
|
||||||
landuse,
|
landuse,
|
||||||
|
externalGateways: inputExternalGateways,
|
||||||
|
transportDebug,
|
||||||
|
} = features;
|
||||||
|
|
||||||
|
const {
|
||||||
adminCentersRaw,
|
adminCentersRaw,
|
||||||
adminId,
|
adminId,
|
||||||
adminBorders,
|
adminBorders,
|
||||||
adminDebug,
|
adminDebug,
|
||||||
riverPaths,
|
} = admin;
|
||||||
mainRivers,
|
|
||||||
tributaryRivers,
|
let villages = inputVillages;
|
||||||
smallStreams,
|
let ports = inputPorts;
|
||||||
externalGateways,
|
let crossings = inputCrossings;
|
||||||
transportDebug,
|
let passes = inputPasses;
|
||||||
prefectureMask,
|
let markets = inputMarkets;
|
||||||
prefectureBorder,
|
let castles = inputCastles;
|
||||||
prefectureRegionId,
|
let castleTowns = inputCastleTowns;
|
||||||
regionalDebug,
|
let modernCities = inputModernCities;
|
||||||
terrainDebug,
|
let stations = inputStations;
|
||||||
regionalPrefectureBorders,
|
let industrialZones = inputIndustrialZones;
|
||||||
}) {
|
let interchanges = inputInterchanges;
|
||||||
|
let logisticsParks = inputLogisticsParks;
|
||||||
|
let satelliteCities = inputSatelliteCities;
|
||||||
|
let newTowns = inputNewTowns;
|
||||||
|
let externalGateways = inputExternalGateways;
|
||||||
|
|
||||||
const outputProgress = (step) => options?.onProgress?.({ status: "output-step", key: "output", label: `Output: ${step}`, step });
|
const outputProgress = (step) => options?.onProgress?.({ status: "output-step", key: "output", label: `Output: ${step}`, step });
|
||||||
outputProgress("population recalculation");
|
outputProgress("population recalculation");
|
||||||
// Final population pass after land-use cleanup, satellite municipality locking, and isolated urban deletion.
|
// Final population pass after land-use cleanup, satellite municipality locking, and isolated urban deletion.
|
||||||
|
|
@ -147,14 +175,8 @@ export function finishMapOutput({
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bridge and tunnel icon systems were removed from the visual model.
|
|
||||||
// Arrays remain empty for backward-compatible tests and downstream code.
|
|
||||||
const bridges = [];
|
|
||||||
const tunnels = [];
|
|
||||||
const harborWorks = makeHarborWorks(ports);
|
const harborWorks = makeHarborWorks(ports);
|
||||||
const abandonedRailways = branchRailways.filter((_, i) => i % 3 === 0);
|
|
||||||
let castleRuins = castles.filter((_, i) => i % 2 === 1).map((c) => ({ ...c, kind: "Castle Ruins" }));
|
let castleRuins = castles.filter((_, i) => i % 2 === 1).map((c) => ({ ...c, kind: "Castle Ruins" }));
|
||||||
const preservedOldRoads = premodernRoads.filter((_, i) => i % 2 === 0);
|
|
||||||
const nameFields = { elevation, slope, sea, river, plain, agriculture, ridgeField, valleyField, basinField, coastalLowland, flowAccum, landuse, populationDensity };
|
const nameFields = { elevation, slope, sea, river, plain, agriculture, ridgeField, valleyField, basinField, coastalLowland, flowAccum, landuse, populationDensity };
|
||||||
const usedNames = new Set();
|
const usedNames = new Set();
|
||||||
const nameDebug = createNameDebug();
|
const nameDebug = createNameDebug();
|
||||||
|
|
@ -318,17 +340,13 @@ export function finishMapOutput({
|
||||||
logisticsParks,
|
logisticsParks,
|
||||||
satelliteCities,
|
satelliteCities,
|
||||||
newTowns,
|
newTowns,
|
||||||
bridges,
|
|
||||||
tunnels,
|
|
||||||
harborWorks,
|
harborWorks,
|
||||||
landuse,
|
landuse,
|
||||||
adminCenters,
|
adminCenters,
|
||||||
adminId,
|
adminId,
|
||||||
adminBorders,
|
adminBorders,
|
||||||
adminDebug,
|
adminDebug,
|
||||||
abandonedRailways,
|
|
||||||
castleRuins,
|
castleRuins,
|
||||||
preservedOldRoads,
|
|
||||||
riverPaths,
|
riverPaths,
|
||||||
mainRivers,
|
mainRivers,
|
||||||
tributaryRivers,
|
tributaryRivers,
|
||||||
|
|
|
||||||
|
|
@ -30,57 +30,44 @@ export function generateMap(seedInput = 114514, options = {}) {
|
||||||
|
|
||||||
const terrain = stage("terrain", "Terrain, rivers, and prefecture regions", () => generateTerrainAndRivers(seed));
|
const terrain = stage("terrain", "Terrain, rivers, and prefecture regions", () => generateTerrainAndRivers(seed));
|
||||||
const {
|
const {
|
||||||
terrainTemplate,
|
|
||||||
seaLevel,
|
|
||||||
elevation,
|
elevation,
|
||||||
moisture,
|
|
||||||
slope,
|
slope,
|
||||||
sea,
|
sea,
|
||||||
ocean,
|
|
||||||
lake,
|
|
||||||
river,
|
river,
|
||||||
floodplain,
|
|
||||||
plain,
|
plain,
|
||||||
agriculture,
|
agriculture,
|
||||||
ridgeField,
|
ridgeField,
|
||||||
valleyField,
|
valleyField,
|
||||||
visibleRavineField,
|
|
||||||
surfaceTextureField,
|
|
||||||
basinField,
|
basinField,
|
||||||
coastalLowland,
|
coastalLowland,
|
||||||
flowAccum,
|
flowAccum,
|
||||||
erosionField,
|
|
||||||
depositionField,
|
|
||||||
arcSpineField,
|
|
||||||
branchRidgeField,
|
|
||||||
depositionalLowland,
|
|
||||||
alluvialFanField,
|
|
||||||
deltaField,
|
|
||||||
naturalBarrierScore,
|
naturalBarrierScore,
|
||||||
portSuitability,
|
|
||||||
crossingSuitability,
|
|
||||||
passSuitability,
|
|
||||||
prefectureMask,
|
prefectureMask,
|
||||||
prefectureBorder,
|
|
||||||
prefectureRegionId,
|
prefectureRegionId,
|
||||||
adminPrefectureRegionId,
|
adminPrefectureRegionId,
|
||||||
regionalDebug,
|
|
||||||
terrainDebug,
|
|
||||||
regionalPrefectureBorders,
|
|
||||||
riverPaths,
|
|
||||||
mainRivers,
|
|
||||||
tributaryRivers,
|
|
||||||
smallStreams,
|
|
||||||
} = terrain;
|
} = terrain;
|
||||||
|
|
||||||
const features = stage("human", "Human geography and transport", () => generateMapFeatures(seed, terrain));
|
const features = stage("human", "Human geography and transport", () => generateMapFeatures(seed, terrain));
|
||||||
const {
|
const {
|
||||||
ports, crossings, passes, settlementCluster, settlementScore, villages, markets, castles, premodernRoads, minorRoads, castleTowns, modernCities, populationDensity,
|
settlementScore,
|
||||||
railways, branchRailways, ringRailways, externalRailways, stations, industrialZones, nationalRoads, ringRoads, expressways, ringExpressways, icAccessRoads, externalRoads, externalExpressways,
|
villages,
|
||||||
interchanges, logisticsParks, satelliteCities, newTowns, landuse, stationInfluence, roadInfluence, railInfluence2, villageInfluence, externalGateways, cityPopulationCap, transportDebug,
|
markets,
|
||||||
|
modernCities,
|
||||||
|
populationDensity,
|
||||||
|
stations,
|
||||||
|
industrialZones,
|
||||||
|
logisticsParks,
|
||||||
|
satelliteCities,
|
||||||
|
newTowns,
|
||||||
|
ports,
|
||||||
|
landuse,
|
||||||
|
stationInfluence,
|
||||||
|
roadInfluence,
|
||||||
|
railInfluence2,
|
||||||
|
villageInfluence,
|
||||||
} = features;
|
} = features;
|
||||||
|
|
||||||
const { adminCentersRaw, adminId, adminBorders, adminDebug } = stage("admin", "Municipal and prefectural administration", () => generateAdminLayout({
|
const admin = stage("admin", "Municipal and prefectural administration", () => generateAdminLayout({
|
||||||
seed, prefectureMask, prefectureRegionId: adminPrefectureRegionId || prefectureRegionId, sea, elevation, slope, river, ridgeField, naturalBarrierScore, valleyField, basinField, coastalLowland, flowAccum, plain, agriculture,
|
seed, prefectureMask, prefectureRegionId: adminPrefectureRegionId || prefectureRegionId, sea, elevation, slope, river, ridgeField, naturalBarrierScore, valleyField, basinField, coastalLowland, flowAccum, plain, agriculture,
|
||||||
settlementScore, populationDensity, stationInfluence, roadInfluence, railInfluence2, villageInfluence, landuse, modernCities, satelliteCities, newTowns, markets, villages, ports, stations, industrialZones, logisticsParks,
|
settlementScore, populationDensity, stationInfluence, roadInfluence, railInfluence2, villageInfluence, landuse, modernCities, satelliteCities, newTowns, markets, villages, ports, stations, industrialZones, logisticsParks,
|
||||||
adminProgress: (event) => options?.onProgress?.({
|
adminProgress: (event) => options?.onProgress?.({
|
||||||
|
|
@ -96,14 +83,11 @@ export function generateMap(seedInput = 114514, options = {}) {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const output = stage("output", "Names, population totals, and final packaging", () => finishMapOutput({
|
const output = stage("output", "Names, population totals, and final packaging", () => finishMapOutput({
|
||||||
seed, options, terrainTemplate, seaLevel, cityPopulationCap, stationInfluence, roadInfluence, railInfluence2,
|
seed,
|
||||||
elevation, moisture, slope, sea, ocean, lake, river, floodplain, plain, agriculture, settlementCluster, ridgeField, valleyField, visibleRavineField, surfaceTextureField, basinField, coastalLowland, flowAccum, erosionField, depositionField,
|
options,
|
||||||
arcSpineField, branchRidgeField, depositionalLowland, alluvialFanField, deltaField, naturalBarrierScore,
|
terrain,
|
||||||
villages, ports, crossings, passes, markets, castles, castleTowns, premodernRoads, minorRoads, modernCities, populationDensity,
|
features,
|
||||||
railways, branchRailways, ringRailways, externalRailways, stations, industrialZones, nationalRoads, ringRoads, expressways, ringExpressways, icAccessRoads, externalRoads, externalExpressways,
|
admin,
|
||||||
interchanges, logisticsParks, satelliteCities, newTowns, landuse, adminCentersRaw, adminId, adminBorders, adminDebug,
|
|
||||||
riverPaths, mainRivers, tributaryRivers, smallStreams, externalGateways, prefectureMask, prefectureBorder, prefectureRegionId, regionalPrefectureBorders,
|
|
||||||
regionalDebug, terrainDebug, transportDebug,
|
|
||||||
}));
|
}));
|
||||||
output.generationTimings = generationTimings;
|
output.generationTimings = generationTimings;
|
||||||
output.generationTotalMs = generationTimings.reduce((sum, row) => sum + row.ms, 0);
|
output.generationTotalMs = generationTimings.reduce((sum, row) => sum + row.ms, 0);
|
||||||
|
|
|
||||||
102
renderer.js
102
renderer.js
|
|
@ -407,16 +407,16 @@ function discreteColor(map, x, y, mode) {
|
||||||
color = [160, 205, 239];
|
color = [160, 205, 239];
|
||||||
} else if (mode === "landuse") {
|
} else if (mode === "landuse") {
|
||||||
const colors = {
|
const colors = {
|
||||||
0: [242, 248, 238], // rural / natural land
|
0: [244, 247, 240], // rural / natural land
|
||||||
1: [238, 246, 222], // farmland
|
1: [222, 236, 188], // farmland
|
||||||
2: [240, 238, 232], // old urban
|
2: [232, 222, 214], // old urban
|
||||||
3: [245, 230, 220], // CBD / DID core
|
3: [221, 188, 184], // CBD / DID core
|
||||||
4: [250, 248, 245], // suburb
|
4: [235, 225, 236], // suburb
|
||||||
5: [235, 235, 240], // industrial
|
5: [223, 224, 232], // industrial
|
||||||
6: [240, 245, 240], // logistics
|
6: [232, 237, 232], // logistics
|
||||||
7: [245, 248, 252], // new town
|
7: [231, 236, 246], // new town
|
||||||
8: [250, 248, 240], // roadside
|
8: [243, 233, 210], // roadside
|
||||||
9: [225, 238, 220], // forest / mountain land
|
9: [221, 236, 216], // forest / mountain land
|
||||||
};
|
};
|
||||||
color = colors[map.landuse[i]] || colors[0];
|
color = colors[map.landuse[i]] || colors[0];
|
||||||
} else if (mode === "admin") {
|
} else if (mode === "admin") {
|
||||||
|
|
@ -573,24 +573,25 @@ function drawSegments(ctx, segments, color, width, dashed = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawUrbanAreas(ctx, map, mode) {
|
function drawUrbanAreas(ctx, map, mode) {
|
||||||
const visibleModes = ["all", "modern", "development", "landuse", "roads", "admin"];
|
const visibleModes = ["all", "modern", "development", "landuse", "admin"];
|
||||||
if (!visibleModes.includes(mode)) return;
|
if (!visibleModes.includes(mode)) return;
|
||||||
|
|
||||||
const colors = {
|
const colors = {
|
||||||
2: "rgba(225, 222, 215, 0.6)",
|
2: "rgba(223, 214, 206, 0.72)",
|
||||||
3: "rgba(240, 220, 205, 0.85)",
|
3: "rgba(215, 175, 172, 0.88)",
|
||||||
4: "rgba(242, 240, 235, 0.5)",
|
4: "rgba(231, 219, 231, 0.68)",
|
||||||
5: "rgba(220, 220, 225, 0.6)",
|
5: "rgba(218, 218, 226, 0.64)",
|
||||||
6: "rgba(225, 230, 225, 0.5)",
|
6: "rgba(225, 230, 225, 0.56)",
|
||||||
7: "rgba(235, 240, 245, 0.6)",
|
7: "rgba(229, 234, 242, 0.64)",
|
||||||
8: "rgba(245, 242, 235, 0.5)",
|
8: "rgba(244, 230, 205, 0.62)",
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
for (let y = 0; y < MAP_H; y++) {
|
for (let y = 0; y < MAP_H; y++) {
|
||||||
for (let x = 0; x < MAP_W; x++) {
|
for (let x = 0; x < MAP_W; x++) {
|
||||||
const i = indexOf(x, y);
|
const i = indexOf(x, y);
|
||||||
if (!map.prefectureMask[i]) continue;
|
const areaMask = map.humanRegionMask || map.prefectureMask;
|
||||||
|
if (areaMask && !areaMask[i]) continue;
|
||||||
const lu = map.landuse[i];
|
const lu = map.landuse[i];
|
||||||
if (!colors[lu]) continue;
|
if (!colors[lu]) continue;
|
||||||
|
|
||||||
|
|
@ -678,6 +679,42 @@ function drawLabels(ctx, points, limit = Infinity) {
|
||||||
for (const p of prioritized.slice(0, limit)) labelWithCollision(ctx, p, occupied);
|
for (const p of prioritized.slice(0, limit)) labelWithCollision(ctx, p, occupied);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawScaleBar(ctx) {
|
||||||
|
const kmPerCell = 2;
|
||||||
|
const targetKm = 20;
|
||||||
|
const lengthCells = Math.max(8, Math.round(targetKm / kmPerCell));
|
||||||
|
const lengthPx = lengthCells * CELL_SIZE;
|
||||||
|
const margin = 14;
|
||||||
|
const x = margin;
|
||||||
|
const y = margin + 18;
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.lineCap = "butt";
|
||||||
|
ctx.strokeStyle = "rgba(0,0,0,0.78)";
|
||||||
|
ctx.lineWidth = 2.2;
|
||||||
|
ctx.fillStyle = "rgba(255,255,255,0.92)";
|
||||||
|
ctx.fillRect(x - 8, y - 18, lengthPx + 16, 30);
|
||||||
|
ctx.strokeStyle = "rgba(80,80,80,0.22)";
|
||||||
|
ctx.strokeRect(x - 8, y - 18, lengthPx + 16, 30);
|
||||||
|
ctx.strokeStyle = "rgba(30,30,30,0.82)";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
ctx.lineTo(x + lengthPx, y);
|
||||||
|
ctx.stroke();
|
||||||
|
for (let t = 0; t <= 2; t++) {
|
||||||
|
const tx = x + (lengthPx * t) / 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(tx, y - 5);
|
||||||
|
ctx.lineTo(tx, y + 5);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
ctx.fillStyle = "rgba(20,20,20,0.88)";
|
||||||
|
ctx.font = "600 11px ui-sans-serif, system-ui, -apple-system, sans-serif";
|
||||||
|
ctx.textAlign = "center";
|
||||||
|
ctx.fillText(`${targetKm} km`, x + lengthPx / 2, y - 7);
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
export function drawMap(canvas, map, options) {
|
export function drawMap(canvas, map, options) {
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
|
|
@ -749,9 +786,9 @@ export function drawMap(canvas, map, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const showHistory = ["history", "all", "terrain"].includes(mode);
|
const showHistory = ["history", "all", "terrain"].includes(mode);
|
||||||
const showModern = ["modern", "all", "development", "landuse", "roads", "admin-debug", "borders-debug"].includes(mode);
|
const showModern = ["modern", "all", "development", "landuse", "admin-debug", "borders-debug"].includes(mode);
|
||||||
const showRoads = ["roads", "all", "development"].includes(mode);
|
const showRoads = ["all", "development"].includes(mode);
|
||||||
const showMinorRoads = ["roads", "all", "modern", "development"].includes(mode);
|
const showMinorRoads = ["all", "modern", "development"].includes(mode);
|
||||||
const showAdmin = ["admin", "all", "admin-debug", "borders-debug"].includes(mode);
|
const showAdmin = ["admin", "all", "admin-debug", "borders-debug"].includes(mode);
|
||||||
|
|
||||||
// 3. Borders
|
// 3. Borders
|
||||||
|
|
@ -829,11 +866,15 @@ export function drawMap(canvas, map, options) {
|
||||||
|
|
||||||
if (showModern) {
|
if (showModern) {
|
||||||
for (const p of map.stations) dot(ctx, p, 2.5, "#fff", "#444");
|
for (const p of map.stations) dot(ctx, p, 2.5, "#fff", "#444");
|
||||||
|
const allLayerTowns = mode === "all"
|
||||||
|
? (map.markets || []).filter((p) => (p.population || 0) >= 25000 && !(map.modernCities || []).some((c) => c.x === p.x && c.y === p.y))
|
||||||
|
: [];
|
||||||
|
for (const p of allLayerTowns) dot(ctx, p, 3.1, "rgba(244, 164, 118, 0.92)", "rgba(255,255,255,0.88)");
|
||||||
for (const p of map.modernCities) {
|
for (const p of map.modernCities) {
|
||||||
const popRadius = p.population ? Math.min(8.5, 3.5 + Math.sqrt(p.population) / 400) : 4.5;
|
const popRadius = p.population ? Math.min(9.4, 3.8 + Math.sqrt(p.population) / 360) : 4.8;
|
||||||
dot(ctx, p, popRadius, "rgba(240, 110, 110, 0.95)", "rgba(255,255,255,0.9)");
|
dot(ctx, p, popRadius, "rgba(240, 110, 110, 0.95)", "rgba(255,255,255,0.9)");
|
||||||
if (p.isPrefecturalCapital) dot(ctx, p, popRadius + 3.0, "transparent", "rgba(200,80,80,0.9)");
|
if (p.isPrefecturalCapital) dot(ctx, p, popRadius + 3.4, "transparent", "rgba(200,80,80,0.9)");
|
||||||
else if (p.isRegionalCapital) dot(ctx, p, popRadius + 2.2, "transparent", "rgba(190,95,95,0.62)");
|
else if (p.isRegionalCapital) dot(ctx, p, popRadius + 2.4, "transparent", "rgba(190,95,95,0.62)");
|
||||||
}
|
}
|
||||||
for (const p of map.interchanges || []) dot(ctx, p, 2.7, "rgba(250, 250, 245, 0.95)", "rgba(95, 125, 95, 0.95)");
|
for (const p of map.interchanges || []) dot(ctx, p, 2.7, "rgba(250, 250, 245, 0.95)", "rgba(95, 125, 95, 0.95)");
|
||||||
if (mode === "admin-debug" || mode === "borders-debug") {
|
if (mode === "admin-debug" || mode === "borders-debug") {
|
||||||
|
|
@ -845,17 +886,24 @@ export function drawMap(canvas, map, options) {
|
||||||
if (showLabels) {
|
if (showLabels) {
|
||||||
if (mode === "admin") {
|
if (mode === "admin") {
|
||||||
drawLabels(ctx, map.adminCenters || [], Infinity);
|
drawLabels(ctx, map.adminCenters || [], Infinity);
|
||||||
|
drawScaleBar(ctx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mode === "admin-debug" || mode === "borders-debug") {
|
if (mode === "admin-debug" || mode === "borders-debug") {
|
||||||
drawLabels(ctx, map.adminCenters || [], Infinity);
|
drawLabels(ctx, map.adminCenters || [], Infinity);
|
||||||
|
drawScaleBar(ctx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const allLayerTowns = mode === "all"
|
||||||
|
? (map.markets || []).filter((p) => (p.population || 0) >= 25000 && !(map.modernCities || []).some((c) => c.x === p.x && c.y === p.y)).map((p) => ({ ...p, labelPriorityBase: 120 }))
|
||||||
|
: [];
|
||||||
const important = [
|
const important = [
|
||||||
...map.modernCities,
|
...map.modernCities,
|
||||||
...map.ports,
|
...map.ports,
|
||||||
...(map.satelliteCities || []),
|
...(map.satelliteCities || []),
|
||||||
].filter((p) => p.insidePrefecture || p.isRegionalCapital || p.kind === "External Gateway");
|
...allLayerTowns,
|
||||||
drawLabels(ctx, important, 60);
|
].filter((p) => p.insidePrefecture || p.isRegionalCapital || p.kind === "External Gateway" || (p.population || 0) >= 25000);
|
||||||
|
drawLabels(ctx, important, mode === "all" ? 85 : 60);
|
||||||
}
|
}
|
||||||
|
drawScaleBar(ctx);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
test.js
2
test.js
|
|
@ -531,7 +531,7 @@ try {
|
||||||
assert(map.terrainDebug.depositionLowlandArea > 0, "depositional lowland area is tracked");
|
assert(map.terrainDebug.depositionLowlandArea > 0, "depositional lowland area is tracked");
|
||||||
assert(["east-west", "north-south", "diagonal"].includes(map.terrainTemplate.coastAxis) && map.terrainTemplate.coastSides?.length === 2, "paired coast template parameters are exposed");
|
assert(["east-west", "north-south", "diagonal"].includes(map.terrainTemplate.coastAxis) && map.terrainTemplate.coastSides?.length === 2, "paired coast template parameters are exposed");
|
||||||
assert(map.settlementCluster.length === size, "settlement cluster field matches map size");
|
assert(map.settlementCluster.length === size, "settlement cluster field matches map size");
|
||||||
assert(Array.isArray(map.bridges) && Array.isArray(map.tunnels) && Array.isArray(map.harborWorks), "legacy bridge/tunnel arrays and harbor arrays exist");
|
assert(Array.isArray(map.harborWorks), "harbor arrays exist");
|
||||||
assert(map.prefectureRegionId.length === size && Array.isArray(map.regionalPrefectureBorders), "neighbor prefecture regions exist");
|
assert(map.prefectureRegionId.length === size && Array.isArray(map.regionalPrefectureBorders), "neighbor prefecture regions exist");
|
||||||
assert(map.regionalDebug && Number.isFinite(map.regionalDebug.regionalChangedAfterNaturalPartition), "regional changed-cell debug exists");
|
assert(map.regionalDebug && Number.isFinite(map.regionalDebug.regionalChangedAfterNaturalPartition), "regional changed-cell debug exists");
|
||||||
assert(map.regionalDebug.regionalChangedAfterNaturalPartition > 0, "regional natural partition changes region cells");
|
assert(map.regionalDebug.regionalChangedAfterNaturalPartition > 0, "regional natural partition changes region cells");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue