2026-05-21 13:20:19 +09:00
import { CELL _SIZE , MAP _H , MAP _W , indexOf } from "./mapUtils.js" ;
2026-05-29 14:31:42 +09:00
import { generateTerrainAndRivers } from "./mapTerrain.js" ;
2026-05-26 00:45:01 +09:00
import { generateMapFeatures } from "./mapFeatures.js" ;
2026-05-21 13:20:19 +09:00
import { finishMapOutput } from "./mapOutput.js" ;
import { generateAdminLayout } from "./mapAdminStage.js" ;
2026-05-28 00:30:09 +09:00
import { buildGeographicBasis , finalizeGeographicBasis } from "./mapGeography.js" ;
import { finalizeAdminAwareTransport } from "./mapPostAdminTransport.js" ;
2026-05-21 13:20:19 +09:00
export { CELL _SIZE , MAP _H , MAP _W , indexOf } from "./mapUtils.js" ;
2026-05-29 14:31:42 +09:00
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 ,
} ;
}
2026-05-28 23:51:55 +09:00
function generateInitialTerrain ( seed , options = { } ) {
2026-05-29 14:31:42 +09:00
// 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.
2026-05-28 23:51:55 +09:00
return generateTerrainAndRivers ( seed , options ) ;
}
2026-05-29 14:31:42 +09:00
function terrainStageLabel ( ) {
return "Terrain, rivers, and natural compartments" ;
2026-05-28 23:51:55 +09:00
}
2026-05-24 19:33:09 +09:00
function nowMs ( ) {
return typeof performance !== "undefined" && performance . now ? performance . now ( ) : Date . now ( ) ;
}
function timedStage ( timings , options , key , label , fn ) {
2026-05-24 22:52:22 +09:00
options ? . onProgress ? . ( { status : "start" , key , label , timings : timings . slice ( ) , startedAt : nowMs ( ) } ) ;
2026-05-24 19:33:09 +09:00
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 ;
}
2026-05-24 22:52:22 +09:00
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 ;
}
2026-05-21 13:20:19 +09:00
export function generateMap ( seedInput = 114514 , options = { } ) {
2026-05-29 14:31:42 +09:00
const baseSeed = Number ( seedInput ) >>> 0 ;
options = makeRuntimeOptions ( options , baseSeed ) ;
const seed = options . effectiveSeed ;
2026-05-24 19:33:09 +09:00
if ( typeof options . onProgress !== "function" ) options = { ... options , onProgress : ( ) => { } } ;
const generationTimings = [ ] ;
const stage = ( key , label , fn ) => timedStage ( generationTimings , options , key , label , fn ) ;
2026-05-21 13:20:19 +09:00
2026-05-28 23:51:55 +09:00
const terrain = stage ( "terrain" , terrainStageLabel ( options ) , ( ) => generateInitialTerrain ( seed , options ) ) ;
2026-05-21 13:20:19 +09:00
const {
elevation ,
slope ,
sea ,
river ,
plain ,
agriculture ,
ridgeField ,
valleyField ,
basinField ,
coastalLowland ,
flowAccum ,
2026-05-21 22:03:14 +09:00
naturalBarrierScore ,
2026-05-21 13:20:19 +09:00
prefectureMask ,
2026-05-26 15:32:27 +09:00
landMask ,
naturalCompartmentId ,
naturalCompartments ,
2026-05-21 13:20:19 +09:00
} = terrain ;
2026-05-29 14:31:42 +09:00
const geographyBasis = stage ( "geography" , "Unified geographic basis" , ( ) => buildGeographicBasis ( seed , terrain , options ) ) ;
const terrainWithGeography = { ... terrain , geography : geographyBasis , generationContext : options . generationContext } ;
2026-05-28 00:30:09 +09:00
2026-05-29 14:31:42 +09:00
const features = stage ( "settlements" , "Settlements, towns, ports, and land-use demand" , ( ) => generateMapFeatures ( seed , terrainWithGeography , options ) ) ;
2026-05-21 13:20:19 +09:00
const {
2026-05-24 21:21:17 +09:00
settlementScore ,
villages ,
markets ,
modernCities ,
populationDensity ,
stations ,
industrialZones ,
logisticsParks ,
satelliteCities ,
newTowns ,
ports ,
landuse ,
stationInfluence ,
roadInfluence ,
railInfluence2 ,
villageInfluence ,
2026-05-21 13:20:19 +09:00
} = features ;
2026-05-29 14:31:42 +09:00
const geography = stage ( "geographyFinal" , "Unified accessibility and centrality fields" , ( ) => finalizeGeographicBasis ( seed , terrain , features , geographyBasis , options ) ) ;
2026-05-28 00:30:09 +09:00
2026-05-24 21:21:17 +09:00
const admin = stage ( "admin" , "Municipal and prefectural administration" , ( ) => generateAdminLayout ( {
2026-05-29 14:31:42 +09:00
seed , generationContext : options . generationContext , prefectureMask : landMask || prefectureMask , focusedPrefectureMask : prefectureMask , naturalCompartmentId , naturalCompartments , sea , elevation , slope , river , ridgeField , naturalBarrierScore , valleyField , basinField , coastalLowland , flowAccum , plain , agriculture ,
2026-05-28 00:30:09 +09:00
geography , habitability : geography . habitability , accessibility : geography . accessibility , centrality : geography . centrality , geographicBarrier : geography . geographicBarrier , geographicBarrierCost : geography . geographicBarrierCost , adminBoundaryPreference : geography . adminBoundaryPreference , boundaryAvoidance : geography . boundaryAvoidance ,
2026-05-21 13:20:19 +09:00
settlementScore , populationDensity , stationInfluence , roadInfluence , railInfluence2 , villageInfluence , landuse , modernCities , satelliteCities , newTowns , markets , villages , ports , stations , industrialZones , logisticsParks ,
2026-05-24 19:33:09 +09:00
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 ( ) ,
} ) ,
} ) ) ;
2026-05-21 13:20:19 +09:00
2026-05-29 14:31:42 +09:00
stage ( "transport" , "Administrative-aware final transport network" , ( ) => finalizeAdminAwareTransport ( { seed , terrain , features , admin , geography , generationContext : options . generationContext } ) ) ;
2026-05-28 00:30:09 +09:00
2026-05-24 19:33:09 +09:00
const output = stage ( "output" , "Names, population totals, and final packaging" , ( ) => finishMapOutput ( {
2026-05-24 21:21:17 +09:00
seed ,
options ,
terrain ,
features ,
admin ,
2026-05-28 00:30:09 +09:00
geography ,
2026-05-24 19:33:09 +09:00
} ) ) ;
output . generationTimings = generationTimings ;
output . generationTotalMs = generationTimings . reduce ( ( sum , row ) => sum + row . ms , 0 ) ;
2026-05-29 14:31:42 +09:00
output . baseSeed = options . baseSeed ;
output . effectiveSeed = seed ;
output . generationContext = { ... options . generationContext } ;
2026-05-24 19:33:09 +09:00
return output ;
2026-05-21 13:20:19 +09:00
}
2026-05-24 22:52:22 +09:00
export async function generateMapAsync ( seedInput = 114514 , options = { } ) {
2026-05-29 14:31:42 +09:00
const baseSeed = Number ( seedInput ) >>> 0 ;
options = makeRuntimeOptions ( options , baseSeed ) ;
const seed = options . effectiveSeed ;
2026-05-24 22:52:22 +09:00
if ( typeof options . onProgress !== "function" ) options = { ... options , onProgress : ( ) => { } } ;
const generationTimings = [ ] ;
const stage = ( key , label , fn ) => timedStageAsync ( generationTimings , options , key , label , fn ) ;
2026-05-28 23:51:55 +09:00
const terrain = await stage ( "terrain" , terrainStageLabel ( options ) , ( ) => generateInitialTerrain ( seed , options ) ) ;
2026-05-24 22:52:22 +09:00
const {
elevation ,
slope ,
sea ,
river ,
plain ,
agriculture ,
ridgeField ,
valleyField ,
basinField ,
coastalLowland ,
flowAccum ,
naturalBarrierScore ,
prefectureMask ,
2026-05-26 15:32:27 +09:00
landMask ,
naturalCompartmentId ,
naturalCompartments ,
2026-05-24 22:52:22 +09:00
} = terrain ;
2026-05-29 14:31:42 +09:00
const geographyBasis = await stage ( "geography" , "Unified geographic basis" , ( ) => buildGeographicBasis ( seed , terrain , options ) ) ;
const terrainWithGeography = { ... terrain , geography : geographyBasis , generationContext : options . generationContext } ;
2026-05-28 00:30:09 +09:00
2026-05-29 14:31:42 +09:00
const features = await stage ( "settlements" , "Settlements, towns, ports, and land-use demand" , ( ) => generateMapFeatures ( seed , terrainWithGeography , options ) ) ;
2026-05-24 22:52:22 +09:00
const {
settlementScore ,
villages ,
markets ,
modernCities ,
populationDensity ,
stations ,
industrialZones ,
logisticsParks ,
satelliteCities ,
newTowns ,
ports ,
landuse ,
stationInfluence ,
roadInfluence ,
railInfluence2 ,
villageInfluence ,
} = features ;
2026-05-29 14:31:42 +09:00
const geography = await stage ( "geographyFinal" , "Unified accessibility and centrality fields" , ( ) => finalizeGeographicBasis ( seed , terrain , features , geographyBasis , options ) ) ;
2026-05-28 00:30:09 +09:00
2026-05-24 22:52:22 +09:00
const admin = await stage ( "admin" , "Municipal and prefectural administration" , ( ) => generateAdminLayout ( {
2026-05-29 14:31:42 +09:00
seed , generationContext : options . generationContext , prefectureMask : landMask || prefectureMask , focusedPrefectureMask : prefectureMask , naturalCompartmentId , naturalCompartments , sea , elevation , slope , river , ridgeField , naturalBarrierScore , valleyField , basinField , coastalLowland , flowAccum , plain , agriculture ,
2026-05-28 00:30:09 +09:00
geography , habitability : geography . habitability , accessibility : geography . accessibility , centrality : geography . centrality , geographicBarrier : geography . geographicBarrier , geographicBarrierCost : geography . geographicBarrierCost , adminBoundaryPreference : geography . adminBoundaryPreference , boundaryAvoidance : geography . boundaryAvoidance ,
2026-05-24 22:52:22 +09:00
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 ( ) ,
} ) ,
} ) ) ;
2026-05-29 14:31:42 +09:00
await stage ( "transport" , "Administrative-aware final transport network" , ( ) => finalizeAdminAwareTransport ( { seed , terrain , features , admin , geography , generationContext : options . generationContext } ) ) ;
2026-05-28 00:30:09 +09:00
2026-05-24 22:52:22 +09:00
const output = await stage ( "output" , "Names, population totals, and final packaging" , ( ) => finishMapOutput ( {
seed ,
options ,
terrain ,
features ,
admin ,
2026-05-28 00:30:09 +09:00
geography ,
2026-05-24 22:52:22 +09:00
} ) ) ;
output . generationTimings = generationTimings ;
output . generationTotalMs = generationTimings . reduce ( ( sum , row ) => sum + row . ms , 0 ) ;
2026-05-29 14:31:42 +09:00
output . baseSeed = options . baseSeed ;
output . effectiveSeed = seed ;
output . generationContext = { ... options . generationContext } ;
2026-05-24 22:52:22 +09:00
return output ;
}