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"; import { buildGeographicBasis, finalizeGeographicBasis } from "./mapGeography.js"; import { finalizeAdminAwareTransport } from "./mapPostAdminTransport.js"; export { CELL_SIZE, MAP_H, MAP_W, indexOf } from "./mapUtils.js"; function normalizeRectLike(rect) { if (!rect) return null; const x0 = Math.floor(Math.min(Number(rect.x0), Number(rect.x1))); const y0 = Math.floor(Math.min(Number(rect.y0), Number(rect.y1))); const x1 = Math.ceil(Math.max(Number(rect.x0), Number(rect.x1))); const y1 = Math.ceil(Math.max(Number(rect.y0), Number(rect.y1))); if (![x0, y0, x1, y1].every(Number.isFinite)) return null; return { x0, y0, x1, y1 }; } function normalizeGenerationContext(options = {}) { const originX = Math.floor(Number.isFinite(options.originX) ? options.originX : 0); const originY = Math.floor(Number.isFinite(options.originY) ? options.originY : 0); const width = Math.max(1, Math.floor(Number.isFinite(options.width) ? options.width : MAP_W)); const height = Math.max(1, Math.floor(Number.isFinite(options.height) ? options.height : MAP_H)); const variant = Math.max(0, Math.floor(Number.isFinite(options.variant) ? options.variant : 0)) >>> 0; const contextRect = normalizeRectLike(options.contextRect); return { worldNative: options.worldNative === true, legacyTerrain: options.legacyTerrain !== false, originX, originY, width, height, variant, contextRect, hasBoundaryWorld: !!options.boundaryWorld, }; } function mixUint(h, value) { h = Math.imul((h ^ (value >>> 0)) >>> 0, 2246822519) >>> 0; h ^= h >>> 13; return Math.imul(h, 3266489917) >>> 0; } function mixString(h, value) { const text = String(value ?? ""); for (let i = 0; i < text.length; i++) h = mixUint(h, text.charCodeAt(i)); return h >>> 0; } function contextualSeed(seed, context, options = {}) { let h = seed >>> 0; if (!context.worldNative && !context.variant && !context.originX && !context.originY) return h; h = mixString(h, options.terrainType || options.generationType || "auto"); h = mixUint(h, context.variant); h = mixUint(h, context.originX | 0); h = mixUint(h, context.originY | 0); h = mixUint(h, context.width); h = mixUint(h, context.height); if (context.contextRect) { h = mixUint(h, context.contextRect.x0 | 0); h = mixUint(h, context.contextRect.y0 | 0); h = mixUint(h, context.contextRect.x1 | 0); h = mixUint(h, context.contextRect.y1 | 0); } return h >>> 0; } function makeRuntimeOptions(options, baseSeed) { const generationContext = normalizeGenerationContext(options); const effectiveSeed = contextualSeed(baseSeed, generationContext, options); return { ...options, generationContext, baseSeed, effectiveSeed, originX: generationContext.originX, originY: generationContext.originY, width: generationContext.width, height: generationContext.height, variant: generationContext.variant, }; } function generateInitialTerrain(seed, options = {}) { // Production generation is intentionally pinned to the legacy high-detail // terrain/natural-compartment pipeline. Rect-native terrain helpers may remain // in the codebase for experiments, but they are not reachable from generateMap. return generateTerrainAndRivers(seed, options); } function terrainStageLabel() { return "Terrain, rivers, and natural compartments"; } 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 baseSeed = Number(seedInput) >>> 0; options = makeRuntimeOptions(options, baseSeed); const seed = options.effectiveSeed; 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", terrainStageLabel(options), () => generateInitialTerrain(seed, options)); const { elevation, slope, sea, river, plain, agriculture, ridgeField, valleyField, basinField, coastalLowland, flowAccum, naturalBarrierScore, prefectureMask, landMask, naturalCompartmentId, naturalCompartments, } = terrain; const geographyBasis = stage("geography", "Unified geographic basis", () => buildGeographicBasis(seed, terrain, options)); const terrainWithGeography = { ...terrain, geography: geographyBasis, generationContext: options.generationContext }; const features = stage("settlements", "Settlements, towns, ports, and land-use demand", () => generateMapFeatures(seed, terrainWithGeography, options)); const { settlementScore, villages, markets, modernCities, populationDensity, stations, industrialZones, logisticsParks, satelliteCities, newTowns, ports, landuse, stationInfluence, roadInfluence, railInfluence2, villageInfluence, } = features; const geography = stage("geographyFinal", "Unified accessibility and centrality fields", () => finalizeGeographicBasis(seed, terrain, features, geographyBasis, options)); const admin = stage("admin", "Municipal and prefectural administration", () => generateAdminLayout({ seed, generationContext: options.generationContext, prefectureMask: landMask || prefectureMask, focusedPrefectureMask: prefectureMask, naturalCompartmentId, naturalCompartments, sea, elevation, slope, river, ridgeField, naturalBarrierScore, valleyField, basinField, coastalLowland, flowAccum, plain, agriculture, geography, habitability: geography.habitability, accessibility: geography.accessibility, centrality: geography.centrality, geographicBarrier: geography.geographicBarrier, geographicBarrierCost: geography.geographicBarrierCost, adminBoundaryPreference: geography.adminBoundaryPreference, boundaryAvoidance: geography.boundaryAvoidance, 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(), }), })); stage("transport", "Administrative-aware final transport network", () => finalizeAdminAwareTransport({ seed, terrain, features, admin, geography, generationContext: options.generationContext })); const output = stage("output", "Names, population totals, and final packaging", () => finishMapOutput({ seed, options, terrain, features, admin, geography, })); output.generationTimings = generationTimings; output.generationTotalMs = generationTimings.reduce((sum, row) => sum + row.ms, 0); output.baseSeed = options.baseSeed; output.effectiveSeed = seed; output.generationContext = { ...options.generationContext }; return output; } export async function generateMapAsync(seedInput = 114514, options = {}) { const baseSeed = Number(seedInput) >>> 0; options = makeRuntimeOptions(options, baseSeed); const seed = options.effectiveSeed; 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", terrainStageLabel(options), () => generateInitialTerrain(seed, options)); const { elevation, slope, sea, river, plain, agriculture, ridgeField, valleyField, basinField, coastalLowland, flowAccum, naturalBarrierScore, prefectureMask, landMask, naturalCompartmentId, naturalCompartments, } = terrain; const geographyBasis = await stage("geography", "Unified geographic basis", () => buildGeographicBasis(seed, terrain, options)); const terrainWithGeography = { ...terrain, geography: geographyBasis, generationContext: options.generationContext }; const features = await stage("settlements", "Settlements, towns, ports, and land-use demand", () => generateMapFeatures(seed, terrainWithGeography, options)); const { settlementScore, villages, markets, modernCities, populationDensity, stations, industrialZones, logisticsParks, satelliteCities, newTowns, ports, landuse, stationInfluence, roadInfluence, railInfluence2, villageInfluence, } = features; const geography = await stage("geographyFinal", "Unified accessibility and centrality fields", () => finalizeGeographicBasis(seed, terrain, features, geographyBasis, options)); const admin = await stage("admin", "Municipal and prefectural administration", () => generateAdminLayout({ seed, generationContext: options.generationContext, prefectureMask: landMask || prefectureMask, focusedPrefectureMask: prefectureMask, naturalCompartmentId, naturalCompartments, sea, elevation, slope, river, ridgeField, naturalBarrierScore, valleyField, basinField, coastalLowland, flowAccum, plain, agriculture, geography, habitability: geography.habitability, accessibility: geography.accessibility, centrality: geography.centrality, geographicBarrier: geography.geographicBarrier, geographicBarrierCost: geography.geographicBarrierCost, adminBoundaryPreference: geography.adminBoundaryPreference, boundaryAvoidance: geography.boundaryAvoidance, 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(), }), })); await stage("transport", "Administrative-aware final transport network", () => finalizeAdminAwareTransport({ seed, terrain, features, admin, geography, generationContext: options.generationContext })); const output = await stage("output", "Names, population totals, and final packaging", () => finishMapOutput({ seed, options, terrain, features, admin, geography, })); output.generationTimings = generationTimings; output.generationTotalMs = generationTimings.reduce((sum, row) => sum + row.ms, 0); output.baseSeed = options.baseSeed; output.effectiveSeed = seed; output.generationContext = { ...options.generationContext }; return output; }