import { CELL_SIZE, MAP_H, MAP_W, indexOf } from "./mapUtils.js"; import { generateTerrainAndRivers } from "./mapTerrain.js"; import { generateMapFeatures } from "./mapFeatures.js"; import { finishMapOutput } from "./mapOutput.js"; import { generateAdminLayout } from "./mapAdminStage.js"; export { CELL_SIZE, MAP_H, MAP_W, indexOf } from "./mapUtils.js"; function nowMs() { return typeof performance !== "undefined" && performance.now ? performance.now() : Date.now(); } function timedStage(timings, options, key, label, fn) { options?.onProgress?.({ status: "start", key, label, timings: timings.slice(), startedAt: nowMs() }); const t0 = nowMs(); const value = fn(); const ms = Math.round((nowMs() - t0) * 10) / 10; const entry = { key, label, ms }; timings.push(entry); options?.onProgress?.({ status: "done", key, label, ms, timings: timings.slice() }); return value; } function yieldToBrowser() { if (typeof requestAnimationFrame === "function") { return new Promise((resolve) => requestAnimationFrame(() => resolve())); } return new Promise((resolve) => setTimeout(resolve, 0)); } async function timedStageAsync(timings, options, key, label, fn) { options?.onProgress?.({ status: "start", key, label, timings: timings.slice(), startedAt: nowMs() }); await yieldToBrowser(); const t0 = nowMs(); const value = fn(); const ms = Math.round((nowMs() - t0) * 10) / 10; const entry = { key, label, ms }; timings.push(entry); options?.onProgress?.({ status: "done", key, label, ms, timings: timings.slice() }); await yieldToBrowser(); return value; } export function generateMap(seedInput = 114514, options = {}) { const seed = Number(seedInput) >>> 0; if (typeof options.onProgress !== "function") options = { ...options, onProgress: () => {} }; const generationTimings = []; const stage = (key, label, fn) => timedStage(generationTimings, options, key, label, fn); const terrain = stage("terrain", "Terrain, rivers, and natural compartments", () => generateTerrainAndRivers(seed)); const { elevation, slope, sea, river, plain, agriculture, ridgeField, valleyField, basinField, coastalLowland, flowAccum, naturalBarrierScore, prefectureMask, landMask, naturalCompartmentId, naturalCompartments, } = terrain; const features = stage("human", "Human geography and transport", () => generateMapFeatures(seed, terrain)); const { settlementScore, villages, markets, modernCities, populationDensity, stations, industrialZones, logisticsParks, satelliteCities, newTowns, ports, landuse, stationInfluence, roadInfluence, railInfluence2, villageInfluence, } = features; const admin = stage("admin", "Municipal and prefectural administration", () => generateAdminLayout({ seed, prefectureMask: landMask || prefectureMask, focusedPrefectureMask: prefectureMask, naturalCompartmentId, naturalCompartments, 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, adminProgress: (event) => options?.onProgress?.({ ...event, key: "admin", label: event.status === "region-done" ? `Admin region ${event.regionId} done` : event.status === "admin-step" ? `Admin region ${event.regionId}: ${event.step}` : `Admin region ${event.regionId}`, timings: generationTimings.slice(), }), })); const output = stage("output", "Names, population totals, and final packaging", () => finishMapOutput({ seed, options, terrain, features, admin, })); output.generationTimings = generationTimings; output.generationTotalMs = generationTimings.reduce((sum, row) => sum + row.ms, 0); return output; } export async function generateMapAsync(seedInput = 114514, options = {}) { const seed = Number(seedInput) >>> 0; if (typeof options.onProgress !== "function") options = { ...options, onProgress: () => {} }; const generationTimings = []; const stage = (key, label, fn) => timedStageAsync(generationTimings, options, key, label, fn); const terrain = await stage("terrain", "Terrain, rivers, and natural compartments", () => generateTerrainAndRivers(seed)); const { elevation, slope, sea, river, plain, agriculture, ridgeField, valleyField, basinField, coastalLowland, flowAccum, naturalBarrierScore, prefectureMask, landMask, naturalCompartmentId, naturalCompartments, } = terrain; const features = await stage("human", "Human geography and transport", () => generateMapFeatures(seed, terrain)); const { settlementScore, villages, markets, modernCities, populationDensity, stations, industrialZones, logisticsParks, satelliteCities, newTowns, ports, landuse, stationInfluence, roadInfluence, railInfluence2, villageInfluence, } = features; const admin = await stage("admin", "Municipal and prefectural administration", () => generateAdminLayout({ seed, prefectureMask: landMask || prefectureMask, focusedPrefectureMask: prefectureMask, naturalCompartmentId, naturalCompartments, 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, adminProgress: (event) => options?.onProgress?.({ ...event, key: "admin", label: event.status === "region-done" ? `Admin region ${event.regionId} done` : event.status === "admin-step" ? `Admin region ${event.regionId}: ${event.step}` : `Admin region ${event.regionId}`, timings: generationTimings.slice(), }), })); const output = await stage("output", "Names, population totals, and final packaging", () => finishMapOutput({ seed, options, terrain, features, admin, })); output.generationTimings = generationTimings; output.generationTotalMs = generationTimings.reduce((sum, row) => sum + row.ms, 0); return output; }