import { INF, MAP_H, MAP_W, SIZE, clamp, hash2, indexOf, inside } from "./mapUtils.js"; export function buildFeatureTransportCostFields(ctx) { const { seed, sea, elevation, slope, river, plain, agriculture, ridgeField, valleyField, basinField, coastalLowland, portSuitability, passSuitability, crossingSuitability, naturalBarrierScore, settlementDemand, urbanEdge, logisticsPreSuitability, preliminaryTownInfluence, preliminaryVillageInfluence, valleySettlement, coastalSettlement, developable, } = ctx; const expressway = new Float32Array(SIZE); const rail = new Float32Array(SIZE); const national = new Float32Array(SIZE); const local = new Float32Array(SIZE); const expresswayPotential = new Float32Array(SIZE); const railPotential = new Float32Array(SIZE); const nationalPotential = new Float32Array(SIZE); const localPotential = new Float32Array(SIZE); function seaAdjacency(x, y, radius = 1) { let sum = 0; let total = 0; for (let dy = -radius; dy <= radius; dy++) { for (let dx = -radius; dx <= radius; dx++) { if (!dx && !dy) continue; const nx = x + dx; const ny = y + dy; if (!inside(nx, ny)) continue; total++; if (sea[indexOf(nx, ny)]) sum += 1; } } return total > 0 ? sum / total : 0; } function highAltitudeTransportClosed(i) { // Above this contour the generator should treat mountains as no-road // terrain. A strong mapped pass is the exception, so genuine saddle // crossings can still exist without roads drilling through entire ranges. return elevation[i] >= 0.70; } for (let y = 0; y < MAP_H; y++) { for (let x = 0; x < MAP_W; x++) { const i = indexOf(x, y); if (sea[i] || highAltitudeTransportClosed(i)) { expressway[i] = rail[i] = national[i] = local[i] = INF; expresswayPotential[i] = railPotential[i] = nationalPotential[i] = localPotential[i] = 0; continue; } const density = settlementDemand[i]; const mediumDensity = clamp(1 - Math.abs(density - 0.42) / 0.30); const highDensity = clamp((density - 0.32) / 0.50); const lowland = clamp(plain[i] * 0.48 + basinField[i] * 0.28 + valleyField[i] * 0.24 + coastalLowland[i] * 0.26 + agriculture[i] * 0.16); const pass = passSuitability?.[i] || 0; const crossing = crossingSuitability?.[i] || 0; const waterCrossingPenalty = river[i] > 0.18 ? (1 - crossing) * (0.72 + river[i] * 1.35) : 0; const seaNear = seaAdjacency(x, y, 1); const seaBroad = seaAdjacency(x, y, 3); const seaWide = seaAdjacency(x, y, 5); // Roads should use coastal lowlands when there is a settlement/port reason, // but should not casually trace beaches or hop over small bays. const coastalTraversePenalty = clamp(seaBroad * 1.72 + seaWide * 0.82 - coastalLowland[i] * 0.48 - (portSuitability?.[i] || 0) * 0.30); const highMountain = clamp((elevation[i] - 0.52) * 3.6 + slope[i] * 0.95 + ridgeField[i] * 1.05 - pass * 0.55 - valleyField[i] * 0.12); const extremeMountain = clamp((elevation[i] - 0.64) * 4.8 + slope[i] * 1.55 + ridgeField[i] * 1.45 - pass * 0.80); const denseCorePenalty = clamp((density - 0.66) / 0.28); const openPlainParallelPenalty = clamp(plain[i] * 0.48 + agriculture[i] * 0.26 - density * 0.20 - valleyField[i] * 0.20 - coastalLowland[i] * 0.12); const boundaryRidgePenalty = Math.pow(naturalBarrierScore?.[i] || 0, 2); if (extremeMountain > 0.92 && pass < 0.34) { expressway[i] = rail[i] = INF; national[i] = elevation[i] >= 0.68 ? INF : 2.9 + extremeMountain * 2.8 + waterCrossingPenalty; local[i] = elevation[i] >= 0.69 ? INF : 2.2 + extremeMountain * 2.3 + waterCrossingPenalty * 0.55; expresswayPotential[i] = 0; railPotential[i] = 0; nationalPotential[i] = clamp(lowland * 0.18 + pass * 0.24 - extremeMountain * 0.55); localPotential[i] = clamp(valleySettlement[i] * 0.14 + pass * 0.18 - extremeMountain * 0.28); continue; } expresswayPotential[i] = clamp( mediumDensity * 0.62 + urbanEdge[i] * 0.38 + logisticsPreSuitability[i] * 0.54 + lowland * 0.36 + agriculture[i] * 0.16 - denseCorePenalty * 0.54 - slope[i] * 0.82 - highMountain * 0.92 - coastalTraversePenalty * 0.32 - river[i] * 0.14 ); railPotential[i] = clamp( highDensity * 0.80 + preliminaryTownInfluence[i] * 0.22 + lowland * 0.46 + valleyField[i] * 0.22 + coastalLowland[i] * 0.22 - slope[i] * 1.28 - highMountain * 1.10 - coastalTraversePenalty * 0.26 - ridgeField[i] * 0.34 ); nationalPotential[i] = clamp( density * 0.46 + preliminaryTownInfluence[i] * 0.32 + preliminaryVillageInfluence[i] * 0.20 + agriculture[i] * 0.24 + valleyField[i] * 0.28 + coastalLowland[i] * 0.26 + pass * 0.18 + crossing * 0.18 - slope[i] * 0.48 - highMountain * 0.24 - coastalTraversePenalty * 0.16 - ridgeField[i] * 0.18 ); localPotential[i] = clamp( preliminaryVillageInfluence[i] * 0.52 + agriculture[i] * 0.38 + coastalSettlement[i] * 0.30 + valleySettlement[i] * 0.30 + developable[i] * 0.18 - slope[i] * 0.34 - coastalTraversePenalty * 0.08 - ridgeField[i] * 0.10 ); expressway[i] = Math.max(0.18, 1.62 - expresswayPotential[i] * 0.96 + denseCorePenalty * 1.30 + slope[i] * 5.4 + highMountain * 5.8 + extremeMountain * 4.2 + boundaryRidgePenalty * 4.2 + waterCrossingPenalty * 2.1 + coastalTraversePenalty * 2.65 + seaNear * 2.35 + seaWide * 1.10 + openPlainParallelPenalty * 0.12 + hash2(x, y, seed + 13301) * 0.04 ); rail[i] = Math.max(0.16, 1.48 - railPotential[i] * 1.02 + slope[i] * 7.2 + highMountain * 7.0 + extremeMountain * 4.8 + boundaryRidgePenalty * 2.4 + waterCrossingPenalty * 1.7 + coastalTraversePenalty * 1.75 + seaNear * 1.50 + seaWide * 0.70 + hash2(x, y, seed + 13302) * 0.03 ); national[i] = Math.max(0.16, 1.28 - nationalPotential[i] * 0.84 + slope[i] * 2.8 + ridgeField[i] * 1.18 + Math.max(0, elevation[i] - 0.62) * 3.0 + highMountain * 2.9 + boundaryRidgePenalty * 1.8 + waterCrossingPenalty * 1.25 - valleyField[i] * 0.18 - coastalLowland[i] * 0.08 + coastalTraversePenalty * 1.55 + seaNear * 0.84 + seaWide * 0.48 - pass * 0.42 + hash2(x, y, seed + 13303) * 0.05 ); local[i] = Math.max(0.14, 1.12 - localPotential[i] * 0.86 + slope[i] * 1.72 + ridgeField[i] * 0.82 + Math.max(0, elevation[i] - 0.68) * 1.9 + highMountain * 1.24 + boundaryRidgePenalty * 0.72 + waterCrossingPenalty * 0.65 - valleyField[i] * 0.22 - coastalLowland[i] * 0.10 + coastalTraversePenalty * 0.82 + seaNear * 0.48 + seaWide * 0.26 + hash2(x, y, seed + 13304) * 0.07 ); } } return { expressway, rail, national, local, expresswayPotential, railPotential, nationalPotential, localPotential }; }