This commit is contained in:
33333-33333 2026-05-29 14:31:42 +09:00
commit f2d0306d96
13 changed files with 1434 additions and 516 deletions

View file

@ -1,5 +1,5 @@
import { CELL_SIZE, MAP_H, MAP_W, indexOf } from "./mapUtils.js";
import { generateInitialTerrainRect, generateTerrainAndRivers } from "./mapTerrain.js";
import { generateTerrainAndRivers } from "./mapTerrain.js";
import { generateMapFeatures } from "./mapFeatures.js";
import { finishMapOutput } from "./mapOutput.js";
import { generateAdminLayout } from "./mapAdminStage.js";
@ -8,19 +8,91 @@ 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 = {}) {
// The original high-detail terrain system remains the default for full-map
// generation. Rect-native terrain is available as an explicit option and is
// used by patch generation, but the historical noise/natural-compartment
// pipeline is still the visual baseline for ordinary maps.
if (options?.rectNativeInitial === true) return generateInitialTerrainRect(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(options = {}) {
return options?.rectNativeInitial === true
? "Rect-native terrain, rivers, and natural compartments"
: "Terrain, rivers, and natural compartments";
function terrainStageLabel() {
return "Terrain, rivers, and natural compartments";
}
function nowMs() {
@ -60,7 +132,9 @@ async function timedStageAsync(timings, options, key, label, fn) {
export function generateMap(seedInput = 114514, options = {}) {
const seed = Number(seedInput) >>> 0;
const baseSeed = Number(seedInput) >>> 0;
options = makeRuntimeOptions(options, baseSeed);
const seed = options.effectiveSeed;
if (typeof options.onProgress !== "function") options = { ...options, onProgress: () => {} };
const generationTimings = [];
@ -86,10 +160,10 @@ export function generateMap(seedInput = 114514, options = {}) {
naturalCompartments,
} = terrain;
const geographyBasis = stage("geography", "Unified geographic basis", () => buildGeographicBasis(seed, terrain));
const terrainWithGeography = { ...terrain, geography: geographyBasis };
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));
const features = stage("settlements", "Settlements, towns, ports, and land-use demand", () => generateMapFeatures(seed, terrainWithGeography, options));
const {
settlementScore,
villages,
@ -109,10 +183,10 @@ export function generateMap(seedInput = 114514, options = {}) {
villageInfluence,
} = features;
const geography = stage("geographyFinal", "Unified accessibility and centrality fields", () => finalizeGeographicBasis(seed, terrain, features, geographyBasis));
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, prefectureMask: landMask || prefectureMask, focusedPrefectureMask: prefectureMask, naturalCompartmentId, naturalCompartments, sea, elevation, slope, river, ridgeField, naturalBarrierScore, valleyField, basinField, coastalLowland, flowAccum, plain, agriculture,
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?.({
@ -127,7 +201,7 @@ export function generateMap(seedInput = 114514, options = {}) {
}),
}));
stage("transport", "Administrative-aware final transport network", () => finalizeAdminAwareTransport({ seed, terrain, features, admin, geography }));
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,
@ -139,11 +213,16 @@ export function generateMap(seedInput = 114514, options = {}) {
}));
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 seed = Number(seedInput) >>> 0;
const baseSeed = Number(seedInput) >>> 0;
options = makeRuntimeOptions(options, baseSeed);
const seed = options.effectiveSeed;
if (typeof options.onProgress !== "function") options = { ...options, onProgress: () => {} };
const generationTimings = [];
@ -169,10 +248,10 @@ export async function generateMapAsync(seedInput = 114514, options = {}) {
naturalCompartments,
} = terrain;
const geographyBasis = await stage("geography", "Unified geographic basis", () => buildGeographicBasis(seed, terrain));
const terrainWithGeography = { ...terrain, geography: geographyBasis };
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));
const features = await stage("settlements", "Settlements, towns, ports, and land-use demand", () => generateMapFeatures(seed, terrainWithGeography, options));
const {
settlementScore,
villages,
@ -192,10 +271,10 @@ export async function generateMapAsync(seedInput = 114514, options = {}) {
villageInfluence,
} = features;
const geography = await stage("geographyFinal", "Unified accessibility and centrality fields", () => finalizeGeographicBasis(seed, terrain, features, geographyBasis));
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, prefectureMask: landMask || prefectureMask, focusedPrefectureMask: prefectureMask, naturalCompartmentId, naturalCompartments, sea, elevation, slope, river, ridgeField, naturalBarrierScore, valleyField, basinField, coastalLowland, flowAccum, plain, agriculture,
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?.({
@ -210,7 +289,7 @@ export async function generateMapAsync(seedInput = 114514, options = {}) {
}),
}));
await stage("transport", "Administrative-aware final transport network", () => finalizeAdminAwareTransport({ seed, terrain, features, admin, geography }));
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,
@ -222,5 +301,8 @@ export async function generateMapAsync(seedInput = 114514, options = {}) {
}));
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;
}