chinko
This commit is contained in:
parent
0359bb2445
commit
b17be0e0d2
21 changed files with 8034 additions and 2737 deletions
387
mapGeography.js
Normal file
387
mapGeography.js
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
import { INF, MAP_H, MAP_W, SIZE, clamp, indexOf, inside } from "./mapUtils.js";
|
||||
import { influenceFromPoints } from "./mapGeneratorHelpers.js";
|
||||
|
||||
const GEOGRAPHY_VERSION = "unified-geography-v1";
|
||||
|
||||
function localConfluenceScore(x, y, river) {
|
||||
let arms = 0;
|
||||
let strong = 0;
|
||||
for (const [dx, dy] of [[1,0],[-1,0],[0,1],[0,-1],[1,1],[-1,1],[1,-1],[-1,-1]]) {
|
||||
const nx = x + dx;
|
||||
const ny = y + dy;
|
||||
if (!inside(nx, ny)) continue;
|
||||
const rv = river?.[indexOf(nx, ny)] || 0;
|
||||
if (rv > 0.18) arms++;
|
||||
if (rv > 0.34) strong++;
|
||||
}
|
||||
return clamp((arms >= 3 ? 0.22 : arms === 2 ? 0.09 : 0) + strong * 0.04);
|
||||
}
|
||||
|
||||
function summarizeField(field, sea = null) {
|
||||
let min = INF;
|
||||
let max = -INF;
|
||||
let sum = 0;
|
||||
let count = 0;
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
if (sea?.[i]) continue;
|
||||
const v = field?.[i];
|
||||
if (!Number.isFinite(v)) continue;
|
||||
min = Math.min(min, v);
|
||||
max = Math.max(max, v);
|
||||
sum += v;
|
||||
count++;
|
||||
}
|
||||
return {
|
||||
min: count ? Math.round(min * 1000) / 1000 : 0,
|
||||
max: count ? Math.round(max * 1000) / 1000 : 0,
|
||||
mean: count ? Math.round((sum / count) * 1000) / 1000 : 0,
|
||||
};
|
||||
}
|
||||
|
||||
function buildProfiles(idField, terrain, fields) {
|
||||
if (!idField) return [];
|
||||
const { sea } = terrain;
|
||||
const rows = new Map();
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
if (sea?.[i]) continue;
|
||||
const id = idField[i];
|
||||
if (id < 0) continue;
|
||||
let row = rows.get(id);
|
||||
if (!row) {
|
||||
row = {
|
||||
id,
|
||||
area: 0,
|
||||
habitableCells: 0,
|
||||
lowlandCells: 0,
|
||||
barrierCells: 0,
|
||||
habitabilitySum: 0,
|
||||
accessibilitySum: 0,
|
||||
centralitySum: 0,
|
||||
barrierSum: 0,
|
||||
sx: 0,
|
||||
sy: 0,
|
||||
};
|
||||
rows.set(id, row);
|
||||
}
|
||||
const x = i % MAP_W;
|
||||
const y = Math.floor(i / MAP_W);
|
||||
const h = fields.habitability?.[i] || 0;
|
||||
const a = fields.accessibility?.[i] || 0;
|
||||
const c = fields.centrality?.[i] || fields.naturalCentrality?.[i] || 0;
|
||||
const b = fields.geographicBarrier?.[i] || 0;
|
||||
row.area++;
|
||||
row.habitabilitySum += h;
|
||||
row.accessibilitySum += a;
|
||||
row.centralitySum += c;
|
||||
row.barrierSum += b;
|
||||
row.sx += x;
|
||||
row.sy += y;
|
||||
if (h > 0.26) row.habitableCells++;
|
||||
if ((terrain.plain?.[i] || 0) > 0.24 || (terrain.basinField?.[i] || 0) > 0.24 || (terrain.coastalLowland?.[i] || 0) > 0.22) row.lowlandCells++;
|
||||
if (b > 0.52) row.barrierCells++;
|
||||
}
|
||||
return [...rows.values()]
|
||||
.map((row) => ({
|
||||
id: row.id,
|
||||
area: row.area,
|
||||
cx: Math.round((row.sx / Math.max(1, row.area)) * 10) / 10,
|
||||
cy: Math.round((row.sy / Math.max(1, row.area)) * 10) / 10,
|
||||
habitableRatio: Math.round((row.habitableCells / Math.max(1, row.area)) * 1000) / 1000,
|
||||
lowlandRatio: Math.round((row.lowlandCells / Math.max(1, row.area)) * 1000) / 1000,
|
||||
barrierRatio: Math.round((row.barrierCells / Math.max(1, row.area)) * 1000) / 1000,
|
||||
avgHabitability: Math.round((row.habitabilitySum / Math.max(1, row.area)) * 1000) / 1000,
|
||||
avgAccessibility: Math.round((row.accessibilitySum / Math.max(1, row.area)) * 1000) / 1000,
|
||||
avgCentrality: Math.round((row.centralitySum / Math.max(1, row.area)) * 1000) / 1000,
|
||||
avgBarrier: Math.round((row.barrierSum / Math.max(1, row.area)) * 1000) / 1000,
|
||||
}))
|
||||
.sort((a, b) => b.area - a.area || a.id - b.id);
|
||||
}
|
||||
|
||||
export function buildGeographicBasis(seed, terrain) {
|
||||
const {
|
||||
elevation,
|
||||
slope,
|
||||
sea,
|
||||
river,
|
||||
floodplain,
|
||||
plain,
|
||||
agriculture,
|
||||
ridgeField,
|
||||
valleyField,
|
||||
basinField,
|
||||
coastalLowland,
|
||||
flowAccum,
|
||||
naturalBarrierScore,
|
||||
portSuitability,
|
||||
crossingSuitability,
|
||||
passSuitability,
|
||||
depositionalLowland,
|
||||
alluvialFanField,
|
||||
deltaField,
|
||||
naturalCompartmentId,
|
||||
watershedId,
|
||||
} = terrain;
|
||||
|
||||
const habitability = new Float32Array(SIZE);
|
||||
const lowlandCapacity = new Float32Array(SIZE);
|
||||
const valleyAccess = new Float32Array(SIZE);
|
||||
const coastalAccess = new Float32Array(SIZE);
|
||||
const geographicBarrier = new Float32Array(SIZE);
|
||||
const geographicBarrierCost = new Float32Array(SIZE);
|
||||
const corridorSuitability = new Float32Array(SIZE);
|
||||
const accessibility = new Float32Array(SIZE);
|
||||
const naturalCentrality = new Float32Array(SIZE);
|
||||
const centrality = new Float32Array(SIZE);
|
||||
const adminBoundaryPreference = new Float32Array(SIZE);
|
||||
const boundaryAvoidance = new Float32Array(SIZE);
|
||||
|
||||
for (let y = 0; y < MAP_H; y++) {
|
||||
for (let x = 0; x < MAP_W; x++) {
|
||||
const i = indexOf(x, y);
|
||||
if (sea?.[i]) {
|
||||
geographicBarrier[i] = 1;
|
||||
geographicBarrierCost[i] = INF;
|
||||
continue;
|
||||
}
|
||||
|
||||
const depositional =
|
||||
(depositionalLowland?.[i] || 0) * 0.92 +
|
||||
(alluvialFanField?.[i] || 0) * 0.56 +
|
||||
(deltaField?.[i] || 0) * 0.86;
|
||||
const highElevation = clamp(((elevation?.[i] || 0) - 0.54) / 0.34);
|
||||
const lowSlope = clamp(1 - (slope?.[i] || 0) * 2.25);
|
||||
const confluence = x > 0 && y > 0 && x < MAP_W - 1 && y < MAP_H - 1 ? localConfluenceScore(x, y, river) : 0;
|
||||
const naturalBarrier = naturalBarrierScore?.[i] || 0;
|
||||
const pass = passSuitability?.[i] || 0;
|
||||
const crossing = crossingSuitability?.[i] || 0;
|
||||
const port = portSuitability?.[i] || 0;
|
||||
|
||||
lowlandCapacity[i] = clamp(
|
||||
(plain?.[i] || 0) * 0.38 +
|
||||
(agriculture?.[i] || 0) * 0.28 +
|
||||
(basinField?.[i] || 0) * 0.24 +
|
||||
(coastalLowland?.[i] || 0) * 0.18 +
|
||||
depositional * 0.20 +
|
||||
lowSlope * 0.13 -
|
||||
(slope?.[i] || 0) * 0.55 -
|
||||
(ridgeField?.[i] || 0) * 0.38 -
|
||||
highElevation * 0.62
|
||||
);
|
||||
valleyAccess[i] = clamp(
|
||||
(valleyField?.[i] || 0) * 0.48 +
|
||||
confluence * 0.40 +
|
||||
crossing * 0.22 +
|
||||
pass * 0.18 +
|
||||
(flowAccum?.[i] || 0) * 0.08 +
|
||||
(basinField?.[i] || 0) * 0.12 -
|
||||
(slope?.[i] || 0) * 0.42 -
|
||||
(ridgeField?.[i] || 0) * 0.22
|
||||
);
|
||||
coastalAccess[i] = clamp(
|
||||
(coastalLowland?.[i] || 0) * 0.46 +
|
||||
port * 0.34 +
|
||||
(deltaField?.[i] || 0) * 0.22 +
|
||||
(plain?.[i] || 0) * 0.10 -
|
||||
(slope?.[i] || 0) * 0.42 -
|
||||
(ridgeField?.[i] || 0) * 0.20
|
||||
);
|
||||
geographicBarrier[i] = clamp(
|
||||
(slope?.[i] || 0) * 0.60 +
|
||||
(ridgeField?.[i] || 0) * 0.52 +
|
||||
naturalBarrier * 0.58 +
|
||||
highElevation * 0.54 +
|
||||
Math.max(0, (elevation?.[i] || 0) - 0.68) * 0.90 -
|
||||
(valleyField?.[i] || 0) * 0.18 -
|
||||
(basinField?.[i] || 0) * 0.12 -
|
||||
pass * 0.30 -
|
||||
(plain?.[i] || 0) * 0.10 -
|
||||
(coastalLowland?.[i] || 0) * 0.06
|
||||
);
|
||||
geographicBarrierCost[i] = 1 + geographicBarrier[i] * 8.5 + (slope?.[i] || 0) * 3.0 + highElevation * 4.2;
|
||||
habitability[i] = clamp(
|
||||
lowlandCapacity[i] * 0.70 +
|
||||
valleyAccess[i] * 0.22 +
|
||||
coastalAccess[i] * 0.26 +
|
||||
(agriculture?.[i] || 0) * 0.16 +
|
||||
confluence * 0.07 -
|
||||
geographicBarrier[i] * 0.38 -
|
||||
(floodplain?.[i] || 0) * 0.04
|
||||
);
|
||||
corridorSuitability[i] = clamp(
|
||||
(valleyField?.[i] || 0) * 0.30 +
|
||||
(coastalLowland?.[i] || 0) * 0.23 +
|
||||
(plain?.[i] || 0) * 0.18 +
|
||||
(basinField?.[i] || 0) * 0.18 +
|
||||
pass * 0.22 +
|
||||
crossing * 0.12 +
|
||||
habitability[i] * 0.20 -
|
||||
geographicBarrier[i] * 0.32 -
|
||||
(slope?.[i] || 0) * 0.20
|
||||
);
|
||||
accessibility[i] = clamp(
|
||||
corridorSuitability[i] * 0.44 +
|
||||
port * 0.16 +
|
||||
crossing * 0.12 +
|
||||
pass * 0.10 +
|
||||
habitability[i] * 0.22 -
|
||||
geographicBarrier[i] * 0.16
|
||||
);
|
||||
naturalCentrality[i] = clamp(
|
||||
habitability[i] * 0.50 +
|
||||
accessibility[i] * 0.30 +
|
||||
(basinField?.[i] || 0) * 0.14 +
|
||||
(plain?.[i] || 0) * 0.10 +
|
||||
(coastalLowland?.[i] || 0) * 0.08 +
|
||||
port * 0.08 +
|
||||
confluence * 0.06 -
|
||||
geographicBarrier[i] * 0.20
|
||||
);
|
||||
centrality[i] = naturalCentrality[i];
|
||||
adminBoundaryPreference[i] = clamp(
|
||||
naturalBarrier * 0.54 +
|
||||
(ridgeField?.[i] || 0) * 0.32 +
|
||||
(river?.[i] || 0) * 0.12 +
|
||||
(flowAccum?.[i] || 0) * 0.08 -
|
||||
naturalCentrality[i] * 0.20 -
|
||||
habitability[i] * 0.08
|
||||
);
|
||||
boundaryAvoidance[i] = clamp(
|
||||
naturalCentrality[i] * 0.54 +
|
||||
habitability[i] * 0.24 +
|
||||
(plain?.[i] || 0) * 0.12 +
|
||||
(basinField?.[i] || 0) * 0.08 -
|
||||
geographicBarrier[i] * 0.18
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const fields = {
|
||||
version: GEOGRAPHY_VERSION,
|
||||
habitability,
|
||||
lowlandCapacity,
|
||||
valleyAccess,
|
||||
coastalAccess,
|
||||
geographicBarrier,
|
||||
geographicBarrierCost,
|
||||
barrierCost: geographicBarrierCost,
|
||||
corridorSuitability,
|
||||
accessibility,
|
||||
naturalCentrality,
|
||||
centrality,
|
||||
adminBoundaryPreference,
|
||||
boundaryAvoidance,
|
||||
};
|
||||
|
||||
const geographyDebug = {
|
||||
version: GEOGRAPHY_VERSION,
|
||||
stage: "terrain-derived",
|
||||
fields: {
|
||||
habitability: summarizeField(habitability, sea),
|
||||
accessibility: summarizeField(accessibility, sea),
|
||||
centrality: summarizeField(centrality, sea),
|
||||
geographicBarrier: summarizeField(geographicBarrier, sea),
|
||||
corridorSuitability: summarizeField(corridorSuitability, sea),
|
||||
},
|
||||
naturalCompartmentCount: new Set([...naturalCompartmentId || []].filter((id, i) => id >= 0 && !sea?.[i])).size,
|
||||
watershedCount: new Set([...watershedId || []].filter((id, i) => id >= 0 && !sea?.[i])).size,
|
||||
};
|
||||
|
||||
return {
|
||||
...fields,
|
||||
compartmentProfiles: buildProfiles(naturalCompartmentId, terrain, fields),
|
||||
watershedProfiles: buildProfiles(watershedId, terrain, fields),
|
||||
geographyDebug,
|
||||
};
|
||||
}
|
||||
|
||||
export function finalizeGeographicBasis(seed, terrain, features, baseGeography) {
|
||||
const base = baseGeography || buildGeographicBasis(seed, terrain);
|
||||
const { sea } = terrain;
|
||||
const {
|
||||
roadInfluence,
|
||||
railInfluence2,
|
||||
stationInfluence,
|
||||
populationDensity,
|
||||
ports = [],
|
||||
modernCities = [],
|
||||
markets = [],
|
||||
} = features || {};
|
||||
|
||||
const portInfluence = influenceFromPoints(ports, 16, (p) => p.portClass === "major" ? 1.1 : p.portClass === "regional" ? 0.78 : 0.34);
|
||||
const cityInfluence = influenceFromPoints(modernCities, 24, (p) => clamp((p.population || 50000) / 240000, 0.35, 2.4));
|
||||
const marketInfluence = influenceFromPoints(markets, 13, (p) => clamp((p.population || 10000) / 48000, 0.18, 1.0));
|
||||
|
||||
const accessibility = new Float32Array(SIZE);
|
||||
const transportAccessibility = new Float32Array(SIZE);
|
||||
const centrality = new Float32Array(SIZE);
|
||||
const humanCentrality = new Float32Array(SIZE);
|
||||
const boundaryAvoidance = new Float32Array(SIZE);
|
||||
const adminBoundaryPreference = new Float32Array(SIZE);
|
||||
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
if (sea?.[i]) continue;
|
||||
transportAccessibility[i] = clamp(
|
||||
(roadInfluence?.[i] || 0) * 0.36 +
|
||||
(railInfluence2?.[i] || 0) * 0.28 +
|
||||
(stationInfluence?.[i] || 0) * 0.20 +
|
||||
portInfluence[i] * 0.16
|
||||
);
|
||||
accessibility[i] = clamp(
|
||||
(base.accessibility?.[i] || 0) * 0.46 +
|
||||
transportAccessibility[i] * 0.48 +
|
||||
(base.corridorSuitability?.[i] || 0) * 0.10 -
|
||||
(base.geographicBarrier?.[i] || 0) * 0.10
|
||||
);
|
||||
humanCentrality[i] = clamp(
|
||||
(populationDensity?.[i] || 0) * 0.38 +
|
||||
cityInfluence[i] * 0.30 +
|
||||
marketInfluence[i] * 0.14 +
|
||||
transportAccessibility[i] * 0.18
|
||||
);
|
||||
centrality[i] = clamp(
|
||||
(base.naturalCentrality?.[i] || 0) * 0.44 +
|
||||
accessibility[i] * 0.26 +
|
||||
humanCentrality[i] * 0.36 -
|
||||
(base.geographicBarrier?.[i] || 0) * 0.08
|
||||
);
|
||||
boundaryAvoidance[i] = clamp(
|
||||
(base.boundaryAvoidance?.[i] || 0) * 0.52 +
|
||||
centrality[i] * 0.42 +
|
||||
transportAccessibility[i] * 0.10
|
||||
);
|
||||
adminBoundaryPreference[i] = clamp(
|
||||
(base.adminBoundaryPreference?.[i] || 0) * 0.84 -
|
||||
centrality[i] * 0.12 -
|
||||
transportAccessibility[i] * 0.08
|
||||
);
|
||||
}
|
||||
|
||||
const finalFields = {
|
||||
...base,
|
||||
accessibility,
|
||||
transportAccessibility,
|
||||
centrality,
|
||||
humanCentrality,
|
||||
boundaryAvoidance,
|
||||
adminBoundaryPreference,
|
||||
};
|
||||
|
||||
return {
|
||||
...finalFields,
|
||||
compartmentProfiles: buildProfiles(terrain.naturalCompartmentId, terrain, finalFields),
|
||||
watershedProfiles: buildProfiles(terrain.watershedId, terrain, finalFields),
|
||||
geographyDebug: {
|
||||
...(base.geographyDebug || {}),
|
||||
stage: "finalized-with-human-network",
|
||||
fields: {
|
||||
...(base.geographyDebug?.fields || {}),
|
||||
accessibility: summarizeField(accessibility, sea),
|
||||
transportAccessibility: summarizeField(transportAccessibility, sea),
|
||||
centrality: summarizeField(centrality, sea),
|
||||
humanCentrality: summarizeField(humanCentrality, sea),
|
||||
adminBoundaryPreference: summarizeField(adminBoundaryPreference, sea),
|
||||
boundaryAvoidance: summarizeField(boundaryAvoidance, sea),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue